]>
Commit | Line | Data |
---|---|---|
6912ea95 | 1 | #!/bin/sh |
0ca71b37 AP |
2 | # |
3 | # git-subtree.sh: split/join git repositories in subdirectories of this one | |
4 | # | |
b77172f8 | 5 | # Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com> |
0ca71b37 | 6 | # |
5a356977 | 7 | |
22d55074 LS |
8 | if test -z "$GIT_EXEC_PATH" || test "${PATH#"${GIT_EXEC_PATH}:"}" = "$PATH" || ! test -f "$GIT_EXEC_PATH/git-sh-setup" |
9 | then | |
10 | echo >&2 'It looks like either your git installation or your' | |
11 | echo >&2 'git-subtree installation is broken.' | |
12 | echo >&2 | |
13 | echo >&2 "Tips:" | |
14 | echo >&2 " - If \`git --exec-path\` does not print the correct path to" | |
15 | echo >&2 " your git install directory, then set the GIT_EXEC_PATH" | |
16 | echo >&2 " environment variable to the correct directory." | |
17 | echo >&2 " - Make sure that your \`${0##*/}\` file is either in your" | |
18 | echo >&2 " PATH or in your git exec path (\`$(git --exec-path)\`)." | |
19 | echo >&2 " - You should run git-subtree as \`git ${0##*/git-}\`," | |
20 | echo >&2 " not as \`${0##*/}\`." >&2 | |
21 | exit 126 | |
22 | fi | |
23 | ||
0ca71b37 | 24 | OPTS_SPEC="\ |
f4f29557 | 25 | git subtree add --prefix=<prefix> <commit> |
1c3e0f00 | 26 | git subtree add --prefix=<prefix> <repository> <ref> |
13648af5 | 27 | git subtree merge --prefix=<prefix> <commit> |
1c3e0f00 AB |
28 | git subtree pull --prefix=<prefix> <repository> <ref> |
29 | git subtree push --prefix=<prefix> <repository> <ref> | |
77128ed9 | 30 | git subtree split --prefix=<prefix> <commit> |
0ca71b37 | 31 | -- |
96db2c04 AP |
32 | h,help show the help |
33 | q quiet | |
942dce55 | 34 | d show debug messages |
6e25f79f | 35 | P,prefix= the name of the subdir to split out |
2da0969a | 36 | m,message= use the given message as the commit message for the merge commit |
13648af5 | 37 | options for 'split' |
d0eb1b14 | 38 | annotate= add a prefix to commit message of new commits |
43a39512 AP |
39 | b,branch= create a new branch from the split subtree |
40 | ignore-joins ignore prior --rejoin commits | |
96db2c04 AP |
41 | onto= try connecting new tree to an existing one |
42 | rejoin merge the new branch back into HEAD | |
6ccc71a9 | 43 | options for 'add', 'merge', and 'pull' |
8e79043c | 44 | squash merge subtree changes as a single commit |
0ca71b37 | 45 | " |
9c632ea2 | 46 | |
e2b11e42 LS |
47 | arg_debug= |
48 | arg_command= | |
49 | arg_prefix= | |
50 | arg_split_branch= | |
51 | arg_split_onto= | |
52 | arg_split_rejoin= | |
53 | arg_split_ignore_joins= | |
54 | arg_split_annotate= | |
55 | arg_addmerge_squash= | |
56 | arg_addmerge_message= | |
0ca71b37 | 57 | |
5cdae0f6 | 58 | # Usage: debug [MSG...] |
d7fd792e | 59 | debug () { |
e2b11e42 | 60 | if test -n "$arg_debug" |
6ae6a233 | 61 | then |
2ded109b | 62 | printf "%s\n" "$*" >&2 |
942dce55 AP |
63 | fi |
64 | } | |
65 | ||
5cdae0f6 | 66 | # Usage: progress [MSG...] |
d7fd792e | 67 | progress () { |
6d43585a | 68 | if test -z "$GIT_QUIET" |
6ae6a233 | 69 | then |
2ded109b | 70 | printf "%s\r" "$*" >&2 |
0ca71b37 AP |
71 | fi |
72 | } | |
73 | ||
5cdae0f6 | 74 | # Usage: assert CMD... |
d7fd792e | 75 | assert () { |
6ae6a233 DA |
76 | if ! "$@" |
77 | then | |
a94f9110 | 78 | die "assertion failed: $*" |
2573354e AP |
79 | fi |
80 | } | |
81 | ||
5a356977 LS |
82 | main () { |
83 | if test $# -eq 0 | |
84 | then | |
85 | set -- -h | |
86 | fi | |
87 | eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)" | |
88 | . git-sh-setup | |
89 | require_work_tree | |
6ae6a233 | 90 | |
5a356977 LS |
91 | while test $# -gt 0 |
92 | do | |
93 | opt="$1" | |
6ae6a233 | 94 | shift |
5a356977 LS |
95 | |
96 | case "$opt" in | |
97 | -q) | |
6d43585a | 98 | GIT_QUIET=1 |
5a356977 LS |
99 | ;; |
100 | -d) | |
e2b11e42 | 101 | arg_debug=1 |
5a356977 LS |
102 | ;; |
103 | --annotate) | |
e2b11e42 | 104 | arg_split_annotate="$1" |
5a356977 LS |
105 | shift |
106 | ;; | |
107 | --no-annotate) | |
e2b11e42 | 108 | arg_split_annotate= |
5a356977 LS |
109 | ;; |
110 | -b) | |
e2b11e42 | 111 | arg_split_branch="$1" |
5a356977 LS |
112 | shift |
113 | ;; | |
114 | -P) | |
e2b11e42 | 115 | arg_prefix="${1%/}" |
5a356977 LS |
116 | shift |
117 | ;; | |
118 | -m) | |
e2b11e42 | 119 | arg_addmerge_message="$1" |
5a356977 LS |
120 | shift |
121 | ;; | |
122 | --no-prefix) | |
e2b11e42 | 123 | arg_prefix= |
5a356977 LS |
124 | ;; |
125 | --onto) | |
e2b11e42 | 126 | arg_split_onto="$1" |
5a356977 LS |
127 | shift |
128 | ;; | |
129 | --no-onto) | |
e2b11e42 | 130 | arg_split_onto= |
5a356977 LS |
131 | ;; |
132 | --rejoin) | |
e2b11e42 | 133 | arg_split_rejoin=1 |
5a356977 LS |
134 | ;; |
135 | --no-rejoin) | |
e2b11e42 | 136 | arg_split_rejoin= |
5a356977 LS |
137 | ;; |
138 | --ignore-joins) | |
e2b11e42 | 139 | arg_split_ignore_joins=1 |
5a356977 LS |
140 | ;; |
141 | --no-ignore-joins) | |
e2b11e42 | 142 | arg_split_ignore_joins= |
5a356977 LS |
143 | ;; |
144 | --squash) | |
e2b11e42 | 145 | arg_addmerge_squash=1 |
5a356977 LS |
146 | ;; |
147 | --no-squash) | |
e2b11e42 | 148 | arg_addmerge_squash= |
5a356977 LS |
149 | ;; |
150 | --) | |
151 | break | |
152 | ;; | |
153 | *) | |
154 | die "Unexpected option: $opt" | |
155 | ;; | |
156 | esac | |
157 | done | |
158 | ||
e2b11e42 | 159 | arg_command="$1" |
5a356977 LS |
160 | shift |
161 | ||
e2b11e42 | 162 | case "$arg_command" in |
e4f8baa8 LS |
163 | add|merge|pull|split|push) |
164 | : | |
6ae6a233 | 165 | ;; |
5a356977 | 166 | *) |
e2b11e42 | 167 | die "Unknown command '$arg_command'" |
6ae6a233 | 168 | ;; |
5a356977 LS |
169 | esac |
170 | ||
e2b11e42 | 171 | if test -z "$arg_prefix" |
5a356977 LS |
172 | then |
173 | die "You must provide the --prefix option." | |
174 | fi | |
175 | ||
e2b11e42 | 176 | case "$arg_command" in |
5a356977 | 177 | add) |
e2b11e42 LS |
178 | test -e "$arg_prefix" && |
179 | die "prefix '$arg_prefix' already exists." | |
6ae6a233 DA |
180 | ;; |
181 | *) | |
e2b11e42 LS |
182 | test -e "$arg_prefix" || |
183 | die "'$arg_prefix' does not exist; use 'git subtree add'" | |
6ae6a233 | 184 | ;; |
0ca71b37 | 185 | esac |
5a356977 | 186 | |
e2b11e42 | 187 | dir="$(dirname "$arg_prefix/.")" |
5a356977 | 188 | |
e2b11e42 | 189 | debug "command: {$arg_command}" |
6d43585a | 190 | debug "quiet: {$GIT_QUIET}" |
5a356977 LS |
191 | debug "dir: {$dir}" |
192 | debug "opts: {$*}" | |
193 | debug | |
194 | ||
e2b11e42 | 195 | "cmd_$arg_command" "$@" |
5a356977 | 196 | } |
0ca71b37 | 197 | |
5cdae0f6 | 198 | # Usage: cache_setup |
d7fd792e | 199 | cache_setup () { |
5cdae0f6 | 200 | assert test $# = 0 |
2573354e | 201 | cachedir="$GIT_DIR/subtree-cache/$$" |
6ae6a233 DA |
202 | rm -rf "$cachedir" || |
203 | die "Can't delete old cachedir: $cachedir" | |
204 | mkdir -p "$cachedir" || | |
205 | die "Can't create new cachedir: $cachedir" | |
206 | mkdir -p "$cachedir/notree" || | |
207 | die "Can't create new cachedir: $cachedir/notree" | |
0ca71b37 | 208 | debug "Using cachedir: $cachedir" >&2 |
0ca71b37 AP |
209 | } |
210 | ||
5cdae0f6 | 211 | # Usage: cache_get [REVS...] |
d7fd792e | 212 | cache_get () { |
6ae6a233 DA |
213 | for oldrev in "$@" |
214 | do | |
215 | if test -r "$cachedir/$oldrev" | |
216 | then | |
0ca71b37 AP |
217 | read newrev <"$cachedir/$oldrev" |
218 | echo $newrev | |
219 | fi | |
220 | done | |
221 | } | |
222 | ||
5cdae0f6 | 223 | # Usage: cache_miss [REVS...] |
d7fd792e | 224 | cache_miss () { |
6ae6a233 DA |
225 | for oldrev in "$@" |
226 | do | |
227 | if ! test -r "$cachedir/$oldrev" | |
228 | then | |
915b9894 JG |
229 | echo $oldrev |
230 | fi | |
231 | done | |
232 | } | |
233 | ||
5cdae0f6 | 234 | # Usage: check_parents PARENTS_EXPR INDENT |
d7fd792e | 235 | check_parents () { |
5cdae0f6 | 236 | assert test $# = 2 |
d2f0f819 | 237 | missed=$(cache_miss "$1") || exit $? |
315a84f9 | 238 | local indent=$(($2 + 1)) |
6ae6a233 DA |
239 | for miss in $missed |
240 | do | |
241 | if ! test -r "$cachedir/notree/$miss" | |
242 | then | |
915b9894 | 243 | debug " incorrect order: $miss" |
315a84f9 | 244 | process_split_commit "$miss" "" "$indent" |
915b9894 JG |
245 | fi |
246 | done | |
247 | } | |
248 | ||
5cdae0f6 | 249 | # Usage: set_notree REV |
d7fd792e | 250 | set_notree () { |
5cdae0f6 | 251 | assert test $# = 1 |
915b9894 JG |
252 | echo "1" > "$cachedir/notree/$1" |
253 | } | |
254 | ||
5cdae0f6 | 255 | # Usage: cache_set OLDREV NEWREV |
d7fd792e | 256 | cache_set () { |
5cdae0f6 | 257 | assert test $# = 2 |
0ca71b37 AP |
258 | oldrev="$1" |
259 | newrev="$2" | |
6ae6a233 DA |
260 | if test "$oldrev" != "latest_old" && |
261 | test "$oldrev" != "latest_new" && | |
262 | test -e "$cachedir/$oldrev" | |
263 | then | |
0ca71b37 AP |
264 | die "cache for $oldrev already exists!" |
265 | fi | |
266 | echo "$newrev" >"$cachedir/$oldrev" | |
267 | } | |
268 | ||
5cdae0f6 | 269 | # Usage: rev_exists REV |
d7fd792e | 270 | rev_exists () { |
5cdae0f6 | 271 | assert test $# = 1 |
6ae6a233 DA |
272 | if git rev-parse "$1" >/dev/null 2>&1 |
273 | then | |
43a39512 AP |
274 | return 0 |
275 | else | |
276 | return 1 | |
277 | fi | |
278 | } | |
279 | ||
5cdae0f6 LS |
280 | # Usage: try_remove_previous REV |
281 | # | |
282 | # If a commit doesn't have a parent, this might not work. But we only want | |
b9de5353 AP |
283 | # to remove the parent from the rev-list, and since it doesn't exist, it won't |
284 | # be there anyway, so do nothing in that case. | |
d7fd792e | 285 | try_remove_previous () { |
5cdae0f6 | 286 | assert test $# = 1 |
6ae6a233 DA |
287 | if rev_exists "$1^" |
288 | then | |
b9de5353 AP |
289 | echo "^$1^" |
290 | fi | |
291 | } | |
292 | ||
5cdae0f6 | 293 | # Usage: find_latest_squash DIR |
d7fd792e | 294 | find_latest_squash () { |
5cdae0f6 | 295 | assert test $# = 1 |
d713e2d8 | 296 | debug "Looking for latest squash ($dir)..." |
1cc2cfff | 297 | dir="$1" |
d713e2d8 AP |
298 | sq= |
299 | main= | |
300 | sub= | |
6f2012cd | 301 | git log --grep="^git-subtree-dir: $dir/*\$" \ |
8841b522 | 302 | --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD | |
6ae6a233 DA |
303 | while read a b junk |
304 | do | |
d713e2d8 AP |
305 | debug "$a $b $junk" |
306 | debug "{{$sq/$main/$sub}}" | |
1cc2cfff | 307 | case "$a" in |
6ae6a233 DA |
308 | START) |
309 | sq="$b" | |
310 | ;; | |
311 | git-subtree-mainline:) | |
312 | main="$b" | |
313 | ;; | |
314 | git-subtree-split:) | |
bbffb023 | 315 | sub="$(git rev-parse "$b^{commit}")" || |
6ae6a233 DA |
316 | die "could not rev-parse split hash $b from commit $sq" |
317 | ;; | |
318 | END) | |
319 | if test -n "$sub" | |
320 | then | |
321 | if test -n "$main" | |
322 | then | |
323 | # a rejoin commit? | |
324 | # Pretend its sub was a squash. | |
325 | sq="$sub" | |
1cc2cfff | 326 | fi |
6ae6a233 DA |
327 | debug "Squash found: $sq $sub" |
328 | echo "$sq" "$sub" | |
329 | break | |
330 | fi | |
331 | sq= | |
332 | main= | |
333 | sub= | |
334 | ;; | |
1cc2cfff | 335 | esac |
d2f0f819 | 336 | done || exit $? |
1cc2cfff AP |
337 | } |
338 | ||
5cdae0f6 | 339 | # Usage: find_existing_splits DIR REV |
d7fd792e | 340 | find_existing_splits () { |
5cdae0f6 | 341 | assert test $# = 2 |
8b4a77f2 AP |
342 | debug "Looking for prior splits..." |
343 | dir="$1" | |
5cdae0f6 | 344 | rev="$2" |
d713e2d8 AP |
345 | main= |
346 | sub= | |
dd21d43b | 347 | local grep_format="^git-subtree-dir: $dir/*\$" |
e2b11e42 | 348 | if test -n "$arg_split_ignore_joins" |
dd21d43b SR |
349 | then |
350 | grep_format="^Add '$dir/' from commit '" | |
351 | fi | |
352 | git log --grep="$grep_format" \ | |
5cdae0f6 | 353 | --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' "$rev" | |
6ae6a233 DA |
354 | while read a b junk |
355 | do | |
8b4a77f2 | 356 | case "$a" in |
6ae6a233 DA |
357 | START) |
358 | sq="$b" | |
359 | ;; | |
360 | git-subtree-mainline:) | |
361 | main="$b" | |
362 | ;; | |
363 | git-subtree-split:) | |
bbffb023 | 364 | sub="$(git rev-parse "$b^{commit}")" || |
6ae6a233 DA |
365 | die "could not rev-parse split hash $b from commit $sq" |
366 | ;; | |
367 | END) | |
368 | debug " Main is: '$main'" | |
369 | if test -z "$main" -a -n "$sub" | |
370 | then | |
371 | # squash commits refer to a subtree | |
372 | debug " Squash: $sq from $sub" | |
373 | cache_set "$sq" "$sub" | |
374 | fi | |
375 | if test -n "$main" -a -n "$sub" | |
376 | then | |
377 | debug " Prior: $main -> $sub" | |
378 | cache_set $main $sub | |
379 | cache_set $sub $sub | |
380 | try_remove_previous "$main" | |
381 | try_remove_previous "$sub" | |
382 | fi | |
383 | main= | |
384 | sub= | |
385 | ;; | |
8b4a77f2 | 386 | esac |
d2f0f819 | 387 | done || exit $? |
8b4a77f2 AP |
388 | } |
389 | ||
5cdae0f6 | 390 | # Usage: copy_commit REV TREE FLAGS_STR |
d7fd792e | 391 | copy_commit () { |
5cdae0f6 | 392 | assert test $# = 3 |
f96bc790 | 393 | # We're going to set some environment vars here, so |
fd9500ee | 394 | # do it in a subshell to get rid of them safely later |
a64f3a72 | 395 | debug copy_commit "{$1}" "{$2}" "{$3}" |
8841b522 | 396 | git log -1 --no-show-signature --pretty=format:'%an%n%ae%n%aD%n%cn%n%ce%n%cD%n%B' "$1" | |
fd9500ee AP |
397 | ( |
398 | read GIT_AUTHOR_NAME | |
399 | read GIT_AUTHOR_EMAIL | |
400 | read GIT_AUTHOR_DATE | |
401 | read GIT_COMMITTER_NAME | |
402 | read GIT_COMMITTER_EMAIL | |
403 | read GIT_COMMITTER_DATE | |
b77172f8 AP |
404 | export GIT_AUTHOR_NAME \ |
405 | GIT_AUTHOR_EMAIL \ | |
406 | GIT_AUTHOR_DATE \ | |
407 | GIT_COMMITTER_NAME \ | |
408 | GIT_COMMITTER_EMAIL \ | |
409 | GIT_COMMITTER_DATE | |
6ae6a233 | 410 | ( |
e2b11e42 | 411 | printf "%s" "$arg_split_annotate" |
6ae6a233 DA |
412 | cat |
413 | ) | | |
fd9500ee AP |
414 | git commit-tree "$2" $3 # reads the rest of stdin |
415 | ) || die "Can't copy commit $1" | |
416 | } | |
417 | ||
5cdae0f6 | 418 | # Usage: add_msg DIR LATEST_OLD LATEST_NEW |
d7fd792e | 419 | add_msg () { |
5cdae0f6 | 420 | assert test $# = 3 |
eb7b590c AP |
421 | dir="$1" |
422 | latest_old="$2" | |
423 | latest_new="$3" | |
e2b11e42 | 424 | if test -n "$arg_addmerge_message" |
6ae6a233 | 425 | then |
e2b11e42 | 426 | commit_message="$arg_addmerge_message" |
2da0969a JS |
427 | else |
428 | commit_message="Add '$dir/' from commit '$latest_new'" | |
429 | fi | |
eb7b590c | 430 | cat <<-EOF |
2da0969a | 431 | $commit_message |
6ae6a233 | 432 | |
eb7b590c AP |
433 | git-subtree-dir: $dir |
434 | git-subtree-mainline: $latest_old | |
435 | git-subtree-split: $latest_new | |
436 | EOF | |
437 | } | |
438 | ||
5cdae0f6 | 439 | # Usage: add_squashed_msg REV DIR |
d7fd792e | 440 | add_squashed_msg () { |
5cdae0f6 | 441 | assert test $# = 2 |
e2b11e42 | 442 | if test -n "$arg_addmerge_message" |
6ae6a233 | 443 | then |
e2b11e42 | 444 | echo "$arg_addmerge_message" |
2da0969a JS |
445 | else |
446 | echo "Merge commit '$1' as '$2'" | |
447 | fi | |
448 | } | |
449 | ||
5cdae0f6 | 450 | # Usage: rejoin_msg DIR LATEST_OLD LATEST_NEW |
d7fd792e | 451 | rejoin_msg () { |
5cdae0f6 | 452 | assert test $# = 3 |
b77172f8 AP |
453 | dir="$1" |
454 | latest_old="$2" | |
455 | latest_new="$3" | |
e2b11e42 | 456 | if test -n "$arg_addmerge_message" |
6ae6a233 | 457 | then |
e2b11e42 | 458 | commit_message="$arg_addmerge_message" |
2da0969a JS |
459 | else |
460 | commit_message="Split '$dir/' into commit '$latest_new'" | |
461 | fi | |
b77172f8 | 462 | cat <<-EOF |
12629161 | 463 | $commit_message |
6ae6a233 | 464 | |
b77172f8 | 465 | git-subtree-dir: $dir |
8b4a77f2 AP |
466 | git-subtree-mainline: $latest_old |
467 | git-subtree-split: $latest_new | |
b77172f8 AP |
468 | EOF |
469 | } | |
470 | ||
5cdae0f6 | 471 | # Usage: squash_msg DIR OLD_SUBTREE_COMMIT NEW_SUBTREE_COMMIT |
d7fd792e | 472 | squash_msg () { |
5cdae0f6 | 473 | assert test $# = 3 |
1cc2cfff AP |
474 | dir="$1" |
475 | oldsub="$2" | |
476 | newsub="$3" | |
1cc2cfff | 477 | newsub_short=$(git rev-parse --short "$newsub") |
6ae6a233 DA |
478 | |
479 | if test -n "$oldsub" | |
480 | then | |
d713e2d8 AP |
481 | oldsub_short=$(git rev-parse --short "$oldsub") |
482 | echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short" | |
483 | echo | |
8841b522 SG |
484 | git log --no-show-signature --pretty=tformat:'%h %s' "$oldsub..$newsub" |
485 | git log --no-show-signature --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub" | |
d713e2d8 AP |
486 | else |
487 | echo "Squashed '$dir/' content from commit $newsub_short" | |
488 | fi | |
6ae6a233 | 489 | |
d713e2d8 AP |
490 | echo |
491 | echo "git-subtree-dir: $dir" | |
492 | echo "git-subtree-split: $newsub" | |
1cc2cfff AP |
493 | } |
494 | ||
5cdae0f6 | 495 | # Usage: toptree_for_commit COMMIT |
d7fd792e | 496 | toptree_for_commit () { |
5cdae0f6 | 497 | assert test $# = 1 |
210d0839 | 498 | commit="$1" |
8841b522 | 499 | git rev-parse --verify "$commit^{tree}" || exit $? |
210d0839 AP |
500 | } |
501 | ||
5cdae0f6 | 502 | # Usage: subtree_for_commit COMMIT DIR |
d7fd792e | 503 | subtree_for_commit () { |
5cdae0f6 | 504 | assert test $# = 2 |
210d0839 AP |
505 | commit="$1" |
506 | dir="$2" | |
507 | git ls-tree "$commit" -- "$dir" | | |
6ae6a233 DA |
508 | while read mode type tree name |
509 | do | |
510 | assert test "$name" = "$dir" | |
511 | assert test "$type" = "tree" -o "$type" = "commit" | |
512 | test "$type" = "commit" && continue # ignore submodules | |
768d6d10 AP |
513 | echo $tree |
514 | break | |
d2f0f819 | 515 | done || exit $? |
768d6d10 AP |
516 | } |
517 | ||
5cdae0f6 | 518 | # Usage: tree_changed TREE [PARENTS...] |
d7fd792e | 519 | tree_changed () { |
5cdae0f6 | 520 | assert test $# -gt 0 |
768d6d10 AP |
521 | tree=$1 |
522 | shift | |
6ae6a233 DA |
523 | if test $# -ne 1 |
524 | then | |
768d6d10 AP |
525 | return 0 # weird parents, consider it changed |
526 | else | |
d2f0f819 | 527 | ptree=$(toptree_for_commit $1) || exit $? |
6ae6a233 DA |
528 | if test "$ptree" != "$tree" |
529 | then | |
768d6d10 AP |
530 | return 0 # changed |
531 | else | |
532 | return 1 # not changed | |
533 | fi | |
534 | fi | |
535 | } | |
536 | ||
5cdae0f6 | 537 | # Usage: new_squash_commit OLD_SQUASHED_COMMIT OLD_NONSQUASHED_COMMIT NEW_NONSQUASHED_COMMIT |
d7fd792e | 538 | new_squash_commit () { |
5cdae0f6 | 539 | assert test $# = 3 |
1cc2cfff AP |
540 | old="$1" |
541 | oldsub="$2" | |
542 | newsub="$3" | |
543 | tree=$(toptree_for_commit $newsub) || exit $? | |
6ae6a233 DA |
544 | if test -n "$old" |
545 | then | |
546 | squash_msg "$dir" "$oldsub" "$newsub" | | |
547 | git commit-tree "$tree" -p "$old" || exit $? | |
d713e2d8 AP |
548 | else |
549 | squash_msg "$dir" "" "$newsub" | | |
6ae6a233 | 550 | git commit-tree "$tree" || exit $? |
d713e2d8 | 551 | fi |
1cc2cfff AP |
552 | } |
553 | ||
5cdae0f6 | 554 | # Usage: copy_or_skip REV TREE NEWPARENTS |
d7fd792e | 555 | copy_or_skip () { |
5cdae0f6 | 556 | assert test $# = 3 |
d6912658 AP |
557 | rev="$1" |
558 | tree="$2" | |
559 | newparents="$3" | |
6ae6a233 | 560 | assert test -n "$tree" |
d6912658 | 561 | |
96db2c04 | 562 | identical= |
49cf8228 | 563 | nonidentical= |
96db2c04 | 564 | p= |
a64f3a72 | 565 | gotparents= |
68f8ff81 | 566 | copycommit= |
6ae6a233 DA |
567 | for parent in $newparents |
568 | do | |
d6912658 | 569 | ptree=$(toptree_for_commit $parent) || exit $? |
6ae6a233 DA |
570 | test -z "$ptree" && continue |
571 | if test "$ptree" = "$tree" | |
572 | then | |
96db2c04 | 573 | # an identical parent could be used in place of this rev. |
68f8ff81 SR |
574 | if test -n "$identical" |
575 | then | |
576 | # if a previous identical parent was found, check whether | |
577 | # one is already an ancestor of the other | |
578 | mergebase=$(git merge-base $identical $parent) | |
579 | if test "$identical" = "$mergebase" | |
580 | then | |
581 | # current identical commit is an ancestor of parent | |
582 | identical="$parent" | |
583 | elif test "$parent" != "$mergebase" | |
584 | then | |
585 | # no common history; commit must be copied | |
586 | copycommit=1 | |
587 | fi | |
588 | else | |
589 | # first identical parent detected | |
590 | identical="$parent" | |
591 | fi | |
49cf8228 AP |
592 | else |
593 | nonidentical="$parent" | |
96db2c04 | 594 | fi |
6ae6a233 | 595 | |
a64f3a72 AP |
596 | # sometimes both old parents map to the same newparent; |
597 | # eliminate duplicates | |
598 | is_new=1 | |
6ae6a233 DA |
599 | for gp in $gotparents |
600 | do | |
601 | if test "$gp" = "$parent" | |
602 | then | |
a64f3a72 AP |
603 | is_new= |
604 | break | |
605 | fi | |
606 | done | |
6ae6a233 DA |
607 | if test -n "$is_new" |
608 | then | |
a64f3a72 | 609 | gotparents="$gotparents $parent" |
d6912658 AP |
610 | p="$p -p $parent" |
611 | fi | |
612 | done | |
933cfeb9 | 613 | |
6ae6a233 DA |
614 | if test -n "$identical" && test -n "$nonidentical" |
615 | then | |
933cfeb9 | 616 | extras=$(git rev-list --count $identical..$nonidentical) |
6ae6a233 DA |
617 | if test "$extras" -ne 0 |
618 | then | |
933cfeb9 DW |
619 | # we need to preserve history along the other branch |
620 | copycommit=1 | |
621 | fi | |
622 | fi | |
6ae6a233 DA |
623 | if test -n "$identical" && test -z "$copycommit" |
624 | then | |
96db2c04 AP |
625 | echo $identical |
626 | else | |
6ae6a233 | 627 | copy_commit "$rev" "$tree" "$p" || exit $? |
96db2c04 | 628 | fi |
d6912658 AP |
629 | } |
630 | ||
5cdae0f6 | 631 | # Usage: ensure_clean |
d7fd792e | 632 | ensure_clean () { |
5cdae0f6 | 633 | assert test $# = 0 |
6ae6a233 DA |
634 | if ! git diff-index HEAD --exit-code --quiet 2>&1 |
635 | then | |
eb7b590c AP |
636 | die "Working tree has modifications. Cannot add." |
637 | fi | |
6ae6a233 DA |
638 | if ! git diff-index --cached HEAD --exit-code --quiet 2>&1 |
639 | then | |
eb7b590c AP |
640 | die "Index has modifications. Cannot add." |
641 | fi | |
13648af5 AP |
642 | } |
643 | ||
5cdae0f6 | 644 | # Usage: ensure_valid_ref_format REF |
d7fd792e | 645 | ensure_valid_ref_format () { |
5cdae0f6 | 646 | assert test $# = 1 |
1c3e0f00 | 647 | git check-ref-format "refs/heads/$1" || |
6ae6a233 | 648 | die "'$1' does not look like a ref" |
1c3e0f00 AB |
649 | } |
650 | ||
5cdae0f6 | 651 | # Usage: process_split_commit REV PARENTS INDENT |
565e4b79 | 652 | process_split_commit () { |
5cdae0f6 | 653 | assert test $# = 3 |
565e4b79 SR |
654 | local rev="$1" |
655 | local parents="$2" | |
315a84f9 SR |
656 | local indent=$3 |
657 | ||
658 | if test $indent -eq 0 | |
659 | then | |
660 | revcount=$(($revcount + 1)) | |
661 | else | |
662 | # processing commit without normal parent information; | |
663 | # fetch from repo | |
19ad68d9 | 664 | parents=$(git rev-parse "$rev^@") |
315a84f9 SR |
665 | extracount=$(($extracount + 1)) |
666 | fi | |
667 | ||
668 | progress "$revcount/$revmax ($createcount) [$extracount]" | |
669 | ||
565e4b79 | 670 | debug "Processing commit: $rev" |
d2f0f819 | 671 | exists=$(cache_get "$rev") || exit $? |
565e4b79 SR |
672 | if test -n "$exists" |
673 | then | |
674 | debug " prior: $exists" | |
675 | return | |
676 | fi | |
677 | createcount=$(($createcount + 1)) | |
678 | debug " parents: $parents" | |
315a84f9 | 679 | check_parents "$parents" "$indent" |
d2f0f819 | 680 | newparents=$(cache_get $parents) || exit $? |
565e4b79 SR |
681 | debug " newparents: $newparents" |
682 | ||
d2f0f819 | 683 | tree=$(subtree_for_commit "$rev" "$dir") || exit $? |
565e4b79 SR |
684 | debug " tree is: $tree" |
685 | ||
565e4b79 SR |
686 | # ugly. is there no better way to tell if this is a subtree |
687 | # vs. a mainline commit? Does it matter? | |
688 | if test -z "$tree" | |
689 | then | |
690 | set_notree "$rev" | |
691 | if test -n "$newparents" | |
692 | then | |
693 | cache_set "$rev" "$rev" | |
694 | fi | |
695 | return | |
696 | fi | |
697 | ||
698 | newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $? | |
699 | debug " newrev is: $newrev" | |
700 | cache_set "$rev" "$newrev" | |
701 | cache_set latest_new "$newrev" | |
702 | cache_set latest_old "$rev" | |
703 | } | |
704 | ||
5cdae0f6 LS |
705 | # Usage: cmd_add REV |
706 | # Or: cmd_add REPOSITORY REF | |
d7fd792e | 707 | cmd_add () { |
c00d1d11 | 708 | |
13648af5 | 709 | ensure_clean |
6ae6a233 DA |
710 | |
711 | if test $# -eq 1 | |
712 | then | |
713 | git rev-parse -q --verify "$1^{commit}" >/dev/null || | |
714 | die "'$1' does not refer to a commit" | |
715 | ||
716 | cmd_add_commit "$@" | |
717 | ||
718 | elif test $# -eq 2 | |
719 | then | |
720 | # Technically we could accept a refspec here but we're | |
721 | # just going to turn around and add FETCH_HEAD under the | |
722 | # specified directory. Allowing a refspec might be | |
723 | # misleading because we won't do anything with any other | |
724 | # branches fetched via the refspec. | |
725 | ensure_valid_ref_format "$2" | |
726 | ||
727 | cmd_add_repository "$@" | |
c00d1d11 | 728 | else |
a94f9110 | 729 | say >&2 "error: parameters were '$*'" |
6ae6a233 | 730 | die "Provide either a commit or a repository and commit." |
eb7b590c | 731 | fi |
c00d1d11 WW |
732 | } |
733 | ||
5cdae0f6 | 734 | # Usage: cmd_add_repository REPOSITORY REFSPEC |
d7fd792e | 735 | cmd_add_repository () { |
5cdae0f6 | 736 | assert test $# = 2 |
c00d1d11 WW |
737 | echo "git fetch" "$@" |
738 | repository=$1 | |
739 | refspec=$2 | |
740 | git fetch "$@" || exit $? | |
e4f8baa8 | 741 | cmd_add_commit FETCH_HEAD |
c00d1d11 WW |
742 | } |
743 | ||
5cdae0f6 | 744 | # Usage: cmd_add_commit REV |
d7fd792e | 745 | cmd_add_commit () { |
e4f8baa8 LS |
746 | # The rev has already been validated by cmd_add(), we just |
747 | # need to normalize it. | |
5cdae0f6 | 748 | assert test $# = 1 |
e4f8baa8 | 749 | rev=$(git rev-parse --verify "$1^{commit}") || exit $? |
6ae6a233 | 750 | |
eb7b590c AP |
751 | debug "Adding $dir as '$rev'..." |
752 | git read-tree --prefix="$dir" $rev || exit $? | |
227f7811 | 753 | git checkout -- "$dir" || exit $? |
eb7b590c | 754 | tree=$(git write-tree) || exit $? |
6ae6a233 | 755 | |
eb7b590c | 756 | headrev=$(git rev-parse HEAD) || exit $? |
6ae6a233 DA |
757 | if test -n "$headrev" && test "$headrev" != "$rev" |
758 | then | |
eb7b590c AP |
759 | headp="-p $headrev" |
760 | else | |
761 | headp= | |
762 | fi | |
6ae6a233 | 763 | |
e2b11e42 | 764 | if test -n "$arg_addmerge_squash" |
6ae6a233 | 765 | then |
d713e2d8 | 766 | rev=$(new_squash_commit "" "" "$rev") || exit $? |
2da0969a | 767 | commit=$(add_squashed_msg "$rev" "$dir" | |
6ae6a233 | 768 | git commit-tree "$tree" $headp -p "$rev") || exit $? |
d713e2d8 | 769 | else |
d2f0f819 | 770 | revp=$(peel_committish "$rev") || exit $? |
6ae6a233 DA |
771 | commit=$(add_msg "$dir" $headrev "$rev" | |
772 | git commit-tree "$tree" $headp -p "$revp") || exit $? | |
d713e2d8 | 773 | fi |
eb7b590c | 774 | git reset "$commit" || exit $? |
6ae6a233 | 775 | |
6d43585a | 776 | say >&2 "Added dir '$dir'" |
eb7b590c AP |
777 | } |
778 | ||
5cdae0f6 | 779 | # Usage: cmd_split [REV] |
d7fd792e | 780 | cmd_split () { |
e4f8baa8 LS |
781 | if test $# -eq 0 |
782 | then | |
783 | rev=$(git rev-parse HEAD) | |
784 | elif test $# -eq 1 | |
785 | then | |
786 | rev=$(git rev-parse -q --verify "$1^{commit}") || | |
787 | die "'$1' does not refer to a commit" | |
788 | else | |
789 | die "You must provide exactly one revision. Got: '$*'" | |
790 | fi | |
791 | ||
0ca71b37 AP |
792 | debug "Splitting $dir..." |
793 | cache_setup || exit $? | |
6ae6a233 | 794 | |
e2b11e42 | 795 | if test -n "$arg_split_onto" |
6ae6a233 | 796 | then |
e2b11e42 LS |
797 | debug "Reading history for --onto=$arg_split_onto..." |
798 | git rev-list $arg_split_onto | | |
6ae6a233 DA |
799 | while read rev |
800 | do | |
33ff583a AP |
801 | # the 'onto' history is already just the subdir, so |
802 | # any parent we find there can be used verbatim | |
2c71b7c4 | 803 | debug " cache: $rev" |
6ae6a233 | 804 | cache_set "$rev" "$rev" |
d2f0f819 | 805 | done || exit $? |
33ff583a | 806 | fi |
6ae6a233 | 807 | |
e4f8baa8 | 808 | unrevs="$(find_existing_splits "$dir" "$rev")" || exit $? |
6ae6a233 | 809 | |
1f73862f AP |
810 | # We can't restrict rev-list to only $dir here, because some of our |
811 | # parents have the $dir contents the root, and those won't match. | |
812 | # (and rev-list --follow doesn't seem to solve this) | |
e4f8baa8 | 813 | grl='git rev-list --topo-order --reverse --parents $rev $unrevs' |
942dce55 AP |
814 | revmax=$(eval "$grl" | wc -l) |
815 | revcount=0 | |
816 | createcount=0 | |
315a84f9 | 817 | extracount=0 |
942dce55 | 818 | eval "$grl" | |
6ae6a233 DA |
819 | while read rev parents |
820 | do | |
315a84f9 | 821 | process_split_commit "$rev" "$parents" 0 |
2573354e | 822 | done || exit $? |
6ae6a233 | 823 | |
d2f0f819 | 824 | latest_new=$(cache_get latest_new) || exit $? |
6ae6a233 DA |
825 | if test -z "$latest_new" |
826 | then | |
e25a6bf8 AP |
827 | die "No new revisions were found" |
828 | fi | |
6ae6a233 | 829 | |
e2b11e42 | 830 | if test -n "$arg_split_rejoin" |
6ae6a233 | 831 | then |
b77172f8 | 832 | debug "Merging split branch into HEAD..." |
d2f0f819 | 833 | latest_old=$(cache_get latest_old) || exit $? |
b77172f8 | 834 | git merge -s ours \ |
0f12c7d4 | 835 | --allow-unrelated-histories \ |
6ae6a233 DA |
836 | -m "$(rejoin_msg "$dir" "$latest_old" "$latest_new")" \ |
837 | "$latest_new" >&2 || exit $? | |
838 | fi | |
e2b11e42 | 839 | if test -n "$arg_split_branch" |
6ae6a233 | 840 | then |
e2b11e42 | 841 | if rev_exists "refs/heads/$arg_split_branch" |
6ae6a233 | 842 | then |
e2b11e42 | 843 | if ! git merge-base --is-ancestor "$arg_split_branch" "$latest_new" |
6ae6a233 | 844 | then |
e2b11e42 | 845 | die "Branch '$arg_split_branch' is not an ancestor of commit '$latest_new'." |
0a562948 JS |
846 | fi |
847 | action='Updated' | |
848 | else | |
849 | action='Created' | |
850 | fi | |
6ae6a233 | 851 | git update-ref -m 'subtree split' \ |
e2b11e42 LS |
852 | "refs/heads/$arg_split_branch" "$latest_new" || exit $? |
853 | say >&2 "$action branch '$arg_split_branch'" | |
43a39512 | 854 | fi |
6ae6a233 | 855 | echo "$latest_new" |
0ca71b37 AP |
856 | exit 0 |
857 | } | |
858 | ||
5cdae0f6 | 859 | # Usage: cmd_merge REV |
d7fd792e | 860 | cmd_merge () { |
e4f8baa8 LS |
861 | test $# -eq 1 || |
862 | die "You must provide exactly one revision. Got: '$*'" | |
863 | rev=$(git rev-parse -q --verify "$1^{commit}") || | |
864 | die "'$1' does not refer to a commit" | |
13648af5 | 865 | ensure_clean |
6ae6a233 | 866 | |
e2b11e42 | 867 | if test -n "$arg_addmerge_squash" |
6ae6a233 | 868 | then |
d2f0f819 | 869 | first_split="$(find_latest_squash "$dir")" || exit $? |
6ae6a233 DA |
870 | if test -z "$first_split" |
871 | then | |
1cc2cfff AP |
872 | die "Can't squash-merge: '$dir' was never added." |
873 | fi | |
874 | set $first_split | |
875 | old=$1 | |
876 | sub=$2 | |
6ae6a233 DA |
877 | if test "$sub" = "$rev" |
878 | then | |
6d43585a | 879 | say >&2 "Subtree is already at commit $rev." |
eb4fb910 AP |
880 | exit 0 |
881 | fi | |
1cc2cfff AP |
882 | new=$(new_squash_commit "$old" "$sub" "$rev") || exit $? |
883 | debug "New squash commit: $new" | |
884 | rev="$new" | |
885 | fi | |
448e71e2 | 886 | |
e2b11e42 | 887 | if test -n "$arg_addmerge_message" |
6ae6a233 | 888 | then |
e2b11e42 LS |
889 | git merge -Xsubtree="$arg_prefix" \ |
890 | --message="$arg_addmerge_message" "$rev" | |
349a70d5 | 891 | else |
e2b11e42 | 892 | git merge -Xsubtree="$arg_prefix" $rev |
349a70d5 | 893 | fi |
13648af5 AP |
894 | } |
895 | ||
5cdae0f6 | 896 | # Usage: cmd_pull REPOSITORY REMOTEREF |
d7fd792e | 897 | cmd_pull () { |
6ae6a233 DA |
898 | if test $# -ne 2 |
899 | then | |
900 | die "You must provide <repository> <ref>" | |
1c3e0f00 | 901 | fi |
13648af5 | 902 | ensure_clean |
1c3e0f00 | 903 | ensure_valid_ref_format "$2" |
e31d1e2f | 904 | git fetch "$@" || exit $? |
e4f8baa8 | 905 | cmd_merge FETCH_HEAD |
c00d1d11 WW |
906 | } |
907 | ||
5cdae0f6 | 908 | # Usage: cmd_push REPOSITORY REMOTEREF |
d7fd792e | 909 | cmd_push () { |
6ae6a233 DA |
910 | if test $# -ne 2 |
911 | then | |
912 | die "You must provide <repository> <ref>" | |
c00d1d11 | 913 | fi |
1c3e0f00 | 914 | ensure_valid_ref_format "$2" |
6ae6a233 DA |
915 | if test -e "$dir" |
916 | then | |
917 | repository=$1 | |
918 | refspec=$2 | |
919 | echo "git push using: " "$repository" "$refspec" | |
e2b11e42 | 920 | localrev=$(git subtree split --prefix="$arg_prefix") || die |
6ae6a233 | 921 | git push "$repository" "$localrev":"refs/heads/$refspec" |
c00d1d11 | 922 | else |
6ae6a233 | 923 | die "'$dir' must already exist. Try 'git subtree add'." |
c00d1d11 | 924 | fi |
0ca71b37 AP |
925 | } |
926 | ||
5a356977 | 927 | main "$@" |