-
Notifications
You must be signed in to change notification settings - Fork 0
/
cg-merge
executable file
·289 lines (249 loc) · 9.68 KB
/
cg-merge
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#!/usr/bin/env bash
#
# Merge a branch to the current branch
# Copyright (c) Petr Baudis, 2005
#
# Takes a parameter identifying the branch to be merged, defaulting
# to 'origin' or the current branch's default remote branch, see `cg-fetch`
# for details).
#
# This command merges all changes currently in the given branch to your
# current branch. This can produce a merging commit on your branch sticking
# the two branch together (so-called 'tree merge'). However in case there
# are no changes in your branch that wouldn't be in the remote branch, no
# merge commit is done and commit pointer of your branch is just updated
# to the last commit on the remote branch (so-called 'fast-forward merge').
#
# In case of conflicts being generated by the merge, you have to examine
# the tree (cg-merge will tell you which files contain commits; the commits
# are denoted by rcsmerge-like markers <<<<, ====, and >>>>) and then do
# `cg-commit` yourself. `cg-commit` will know that you are committing a merge
# and will record it properly.
#
# Note that when you are merging remote branches, `cg-merge` will use them
# in the state they are currently at in your repository. If you want to
# fetch the latest changes from the remote repository, use `cg-fetch`. If you
# want to fetch the changes and then merge them to your branch, use the command
# `cg-update`.
#
# Also note that if you have local changes in your tree that you did not
# commit, cg-merge will always preserve them when fast-forwarding. When doing
# a tree merge, it will preserve them if they don't conflict with the merged
# changes, and report an error otherwise. In short, it should do the Right
# Thing (tm), never lose your local changes and never let them mix up with
# the merge.
#
# OPTIONS
# -------
# -b BASE_COMMIT:: Specify the base commit for the merge
# Parameter specifies the base commit for the merge. Otherwise, the
# least common ancestor is automatically selected.
#
# -j:: Join current branch with BRANCH_NAME
# Join the current branch and BRANCH_NAME together. This makes sense
# when the branches have no common history, meaning they are actually
# not branches related at all as far as GIT is concerned. Merging such
# branches might be a user error and you well may be doing something
# you do not want; but equally likely, you may actually WANT to join
# the projects together, which is what this option does.
#
# -n:: Disable autocommitting
# Parameter specifies that you want to have tree merge never
# autocommitted, but want to review and commit it manually. This will
# basically make cg-merge always behave like there were conflicts
# during the merge.
#
# --squash:: Use "squash" merge to record pending commits as a single merge commit
# "Squash" merge - condense all the to-be-merged commits to a single
# merge commit. This means "throw away history of the branch I'm
# merging", essentially like in CVS or SVN, with the same problem -
# re-merging with that branch later will cause trouble. This is not
# recommended unless you actually really want to flatten the history
# of the merged branch, e.g. when merging topical branches to your
# mainline (you want to have the logical change you developed in
# a branch as a single "do it" commit instead of a sequence of
# "do it I", "fix it", "do it II", "fix it II", "fix it III" commits
# like you would get with a regular merge).
#
# -v:: Enable verbosity
# Display more verbose output - most notably list all the files
# touched by the merged changes.
#
# HOOKS
# -----
# '.git/hooks/merge-pre' BRANCH BASE CURHEAD MERGEDHEAD MERGETYPE::
# If the file exists and is executable it will be executed right
# before the merge itself happens. The merge is cancelled if the script
# returns non-zero exit code.
# - MERGETYPE is either "forward", "squash", or "tree".
#
# '.git/hooks/merge-post' BRANCH BASE CURHEAD MERGEDHEAD MERGETYPE STATUS::
# If the file exists and is executable it will be executed after
# the merge is done.
# - MERGETYPE is either "forward", "squash", or "tree".
# - For 'forward', the STATUS is "ok" or "localchanges", while for
# "squash" and "tree" the STATUS can be "localchanges", "conflicts",
# "nocommit", or "ok".
# Developer's documentation:
#
# ENVIRONMENT
# -----------
# _cg_orig_head::
# The original commit ID of the to-be-merged branch, if cg-merge
# is called right after fetch. This is used to do better decision
# about whether to fast-forward or tree-merge.
# Testsuite: Largely covered (t92xx testsuite family, incomplete coverage;
# missing: hooks, -n, -b, --squash)
USAGE="cg-merge [-n] [-b BASE_COMMIT] [-j] [--squash] [-v] [BRANCH_NAME]"
_git_requires_root=1
. "${COGITO_LIB}"cg-Xlib || exit 1
prehook()
{
if [ -x "$_git/hooks/merge-pre" ]; then
"$_git/hooks/merge-pre" "$branchname" "$base" "$head" "$branch" "$@" || die "merge cancelled by hook"
fi
}
posthook()
{
if [ -x "$_git/hooks/merge-post" ]; then
"$_git/hooks/merge-post" "$branchname" "$base" "$head" "$branch" "$@"
fi
}
head="$(cg-object-id -c)" || exit 1
careful=
base=
join=
squash=
verbose=
while optparse; do
if optparse -n; then
careful=1
elif optparse -c; then
warn "cg-merge -c is deprecated, cg-merge -n is the new flag name"
careful=1
elif optparse -b=; then
base="$(cg-object-id -c "$OPTARG")" || exit 1
elif optparse -j; then
join=1
# -s reserved to strategy
elif optparse --squash; then
squash=1
elif optparse -v; then
verbose=1
else
optfail
fi
done
branchname="${ARGS[0]}"
[ "$branchname" ] || branchname="$(choose_origin branches "what to merge?")" || exit 1
branch=$(cg-object-id -c "$branchname") || exit 1
[ "$base" ] || base="$(git-merge-base --all "$head" "$branch")"
if [ ! "$join" ]; then
[ "$base" ] || die "unable to automatically determine merge base (consider cg-merge -j)"
baselist=($base)
if [ "${#baselist[@]}" -gt "1" ]; then
echo "Multiple merge base candidates, please select one manually (by running cg-merge -b BASE [BRANCH]):"
echo "${baselist[*]}" | tr ' ' '\n'
echo
conservative_merge_base "${baselist[@]}" # -> _cg_baselist
echo -n "The most conservative base (but likely a lot of conflicts): "
echo "${_cg_baselist[*]}"
exit 3
fi >&2
else
[ "$base" ] && die "joining branches with common history is something I refuse to do"
index="$(mktemp -t gitmerge.XXXXXX)" || exit $?
GIT_INDEX_FILE="$index" git-read-tree
base="$(GIT_INDEX_FILE="$index" git-write-tree)"
rm "$index"
fi
[ -s "$_git/blocked" ] && die "merge blocked: $(cat "$_git/blocked")"
# Deprecated as of 2006-11-17
[ -s "$_git/merging" ] && die "old-style merge state detected, panicking; you upgraded cogito in the middle of a merge! redo the merge, cg-reset will bring you back to the starting line"
statedir="$_git/cg-merge-state"
if [ -s "$statedir/merging" ] && grep -q "$branch" "$statedir/merging"; then
echo "Branch already merged in the working tree." >&2
exit 0
fi
if [ "$base" = "$branch" ]; then
echo "Branch already fully merged." >&2
exit 0
fi
if { [ "$head" = "$base" ] || [ "$head" = "$_cg_orig_head" ]; } && [ ! "$squash" ] && [ ! -s "$statedir/merging" ]; then
# No need to do explicit merge with a merge commit; just bring
# the HEAD forward.
echo "Fast-forwarding $base -> $branch" >&2
echo -e "\ton top of $head ..." >&2
[ "$verbose" ] && git-diff-tree --abbrev -r "$(cg-object-id -t "$head")" "$(cg-object-id -t "$branch")"
prehook forward
if ! tree_timewarp "forward" "yes, rollback (or rather rollforth) the tree!" "$head" "$branch"; then
posthook forward localchanges
exit 1
fi
posthook forward ok
exit 0
fi
git-update-index --refresh >/dev/null
if [ ! "$squash" ]; then
[ -s "$statedir/squashing" ] && die "cannot combine squashing and non-squashing merges"
echo "Merging $base -> $branch" >&2
echo -e "\tto $head ..." >&2
mergetype="tree"
else
echo "Squashing $base -> $branch" >&2
echo -e "\ton top of $head ..." >&2
mergetype="squash"
fi
[ "$verbose" ] && git-diff-tree --abbrev -r "$(cg-object-id -t "$base")" "$(cg-object-id -t "$branch")"
prehook "$mergetype"
mkdir -p "$statedir"
git-diff-index --name-only "$(cg-object-id -t $head)" >>"$statedir/commit-ignore"
# Don't keep around useless empty files
[ -s "$statedir/commit-ignore" ] || rm "$statedir/commit-ignore"
if ! git-read-tree -u -m "$(cg-object-id -t "$base")" "$(cg-object-id -t "$head")" "$(cg-object-id -t "$branch")"; then
echo "cg-merge: git-read-tree failed (merge likely blocked by local changes)" >&2
posthook "$mergetype" localchanges
rm -f "$statedir/commit-ignore"
rmdir "$statedir"
exit 1
fi
echo "$base" >>"$statedir/merge-base"
echo "$branch" >>"$statedir/merging"
echo "$branchname" >>"$statedir/merging-sym"
[ "$squash" ] && echo "$branch" >>"$statedir/squashing"
if ! git-merge-index -o -q "${COGITO_LIB}"cg-Xmergefile -a || [ "$careful" ]; then
echo >&2
if [ ! "$careful" ]; then
echo " Conflicts during merge. Do cg-commit after resolving them." >&2
else
echo " Do cg-commit after reviewing the merge." >&2
fi
if [ -s "$statedir/commit-ignore" ]; then
echo " cg-reset will cancel the merge (but also your pending local changes!)." >&2
echo >&2
echo " These files contained local modifications and won't be automatically chosen for committing:" >&2
cat "$statedir/commit-ignore" >&2
else
echo " cg-reset will cancel the merge." >&2
echo >&2
fi
posthook "$mergetype" conflicts
exit 2
fi
echo
readtree=
if ! cg-commit -C; then
readtree=1
echo "cg-merge: COMMIT FAILED, retry manually" >&2
if [ -s "$statedir/commit-ignore" ]; then
echo " cg-reset will cancel the merge (but also your pending local changes!)." >&2
else
echo " cg-reset will cancel the merge." >&2
fi
posthook "$mergetype" nocommit
fi
[ "$readtree" ] && git-read-tree -m HEAD
# update_index here is safe because no tree<->index desyncs could've
# survived the read-tree above
update_index
posthook "$mergetype" ok