git-submodule update: Add --branch option
authorW. Trevor King <wking@tremily.us>
Mon, 26 Nov 2012 19:38:32 +0000 (14:38 -0500)
committerW. Trevor King <wking@tremily.us>
Mon, 26 Nov 2012 20:29:13 +0000 (15:29 -0500)
This allows users to checkout the current
superproject-recorded-submodule-sha as a branch, avoiding the detached
head state that the standard submodule update creates.  This may be
useful for the existing --rebase/--merge workflows which already avoid
detached heads.

It is also useful if you want easy tracking of upstream branches.  The
particular upstream branch to be tracked is configured locally with
.git/modules/<name>/config.  With the new option Ævar's suggested

  $ git submodule foreach 'git checkout $(git config --file $toplevel/.gitm
odules submodule.$name.branch) && git pull'

reduces to a

  $ git submodule update --branch

after each supermodule .gitmodules edit, and a

  $ git submodule foreach 'git pull'

whenever you feel like updating the submodules.  Your still on you're
own to commit (or not) the updated submodule hashes in the
superproject's .gitmodules.

Signed-off-by: W. Trevor King <wking@tremily.us>
Documentation/git-submodule.txt
git-submodule.sh
t/t7406-submodule-update.sh

index d0b44364c6d24ce27f9d84d701eaa9cb436c0942..34392a142ee323794d3e7220679842b2c35f8e28 100644 (file)
@@ -13,7 +13,7 @@ SYNOPSIS
              [-f|--force] [--reference <repository>] [--] <repository> [<path>]
 'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]
 'git submodule' [--quiet] init [--] [<path>...]
-'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase]
+'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--branch] [--rebase]
              [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
 'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>]
              [commit] [--] [<path>...]
@@ -136,11 +136,11 @@ init::
 
 update::
        Update the registered submodules, i.e. clone missing submodules and
-       checkout the commit specified in the index of the containing repository.
-       This will make the submodules HEAD be detached unless `--rebase` or
-       `--merge` is specified or the key `submodule.$name.update` is set to
-       `rebase`, `merge` or `none`. `none` can be overridden by specifying
-       `--checkout`.
+       checkout the commit specified in the index of the containing
+       repository.  This will make the submodules HEAD be detached unless
+       `--branch`, `--rebase`, `--merge` is specified or the key
+       `submodule.$name.update` is set to `branch`, `rebase`, `merge` or
+       `none`. `none` can be overridden by specifying `--checkout`.
 +
 If the submodule is not yet initialized, and you just want to use the
 setting as stored in .gitmodules, you can automatically initialize the
@@ -207,7 +207,13 @@ OPTIONS
 
 -b::
 --branch::
-       Branch of repository to add as submodule.
+       When used with the add command, gives the branch of repository to
+       add as submodule.
++
+When used with the update command, checks out a branch named
+`submodule.<name>.branch` (as set by `--local-branch`) pointing at the
+current HEAD SHA-1.  This is useful for commands like `update
+--rebase` that do not work on detached heads.
 
 --local-branch::
        Record a branch name used as `submodule.<path>.branch` in
index c51b6aeb18ab701408b14c13acd7c7d9995c2fa0..28eb4b1a6d95cc16eb91af10ee2fc2851b772387 100755 (executable)
@@ -8,7 +8,7 @@ dashless=$(basename "$0" | sed -e 's/-/ /')
 USAGE="[--quiet] add [-b branch] [--local-branch[=<branch>]] [-f|--force] [--reference <repository>] [--] <repository> [<path>]
    or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]
    or: $dashless [--quiet] init [--] [<path>...]
-   or: $dashless [--quiet] update [--init] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
+   or: $dashless [--quiet] update [--init] [-N|--no-fetch] [-f|--force] [--branch] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
    or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
    or: $dashless [--quiet] foreach [--recursive] <command>
    or: $dashless [--quiet] sync [--] [<path>...]"
@@ -539,6 +539,9 @@ cmd_update()
                -f|--force)
                        force=$1
                        ;;
+               -b|--branch)
+                       update="branch"
+                       ;;
                -r|--rebase)
                        update="rebase"
                        ;;
@@ -593,6 +596,7 @@ cmd_update()
                fi
                name=$(module_name "$sm_path") || exit
                url=$(git config submodule."$name".url)
+               branch=$(git config submodule."$name".branch)
                if ! test -z "$update"
                then
                        update_module=$update
@@ -627,7 +631,7 @@ Maybe you want to use 'update --init'?")"
                        die "$(eval_gettext "Unable to find current revision in submodule path '\$sm_path'")"
                fi
 
