Skip to content

Commit 1be0659

Browse files
author
Junio C Hamano
committed
checkout: merge local modifications while switching branches.
* Instead of going interactive, introduce a command line switch '-m' to allow merging changes when normal two-way merge by read-tree prevents branch switching. * Leave the unmerged stages intact if automerge fails, but reset index entries of cleanly merged paths to that of the new branch, so that "git diff" (not "git diff HEAD") would show the local modifications. * Swap the order of trees in read-tree three-way merge used in the fallback, so that `git diff` to show the conflicts become more natural. * Describe the new option and give more examples in the documentation. Signed-off-by: Junio C Hamano <[email protected]>
1 parent 19205ac commit 1be0659

File tree

2 files changed

+107
-29
lines changed

2 files changed

+107
-29
lines changed

Documentation/git-checkout.txt

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ git-checkout - Checkout and switch to a branch.
77

88
SYNOPSIS
99
--------
10-
'git-checkout' [-f] [-b <new_branch>] [<branch>] [<paths>...]
10+
'git-checkout' [-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]
1111

1212
DESCRIPTION
1313
-----------
@@ -34,6 +34,19 @@ OPTIONS
3434
-b::
3535
Create a new branch and start it at <branch>.
3636

37+
-m::
38+
If you have local modifications to a file that is
39+
different between the current branch and the branch you
40+
are switching to, the command refuses to switch
41+
branches, to preserve your modifications in context.
42+
With this option, a three-way merge between the current
43+
branch, your working tree contents, and the new branch
44+
is done, and you will be on the new branch.
45+
+
46+
When a merge conflict happens, the index entries for conflicting
47+
paths are left unmerged, and you need to resolve the conflicts
48+
and mark the resolved paths with `git update-index`.
49+
3750
<new_branch>::
3851
Name for the new branch.
3952

@@ -42,13 +55,13 @@ OPTIONS
4255
commit. Defaults to HEAD.
4356

4457

45-
EXAMPLE
46-
-------
58+
EXAMPLES
59+
--------
4760

48-
The following sequence checks out the `master` branch, reverts
61+
. The following sequence checks out the `master` branch, reverts
4962
the `Makefile` to two revisions back, deletes hello.c by
5063
mistake, and gets it back from the index.
51-
64+
+
5265
------------
5366
$ git checkout master <1>
5467
$ git checkout master~2 Makefile <2>
@@ -59,15 +72,64 @@ $ git checkout hello.c <3>
5972
<2> take out a file out of other commit
6073
<3> or "git checkout -- hello.c", as in the next example.
6174
------------
62-
75+
+
6376
If you have an unfortunate branch that is named `hello.c`, the
6477
last step above would be confused as an instruction to switch to
6578
that branch. You should instead write:
66-
79+
+
6780
------------
6881
$ git checkout -- hello.c
6982
------------
7083

84+
. After working in a wrong branch, switching to the correct
85+
branch you would want to is done with:
86+
+
87+
------------
88+
$ git checkout mytopic
89+
------------
90+
+
91+
However, your "wrong" branch and correct "mytopic" branch may
92+
differ in files that you have locally modified, in which case,
93+
the above checkout would fail like this:
94+
+
95+
------------
96+
$ git checkout mytopic
97+
fatal: Entry 'frotz' not uptodate. Cannot merge.
98+
------------
99+
+
100+
You can give the `-m` flag to the command, which would try a
101+
three-way merge:
102+
+
103+
------------
104+
$ git checkout -m mytopic
105+
Auto-merging frotz
106+
------------
107+
+
108+
After this three-way merge, the local modifications are _not_
109+
registered in your index file, so `git diff` would show you what
110+
changes you made since the tip of the new branch.
111+
112+
. When a merge conflict happens during switching branches with
113+
the `-m` option, you would see something like this:
114+
+
115+
------------
116+
$ git checkout -m mytopic
117+
Auto-merging frotz
118+
merge: warning: conflicts during merge
119+
ERROR: Merge conflict in frotz
120+
fatal: merge program failed
121+
------------
122+
+
123+
At this point, `git diff` shows the changes cleanly merged as in
124+
the previous example, as well as the changes in the conflicted
125+
files. Edit and resolve the conflict and mark it resolved with
126+
`git update-index` as usual:
127+
+
128+
------------
129+
$ edit frotz
130+
$ git update-index frotz
131+
------------
132+
71133

