10
10
# The original idea comes from Eric W. Biederman, in
11
11
# http://article.gmane.org/gmane.comp.version-control.git/22407
12
12
13
- USAGE=' (--continue | --abort | --skip | [--onto <branch>] <upstream> [<branch>])'
13
+ USAGE=' (--continue | --abort | --skip | [--preserve-merges] [--verbose]
14
+ [--onto <branch>] <upstream> [<branch>])'
14
15
15
16
. git-sh-setup
16
17
require_work_tree
17
18
18
19
DOTEST=" $GIT_DIR /.dotest-merge"
19
20
TODO=" $DOTEST " /todo
20
21
DONE=" $DOTEST " /done
22
+ REWRITTEN=" $DOTEST " /rewritten
23
+ PRESERVE_MERGES=
21
24
STRATEGY=
22
25
VERBOSE=
23
26
@@ -68,6 +71,8 @@ die_abort () {
68
71
pick_one () {
69
72
case " $1 " in -n) sha1=$2 ;; * ) sha1=$1 ;; esac
70
73
git rev-parse --verify $sha1 || die " Invalid commit name: $sha1 "
74
+ test -d " $REWRITTEN " &&
75
+ pick_one_preserving_merges " $@ " && return
71
76
parent_sha1=$( git rev-parse --verify $sha1 ^ 2> /dev/null)
72
77
current_sha1=$( git rev-parse --verify HEAD)
73
78
if [ $current_sha1 = $parent_sha1 ]; then
@@ -79,6 +84,75 @@ pick_one () {
79
84
fi
80
85
}
81
86
87
+ pick_one_preserving_merges () {
88
+ case " $1 " in -n) sha1=$2 ;; * ) sha1=$1 ;; esac
89
+ sha1=$( git rev-parse $sha1 )
90
+
91
+ if [ -f " $DOTEST " /current-commit ]
92
+ then
93
+ current_commit=$( cat " $DOTEST " /current-commit) &&
94
+ git rev-parse HEAD > " $REWRITTEN " /$current_commit &&
95
+ rm " $DOTEST " /current-commit ||
96
+ die " Cannot write current commit's replacement sha1"
97
+ fi
98
+
99
+ # rewrite parents; if none were rewritten, we can fast-forward.
100
+ fast_forward=t
101
+ preserve=t
102
+ new_parents=
103
+ for p in $( git rev-list --parents -1 $sha1 | cut -d\ -f2-)
104
+ do
105
+ if [ -f " $REWRITTEN " /$p ]
106
+ then
107
+ preserve=f
108
+ new_p=$( cat " $REWRITTEN " /$p )
109
+ test $p ! = $new_p && fast_forward=f
110
+ case " $new_parents " in
111
+ * $new_p * )
112
+ ;; # do nothing; that parent is already there
113
+ * )
114
+ new_parents=" $new_parents $new_p "
115
+ esac
116
+ fi
117
+ done
118
+ case $fast_forward in
119
+ t)
120
+ echo " Fast forward to $sha1 "
121
+ test $preserve =f && echo $sha1 > " $REWRITTEN " /$sha1
122
+ ;;
123
+ f)
124
+ test " a$1 " = a-n && die " Refusing to squash a merge: $sha1 "
125
+
126
+ first_parent=$( expr " $new_parents " : " \([^ ]*\)" )
127
+ # detach HEAD to current parent
128
+ git checkout $first_parent 2> /dev/null ||
129
+ die " Cannot move HEAD to $first_parent "
130
+
131
+ echo $sha1 > " $DOTEST " /current-commit
132
+ case " $new_parents " in
133
+ \ * \ * )
134
+ # redo merge
135
+ author_script=$( get_author_ident_from_commit $sha1 )
136
+ eval " $author_script "
137
+ msg=" $( git cat-file commit $sha1 | \
138
+ sed -e ' 1,/^$/d' -e " s/[\"\\ ]/\\\\ &/g" ) "
139
+ # NEEDSWORK: give rerere a chance
140
+ if ! git merge $STRATEGY -m " $msg " $new_parents
141
+ then
142
+ echo " $msg " > " $GIT_DIR " /MERGE_MSG
143
+ warn Error redoing merge $sha1
144
+ warn
145
+ warn After fixup, please use
146
+ die " $author_script git commit"
147
+ fi
148
+ ;;
149
+ * )
150
+ git cherry-pick $STRATEGY " $@ " ||
151
+ die_with_patch $sha1 " Could not pick $sha1 "
152
+ esac
153
+ esac
154
+ }
155
+
82
156
do_next () {
83
157
read command sha1 rest < " $TODO "
84
158
case " $command " in
@@ -155,7 +229,15 @@ do_next () {
155
229
HEADNAME=$( cat " $DOTEST " /head-name) &&
156
230
OLDHEAD=$( cat " $DOTEST " /head) &&
157
231
SHORTONTO=$( git rev-parse --short $( cat " $DOTEST " /onto) ) &&
158
- NEWHEAD=$( git rev-parse HEAD) &&
232
+ if [ -d " $REWRITTEN " ]
233
+ then
234
+ test -f " $DOTEST " /current-commit &&
235
+ current_commit=$( cat " $DOTEST " /current-commit) &&
236
+ git rev-parse HEAD > " $REWRITTEN " /$current_commit
237
+ NEWHEAD=$( cat " $REWRITTEN " /$OLDHEAD )
238
+ else
239
+ NEWHEAD=$( git rev-parse HEAD)
240
+ fi &&
159
241
message=" $GIT_REFLOG_ACTION : $HEADNAME onto $SHORTONTO )" &&
160
242
git update-ref -m " $message " $HEADNAME $NEWHEAD $OLDHEAD &&
161
243
git symbolic-ref HEAD $HEADNAME &&
226
308
-v|--verbose)
227
309
VERBOSE=t
228
310
;;
311
+ -p|--preserve-merges)
312
+ PRESERVE_MERGES=t
313
+ ;;
229
314
-i|--interactive)
230
315
# yeah, we know
231
316
;;
274
359
echo $UPSTREAM > " $DOTEST " /upstream
275
360
echo $ONTO > " $DOTEST " /onto
276
361
test t = " $VERBOSE " && : > " $DOTEST " /verbose
362
+ if [ t = " $PRESERVE_MERGES " ]
363
+ then
364
+ # $REWRITTEN contains files for each commit that is
365
+ # reachable by at least one merge base of $HEAD and
366
+ # $UPSTREAM. They are not necessarily rewritten, but
367
+ # their children might be.
368
+ # This ensures that commits on merged, but otherwise
369
+ # unrelated side branches are left alone. (Think "X"
370
+ # in the man page's example.)
371
+ mkdir " $REWRITTEN " &&
372
+ for c in $( git merge-base --all $HEAD $UPSTREAM )
373
+ do
374
+ echo $ONTO > " $REWRITTEN " /$c ||
375
+ die " Could not init rewritten commits"
376
+ done
377
+ MERGES_OPTION=
378
+ else
379
+ MERGES_OPTION=--no-merges
380
+ fi
277
381
278
382
SHORTUPSTREAM=$( git rev-parse --short $UPSTREAM )
279
383
SHORTHEAD=$( git rev-parse --short $HEAD )
286
390
# edit = use commit, but stop for amending
287
391
# squash = use commit, but meld into previous commit
288
392
EOF
289
- git rev-list --no-merges --pretty=oneline --abbrev-commit \
393
+ git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
290
394
--abbrev=7 --reverse $UPSTREAM ..$HEAD | \
291
395
sed " s/^/pick /" >> " $TODO "
292
396
0 commit comments