-               if test "$subsha1" != "$sha1" -o -n "$force"
+               if test "$subsha1" != "$sha1" -o -n "$force" -o "$update_module" = "branch"
                then
                        subforce=$force
                        # If we don't already have a -f flag and the submodule has never been checked out
@@ -650,16 +654,21 @@ Maybe you want to use 'update --init'?")"
                        case ";$cloned_modules;" in
                        *";$name;"*)
                                # then there is no local change to integrate
-                               update_module= ;;
+                               case "$update_module" in
+                                       rebase|merge)
+                                               update_module=
+                                               ;;
+                               esac
+                               ;;
                        esac
 
                        must_die_on_failure=
                        case "$update_module" in
                        rebase)
                                command="git rebase"
-                               die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$sm_path'")"
+                               die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$sm_path'")"     
                                say_msg="$(eval_gettext "Submodule path '\$sm_path': rebased into '\$sha1'")"
-                               must_die_on_failure=yes
+                       must_die_on_failure=yes
                                ;;
                        merge)
                                command="git merge"
@@ -674,15 +683,30 @@ Maybe you want to use 'update --init'?")"
                                ;;
                        esac
 
-                       if (clear_local_git_env; cd "$sm_path" && $command "$sha1")
+                       if test "$subsha1" != "$sha1" -o -n "$force"
                        then
-                               say "$say_msg"
-                       elif test -n "$must_die_on_failure"
+                               if (clear_local_git_env; cd "$sm_path" && $command "$sha1")
+                               then
+                                       say "$say_msg"
+                               elif test -n "$must_die_on_failure"
+                               then
+                                       die_with_status 2 "$die_msg"
+                               else
+                                       err="${err};$die_msg"
+                                       continue
+                               fi
+                       fi
+
+                       if test "$update_module" = "branch" -a -n "$branch"
                        then
-                               die_with_status 2 "$die_msg"
-                       else
-                               err="${err};$die_msg"
-                               continue
+                               if (clear_local_git_env; cd "$sm_path" &&
+                                       git branch -f "$branch" "$sha1" &&
+                                       git checkout "$branch")
+                               then
+                                       say "$(eval_gettext "Submodule path '\$sm_path': checked out branch '\$branch'")"
+                               else
+                                       err="${err};$(eval_gettext "Unable to checkout branch '\$branch' in submodule path '\$sm_path'")"
+                               fi
                        fi
                fi
 
index 15426530e49ef2e42cda2528f4a72a784eb40cd9..c876a8b82061235724cf80c0c6d95c0fc00fc72f 100755 (executable)
@@ -6,7 +6,8 @@
 test_description='Test updating submodules
 
 This test verifies that "git submodule update" detaches the HEAD of the
-submodule and "git submodule update --rebase/--merge" does not detach the HEAD.
+submodule and "git submodule update --branch/--rebase/--merge" does not
+detach the HEAD.
 '
 
 . ./test-lib.sh
@@ -135,6 +136,53 @@ test_expect_success 'submodule update --force forcibly checks out submodules' '
        )
 '
 
+test_expect_success 'submodule update --branch detaches without submodule.<name>.branch' '
+       (cd super/submodule &&
+         git checkout master
+       ) &&
+       (cd super &&
+        (cd submodule &&
+         compare_head
+        ) &&
+        git submodule update --branch submodule &&
+        (cd submodule &&
+         test "$(git status -s file)" = ""
+        )
+       )
+'
+
+test_expect_success 'submodule update --branch staying on master' '
+       (cd super/submodule &&
+         git checkout master
+       ) &&
+       (cd super &&
+        (cd submodule &&
+         compare_head
+        ) &&
+        git config submodule.submodule.branch master
+        git submodule update --branch submodule &&
+        cd submodule &&
+        test "refs/heads/master" = "$(git symbolic-ref -q HEAD)" &&
+        compare_head
+       )
+'
+
+test_expect_success 'submodule update --branch creating a new branch' '
+       (cd super/submodule &&
+         git checkout master
+       ) &&
+       (cd super &&
+        (cd submodule &&
+         compare_head
+        ) &&
+        git config submodule.submodule.branch new-branch
+        git submodule update --branch submodule &&
+        cd submodule &&
+        test "refs/heads/new-branch" = "$(git symbolic-ref -q HEAD)" &&
+        compare_head
+       )
+'
+
 test_expect_success 'submodule update --rebase staying on master' '
        (cd super/submodule &&
          git checkout master