72134
Author
73135
------

git-checkout.sh

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/sh
22

3-
USAGE='[-f] [-b <new_branch>] [<branch>] [<paths>...]'
3+
USAGE='[-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]'
44
SUBDIRECTORY_OK=Sometimes
55
. git-sh-setup
66

@@ -9,6 +9,7 @@ new=
99
force=
1010
branch=
1111
newbranch=
12+
merge=
1213
while [ "$#" != "0" ]; do
1314
arg="$1"
1415
shift
@@ -26,6 +27,9 @@ while [ "$#" != "0" ]; do
2627
"-f")
2728
force=1
2829
;;
30+
-m)
31+
merge=1
32+
;;
2933
--)
3034
break
3135
;;
@@ -71,7 +75,7 @@ done
7175

7276
if test "$#" -ge 1
7377
then
74-
if test '' != "$newbranch$force"
78+
if test '' != "$newbranch$force$merge"
7579
then
7680
die "updating paths and switching branches or forcing are incompatible."
7781
fi
@@ -121,32 +125,44 @@ then
121125
git-checkout-index -q -f -u -a
122126
else
123127
git-update-index --refresh >/dev/null
124-
git-read-tree -m -u $old $new || (
125-
echo >&2 -n "Try automerge [y/N]? "
126-
read yesno
127-
case "$yesno" in [yY]*) ;; *) exit 1 ;; esac
128-
129-
# NEEDSWORK: We may want to reset the index from the $new for
130-
# these paths after the automerge happens, but it is not done
131-
# yet. Probably we need to leave unmerged ones alone, and
132-
# yank the object name & mode from $new for cleanly merged
133-
# paths and stuff them in the index.
134-
135-
names=`git diff-files --name-only`
136-
case "$names" in
137-
'') ;;
138-
*)
139-
echo "$names" | git update-index --remove --stdin ;;
128+
merge_error=$(git-read-tree -m -u $old $new 2>&1) || (
129+
case "$merge" in
130+
'')
131+
echo >&2 "$merge_error"
132+
exit 1 ;;
140133
esac
141134

135+
# Match the index to the working tree, and do a three-way.
136+
git diff-files --name-only | git update-index --remove --stdin &&
142137
work=`git write-tree` &&
143-
git read-tree -m -u $old $work $new || exit
138+
git read-tree --reset $new &&
139+
git checkout-index -f -u -q -a &&
140+
git read-tree -m -u $old $new $work || exit
141+
144142
if result=`git write-tree 2>/dev/null`
145143
then
146-
echo >&2 "Trivially automerged." ;# can this even happen?
147-
exit 0
144+
echo >&2 "Trivially automerged."
145+
else
146+
git merge-index -o git-merge-one-file -a
148147
fi
149-
git merge-index -o git-merge-one-file -a
148+
149+
# Do not register the cleanly merged paths in the index yet.
150+
# this is not a real merge before committing, but just carrying
151+
# the working tree changes along.
152+
unmerged=`git ls-files -u`
153+
git read-tree --reset $new
154+
case "$unmerged" in
155+
'') ;;
156+
*)
157+
(
158+
z40=0000000000000000000000000000000000000000
159+
echo "$unmerged" |
160+
sed -e 's/^[0-7]* [0-9a-f]* /'"0 $z40 /"
161+
echo "$unmerged"
162+
) | git update-index --index-info
163+
;;
164+
esac
165+
exit 0
150166
)
151167
fi
152168

0 commit comments

Comments
 (0)