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