diff options
-rw-r--r-- | Documentation/git-checkout.txt | 76 | ||||
-rwxr-xr-x | git-checkout.sh | 60 |
2 files changed, 107 insertions, 29 deletions
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 9442c66b1..df9a61867 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -7,7 +7,7 @@ git-checkout - Checkout and switch to a branch. SYNOPSIS -------- -'git-checkout' [-f] [-b <new_branch>] [<branch>] [<paths>...] +'git-checkout' [-f] [-b <new_branch>] [-m] [<branch>] [<paths>...] DESCRIPTION ----------- @@ -34,6 +34,19 @@ OPTIONS -b:: Create a new branch and start it at <branch>. +-m:: + If you have local modifications to a file that is + different between the current branch and the branch you + are switching to, the command refuses to switch + branches, to preserve your modifications in context. + With this option, a three-way merge between the current + branch, your working tree contents, and the new branch + is done, and you will be on the new branch. ++ +When a merge conflict happens, the index entries for conflicting +paths are left unmerged, and you need to resolve the conflicts +and mark the resolved paths with `git update-index`. + <new_branch>:: Name for the new branch. @@ -42,13 +55,13 @@ OPTIONS commit. Defaults to HEAD. -EXAMPLE -------- +EXAMPLES +-------- -The following sequence checks out the `master` branch, reverts +. The following sequence checks out the `master` branch, reverts the `Makefile` to two revisions back, deletes hello.c by mistake, and gets it back from the index. - ++ ------------ $ git checkout master <1> $ git checkout master~2 Makefile <2> @@ -59,15 +72,64 @@ $ git checkout hello.c <3> <2> take out a file out of other commit <3> or "git checkout -- hello.c", as in the next example. ------------ - ++ If you have an unfortunate branch that is named `hello.c`, the last step above would be confused as an instruction to switch to that branch. You should instead write: - ++ ------------ $ git checkout -- hello.c ------------ +. After working in a wrong branch, switching to the correct +branch you would want to is done with: ++ +------------ +$ git checkout mytopic +------------ ++ +However, your "wrong" branch and correct "mytopic" branch may +differ in files that you have locally modified, in which case, +the above checkout would fail like this: ++ +------------ +$ git checkout mytopic +fatal: Entry 'frotz' not uptodate. Cannot merge. +------------ ++ +You can give the `-m` flag to the command, which would try a +three-way merge: ++ +------------ +$ git checkout -m mytopic +Auto-merging frotz +------------ ++ +After this three-way merge, the local modifications are _not_ +registered in your index file, so `git diff` would show you what +changes you made since the tip of the new branch. + +. When a merge conflict happens during switching branches with +the `-m` option, you would see something like this: ++ +------------ +$ git checkout -m mytopic +Auto-merging frotz +merge: warning: conflicts during merge +ERROR: Merge conflict in frotz +fatal: merge program failed +------------ ++ +At this point, `git diff` shows the changes cleanly merged as in +the previous example, as well as the changes in the conflicted +files. Edit and resolve the conflict and mark it resolved with +`git update-index` as usual: ++ +------------ +$ edit frotz +$ git update-index frotz +------------ + Author ------ diff --git a/git-checkout.sh b/git-checkout.sh index 76e6a41c6..bd7f00730 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -1,6 +1,6 @@ #!/bin/sh -USAGE='[-f] [-b <new_branch>] [<branch>] [<paths>...]' +USAGE='[-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]' SUBDIRECTORY_OK=Sometimes . git-sh-setup @@ -9,6 +9,7 @@ new= force= branch= newbranch= +merge= while [ "$#" != "0" ]; do arg="$1" shift @@ -26,6 +27,9 @@ while [ "$#" != "0" ]; do "-f") force=1 ;; + -m) + merge=1 + ;; --) break ;; @@ -71,7 +75,7 @@ done if test "$#" -ge 1 then - if test '' != "$newbranch$force" + if test '' != "$newbranch$force$merge" then die "updating paths and switching branches or forcing are incompatible." fi @@ -121,32 +125,44 @@ then git-checkout-index -q -f -u -a else git-update-index --refresh >/dev/null - git-read-tree -m -u $old $new || ( - echo >&2 -n "Try automerge [y/N]? " - read yesno - case "$yesno" in [yY]*) ;; *) exit 1 ;; esac - - # NEEDSWORK: We may want to reset the index from the $new for - # these paths after the automerge happens, but it is not done - # yet. Probably we need to leave unmerged ones alone, and - # yank the object name & mode from $new for cleanly merged - # paths and stuff them in the index. - - names=`git diff-files --name-only` - case "$names" in - '') ;; - *) - echo "$names" | git update-index --remove --stdin ;; + merge_error=$(git-read-tree -m -u $old $new 2>&1) || ( + case "$merge" in + '') + echo >&2 "$merge_error" + exit 1 ;; esac + # Match the index to the working tree, and do a three-way. + git diff-files --name-only | git update-index --remove --stdin && work=`git write-tree` && - git read-tree -m -u $old $work $new || exit + git read-tree --reset $new && + git checkout-index -f -u -q -a && + git read-tree -m -u $old $new $work || exit + if result=`git write-tree 2>/dev/null` then - echo >&2 "Trivially automerged." ;# can this even happen? - exit 0 + echo >&2 "Trivially automerged." + else + git merge-index -o git-merge-one-file -a fi - git merge-index -o git-merge-one-file -a + + # Do not register the cleanly merged paths in the index yet. + # this is not a real merge before committing, but just carrying + # the working tree changes along. + unmerged=`git ls-files -u` + git read-tree --reset $new + case "$unmerged" in + '') ;; + *) + ( + z40=0000000000000000000000000000000000000000 + echo "$unmerged" | + sed -e 's/^[0-7]* [0-9a-f]* /'"0 $z40 /" + echo "$unmerged" + ) | git update-index --index-info + ;; + esac + exit 0 ) fi |