]> git.ipfire.org Git - thirdparty/git.git/blame - contrib/subtree/git-subtree.sh
Merge branch 'rs/parse-options-with-keep-unknown-abbrev-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 () {
88983946
PS
376 assert test $# -ge 2
377 assert test $# -le 3
f10d31cf
PB
378 b="$1"
379 sq="$2"
0d330673
PB
380 repository=""
381 if test "$#" = 3
382 then
383 repository="$3"
384 fi
385 fail_msg="fatal: could not rev-parse split hash $b from commit $sq"
386 if ! sub="$(git rev-parse --verify --quiet "$b^{commit}")"
387 then
388 # if 'repository' was given, try to fetch the 'git-subtree-split' hash
389 # before 'rev-parse'-ing it again, as it might be a tag that we do not have locally
390 if test -n "${repository}"
391 then
392 git fetch "$repository" "$b"
393 sub="$(git rev-parse --verify --quiet "$b^{commit}")" ||
394 die "$fail_msg"
395 else
396 hint1=$(printf "hint: hash might be a tag, try fetching it from the subtree repository:")
397 hint2=$(printf "hint: git fetch <subtree-repository> $b")
398 fail_msg=$(printf "$fail_msg\n$hint1\n$hint2")
399 die "$fail_msg"
400 fi
401 fi
f10d31cf
PB
402}
403
0d330673 404# Usage: find_latest_squash DIR [REPOSITORY]
d7fd792e 405find_latest_squash () {
88983946
PS
406 assert test $# -ge 1
407 assert test $# -le 2
34ab458c 408 dir="$1"
0d330673
PB
409 repository=""
410 if test "$#" = 2
411 then
412 repository="$2"
413 fi
414 debug "Looking for latest squash (dir=$dir, repository=$repository)..."
e9525a8a
LS
415 local indent=$(($indent + 1))
416
d713e2d8
AP
417 sq=
418 main=
419 sub=
6f2012cd 420 git log --grep="^git-subtree-dir: $dir/*\$" \
8841b522 421 --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD |
6ae6a233
DA
422 while read a b junk
423 do
d713e2d8
AP
424 debug "$a $b $junk"
425 debug "{{$sq/$main/$sub}}"
1cc2cfff 426 case "$a" in
6ae6a233
DA
427 START)
428 sq="$b"
429 ;;
430 git-subtree-mainline:)
431 main="$b"
432 ;;
433 git-subtree-split:)
0d330673 434 process_subtree_split_trailer "$b" "$sq" "$repository"
6ae6a233
DA
435 ;;
436 END)
437 if test -n "$sub"
438 then
439 if test -n "$main"
440 then
441 # a rejoin commit?
442 # Pretend its sub was a squash.
cb655144
LS
443 sq=$(git rev-parse --verify "$sq^2") ||
444 die
1cc2cfff 445 fi
6ae6a233
DA
446 debug "Squash found: $sq $sub"
447 echo "$sq" "$sub"
448 break
449 fi
450 sq=
451 main=
452 sub=
453 ;;
1cc2cfff 454 esac
d2f0f819 455 done || exit $?
1cc2cfff
AP
456}
457
1762382a 458# Usage: find_existing_splits DIR REV [REPOSITORY]
d7fd792e 459find_existing_splits () {
88983946
PS
460 assert test $# -ge 2
461 assert test $# -le 3
8b4a77f2 462 debug "Looking for prior splits..."
e9525a8a
LS
463 local indent=$(($indent + 1))
464
8b4a77f2 465 dir="$1"
5cdae0f6 466 rev="$2"
1762382a
PB
467 repository=""
468 if test "$#" = 3
469 then
470 repository="$3"
471 fi
d713e2d8
AP
472 main=
473 sub=
dd21d43b 474 local grep_format="^git-subtree-dir: $dir/*\$"
e2b11e42 475 if test -n "$arg_split_ignore_joins"
dd21d43b
SR
476 then
477 grep_format="^Add '$dir/' from commit '"
478 fi
479 git log --grep="$grep_format" \
5cdae0f6 480 --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' "$rev" |
6ae6a233
DA
481 while read a b junk
482 do
8b4a77f2 483 case "$a" in
6ae6a233
DA
484 START)
485 sq="$b"
486 ;;
487 git-subtree-mainline:)
488 main="$b"
489 ;;
490 git-subtree-split:)
1762382a 491 process_subtree_split_trailer "$b" "$sq" "$repository"
6ae6a233
DA
492 ;;
493 END)
e9525a8a 494 debug "Main is: '$main'"
13420028 495 if test -z "$main" && test -n "$sub"
6ae6a233
DA
496 then
497 # squash commits refer to a subtree
498 debug " Squash: $sq from $sub"
499 cache_set "$sq" "$sub"
500 fi
13420028 501 if test -n "$main" && test -n "$sub"
6ae6a233
DA
502 then
503 debug " Prior: $main -> $sub"
504 cache_set $main $sub
505 cache_set $sub $sub
506 try_remove_previous "$main"
507 try_remove_previous "$sub"
508 fi
509 main=
510 sub=
511 ;;
8b4a77f2 512 esac
d2f0f819 513 done || exit $?
8b4a77f2
AP
514}
515
5cdae0f6 516# Usage: copy_commit REV TREE FLAGS_STR
d7fd792e 517copy_commit () {
5cdae0f6 518 assert test $# = 3
f96bc790 519 # We're going to set some environment vars here, so
fd9500ee 520 # do it in a subshell to get rid of them safely later
a64f3a72 521 debug copy_commit "{$1}" "{$2}" "{$3}"
8841b522 522 git log -1 --no-show-signature --pretty=format:'%an%n%ae%n%aD%n%cn%n%ce%n%cD%n%B' "$1" |
fd9500ee
AP
523 (
524 read GIT_AUTHOR_NAME
525 read GIT_AUTHOR_EMAIL
526 read GIT_AUTHOR_DATE
527 read GIT_COMMITTER_NAME
528 read GIT_COMMITTER_EMAIL
529 read GIT_COMMITTER_DATE
b77172f8
AP
530 export GIT_AUTHOR_NAME \
531 GIT_AUTHOR_EMAIL \
532 GIT_AUTHOR_DATE \
533 GIT_COMMITTER_NAME \
534 GIT_COMMITTER_EMAIL \
535 GIT_COMMITTER_DATE
6ae6a233 536 (
e2b11e42 537 printf "%s" "$arg_split_annotate"
6ae6a233
DA
538 cat
539 ) |
fd9500ee 540 git commit-tree "$2" $3 # reads the rest of stdin
5626a9e2 541 ) || die "fatal: can't copy commit $1"
fd9500ee
AP
542}
543
5cdae0f6 544# Usage: add_msg DIR LATEST_OLD LATEST_NEW
d7fd792e 545add_msg () {
5cdae0f6 546 assert test $# = 3
eb7b590c
AP
547 dir="$1"
548 latest_old="$2"
549 latest_new="$3"
e2b11e42 550 if test -n "$arg_addmerge_message"
6ae6a233 551 then
e2b11e42 552 commit_message="$arg_addmerge_message"
2da0969a
JS
553 else
554 commit_message="Add '$dir/' from commit '$latest_new'"
555 fi
cb655144
LS
556 if test -n "$arg_split_rejoin"
557 then
558 # If this is from a --rejoin, then rejoin_msg has
559 # already inserted the `git-subtree-xxx:` tags
560 echo "$commit_message"
561 return
562 fi
eb7b590c 563 cat <<-EOF
2da0969a 564 $commit_message
6ae6a233 565
eb7b590c
AP
566 git-subtree-dir: $dir
567 git-subtree-mainline: $latest_old
568 git-subtree-split: $latest_new
569 EOF
570}
571
5cdae0f6 572# Usage: add_squashed_msg REV DIR
d7fd792e 573add_squashed_msg () {
5cdae0f6 574 assert test $# = 2
e2b11e42 575 if test -n "$arg_addmerge_message"
6ae6a233 576 then
e2b11e42 577 echo "$arg_addmerge_message"
2da0969a
JS
578 else
579 echo "Merge commit '$1' as '$2'"
580 fi
581}
582
5cdae0f6 583# Usage: rejoin_msg DIR LATEST_OLD LATEST_NEW
d7fd792e 584rejoin_msg () {
5cdae0f6 585 assert test $# = 3
b77172f8
AP
586 dir="$1"
587 latest_old="$2"
588 latest_new="$3"
e2b11e42 589 if test -n "$arg_addmerge_message"
6ae6a233 590 then
e2b11e42 591 commit_message="$arg_addmerge_message"
2da0969a
JS
592 else
593 commit_message="Split '$dir/' into commit '$latest_new'"
594 fi
b77172f8 595 cat <<-EOF
12629161 596 $commit_message
6ae6a233 597
b77172f8 598 git-subtree-dir: $dir
8b4a77f2
AP
599 git-subtree-mainline: $latest_old
600 git-subtree-split: $latest_new
b77172f8
AP
601 EOF
602}
603
5cdae0f6 604# Usage: squash_msg DIR OLD_SUBTREE_COMMIT NEW_SUBTREE_COMMIT
d7fd792e 605squash_msg () {
5cdae0f6 606 assert test $# = 3
1cc2cfff
AP
607 dir="$1"
608 oldsub="$2"
609 newsub="$3"
1cc2cfff 610 newsub_short=$(git rev-parse --short "$newsub")
6ae6a233
DA
611
612 if test -n "$oldsub"
613 then
d713e2d8
AP
614 oldsub_short=$(git rev-parse --short "$oldsub")
615 echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short"
616 echo
8841b522
SG
617 git log --no-show-signature --pretty=tformat:'%h %s' "$oldsub..$newsub"
618 git log --no-show-signature --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub"
d713e2d8
AP
619 else
620 echo "Squashed '$dir/' content from commit $newsub_short"
621 fi
6ae6a233 622
d713e2d8
AP
623 echo
624 echo "git-subtree-dir: $dir"
625 echo "git-subtree-split: $newsub"
1cc2cfff
AP
626}
627
5cdae0f6 628# Usage: toptree_for_commit COMMIT
d7fd792e 629toptree_for_commit () {
5cdae0f6 630 assert test $# = 1
210d0839 631 commit="$1"
8841b522 632 git rev-parse --verify "$commit^{tree}" || exit $?
210d0839
AP
633}
634
5cdae0f6 635# Usage: subtree_for_commit COMMIT DIR
d7fd792e 636subtree_for_commit () {
5cdae0f6 637 assert test $# = 2
210d0839
AP
638 commit="$1"
639 dir="$2"
640 git ls-tree "$commit" -- "$dir" |
6ae6a233
DA
641 while read mode type tree name
642 do
643 assert test "$name" = "$dir"
47c39c28
PS
644
645 case "$type" in
646 commit)
647 continue;; # ignore submodules
648 tree)
649 echo $tree
650 break;;
651 *)
652 die "fatal: tree entry is of type ${type}, expected tree or commit";;
653 esac
d2f0f819 654 done || exit $?
768d6d10
AP
655}
656
5cdae0f6 657# Usage: tree_changed TREE [PARENTS...]
d7fd792e 658tree_changed () {
5cdae0f6 659 assert test $# -gt 0
768d6d10
AP
660 tree=$1
661 shift
6ae6a233
DA
662 if test $# -ne 1
663 then
768d6d10
AP
664 return 0 # weird parents, consider it changed
665 else
d2f0f819 666 ptree=$(toptree_for_commit $1) || exit $?
6ae6a233
DA
667 if test "$ptree" != "$tree"
668 then
768d6d10
AP
669 return 0 # changed
670 else
671 return 1 # not changed
672 fi
673 fi
674}
675
5cdae0f6 676# Usage: new_squash_commit OLD_SQUASHED_COMMIT OLD_NONSQUASHED_COMMIT NEW_NONSQUASHED_COMMIT
d7fd792e 677new_squash_commit () {
5cdae0f6 678 assert test $# = 3
1cc2cfff
AP
679 old="$1"
680 oldsub="$2"
681 newsub="$3"
682 tree=$(toptree_for_commit $newsub) || exit $?
6ae6a233
DA
683 if test -n "$old"
684 then
685 squash_msg "$dir" "$oldsub" "$newsub" |
686 git commit-tree "$tree" -p "$old" || exit $?
d713e2d8
AP
687 else
688 squash_msg "$dir" "" "$newsub" |
6ae6a233 689 git commit-tree "$tree" || exit $?
d713e2d8 690 fi
1cc2cfff
AP
691}
692
5cdae0f6 693# Usage: copy_or_skip REV TREE NEWPARENTS
d7fd792e 694copy_or_skip () {
5cdae0f6 695 assert test $# = 3
d6912658
AP
696 rev="$1"
697 tree="$2"
698 newparents="$3"
6ae6a233 699 assert test -n "$tree"
d6912658 700
96db2c04 701 identical=
49cf8228 702 nonidentical=
96db2c04 703 p=
a64f3a72 704 gotparents=
68f8ff81 705 copycommit=
6ae6a233
DA
706 for parent in $newparents
707 do
d6912658 708 ptree=$(toptree_for_commit $parent) || exit $?
6ae6a233
DA
709 test -z "$ptree" && continue
710 if test "$ptree" = "$tree"
711 then
96db2c04 712 # an identical parent could be used in place of this rev.
68f8ff81
SR
713 if test -n "$identical"
714 then
715 # if a previous identical parent was found, check whether
716 # one is already an ancestor of the other
717 mergebase=$(git merge-base $identical $parent)
718 if test "$identical" = "$mergebase"
719 then
720 # current identical commit is an ancestor of parent
721 identical="$parent"
722 elif test "$parent" != "$mergebase"
723 then
724 # no common history; commit must be copied
725 copycommit=1
726 fi
727 else
728 # first identical parent detected
729 identical="$parent"
730 fi
49cf8228
AP
731 else
732 nonidentical="$parent"
96db2c04 733 fi
6ae6a233 734
a64f3a72
AP
735 # sometimes both old parents map to the same newparent;
736 # eliminate duplicates
737 is_new=1
6ae6a233
DA
738 for gp in $gotparents
739 do
740 if test "$gp" = "$parent"
741 then
a64f3a72
AP
742 is_new=
743 break
744 fi
745 done
6ae6a233
DA
746 if test -n "$is_new"
747 then
a64f3a72 748 gotparents="$gotparents $parent"
d6912658
AP
749 p="$p -p $parent"
750 fi
751 done
933cfeb9 752
6ae6a233
DA
753 if test -n "$identical" && test -n "$nonidentical"
754 then
933cfeb9 755 extras=$(git rev-list --count $identical..$nonidentical)
6ae6a233
DA
756 if test "$extras" -ne 0
757 then
933cfeb9
DW
758 # we need to preserve history along the other branch
759 copycommit=1
760 fi
761 fi
6ae6a233
DA
762 if test -n "$identical" && test -z "$copycommit"
763 then
96db2c04
AP
764 echo $identical
765 else
6ae6a233 766 copy_commit "$rev" "$tree" "$p" || exit $?
96db2c04 767 fi
d6912658
AP
768}
769
5cdae0f6 770# Usage: ensure_clean
d7fd792e 771ensure_clean () {
5cdae0f6 772 assert test $# = 0
6ae6a233
DA
773 if ! git diff-index HEAD --exit-code --quiet 2>&1
774 then
5626a9e2 775 die "fatal: working tree has modifications. Cannot add."
eb7b590c 776 fi
6ae6a233
DA
777 if ! git diff-index --cached HEAD --exit-code --quiet 2>&1
778 then
5626a9e2 779 die "fatal: index has modifications. Cannot add."
eb7b590c 780 fi
13648af5
AP
781}
782
5cdae0f6 783# Usage: ensure_valid_ref_format REF
d7fd792e 784ensure_valid_ref_format () {
5cdae0f6 785 assert test $# = 1
1c3e0f00 786 git check-ref-format "refs/heads/$1" ||
5626a9e2 787 die "fatal: '$1' does not look like a ref"
1c3e0f00
AB
788}
789
e9525a8a 790# Usage: process_split_commit REV PARENTS
565e4b79 791process_split_commit () {
e9525a8a 792 assert test $# = 2
565e4b79
SR
793 local rev="$1"
794 local parents="$2"
315a84f9
SR
795
796 if test $indent -eq 0
797 then
798 revcount=$(($revcount + 1))
799 else
800 # processing commit without normal parent information;
801 # fetch from repo
19ad68d9 802 parents=$(git rev-parse "$rev^@")
315a84f9
SR
803 extracount=$(($extracount + 1))
804 fi
805
806 progress "$revcount/$revmax ($createcount) [$extracount]"
807
565e4b79 808 debug "Processing commit: $rev"
e9525a8a 809 local indent=$(($indent + 1))
d2f0f819 810 exists=$(cache_get "$rev") || exit $?
565e4b79
SR
811 if test -n "$exists"
812 then
e9525a8a 813 debug "prior: $exists"
565e4b79
SR
814 return
815 fi
816 createcount=$(($createcount + 1))
e9525a8a 817 debug "parents: $parents"
3ce8888f 818 check_parents $parents
d2f0f819 819 newparents=$(cache_get $parents) || exit $?
e9525a8a 820 debug "newparents: $newparents"
565e4b79 821
d2f0f819 822 tree=$(subtree_for_commit "$rev" "$dir") || exit $?
e9525a8a 823 debug "tree is: $tree"
565e4b79 824
565e4b79
SR
825 # ugly. is there no better way to tell if this is a subtree
826 # vs. a mainline commit? Does it matter?
827 if test -z "$tree"
828 then
829 set_notree "$rev"
830 if test -n "$newparents"
831 then
832 cache_set "$rev" "$rev"
833 fi
834 return
835 fi
836
837 newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
e9525a8a 838 debug "newrev is: $newrev"
565e4b79
SR
839 cache_set "$rev" "$newrev"
840 cache_set latest_new "$newrev"
841 cache_set latest_old "$rev"
842}
843
5cdae0f6
LS
844# Usage: cmd_add REV
845# Or: cmd_add REPOSITORY REF
d7fd792e 846cmd_add () {
c00d1d11 847
13648af5 848 ensure_clean
6ae6a233
DA
849
850 if test $# -eq 1
851 then
852 git rev-parse -q --verify "$1^{commit}" >/dev/null ||
5626a9e2 853 die "fatal: '$1' does not refer to a commit"
6ae6a233
DA
854
855 cmd_add_commit "$@"
856
857 elif test $# -eq 2
858 then
859 # Technically we could accept a refspec here but we're
860 # just going to turn around and add FETCH_HEAD under the
861 # specified directory. Allowing a refspec might be
862 # misleading because we won't do anything with any other
863 # branches fetched via the refspec.
864 ensure_valid_ref_format "$2"
865
866 cmd_add_repository "$@"
c00d1d11 867 else
5626a9e2 868 say >&2 "fatal: parameters were '$*'"
6ae6a233 869 die "Provide either a commit or a repository and commit."
eb7b590c 870 fi
c00d1d11
WW
871}
872
5cdae0f6 873# Usage: cmd_add_repository REPOSITORY REFSPEC
d7fd792e 874cmd_add_repository () {
5cdae0f6 875 assert test $# = 2
c00d1d11
WW
876 echo "git fetch" "$@"
877 repository=$1
878 refspec=$2
879 git fetch "$@" || exit $?
e4f8baa8 880 cmd_add_commit FETCH_HEAD
c00d1d11
WW
881}
882
5cdae0f6 883# Usage: cmd_add_commit REV
d7fd792e 884cmd_add_commit () {
e4f8baa8
LS
885 # The rev has already been validated by cmd_add(), we just
886 # need to normalize it.
5cdae0f6 887 assert test $# = 1
e4f8baa8 888 rev=$(git rev-parse --verify "$1^{commit}") || exit $?
6ae6a233 889
eb7b590c 890 debug "Adding $dir as '$rev'..."
cb655144
LS
891 if test -z "$arg_split_rejoin"
892 then
893 # Only bother doing this if this is a genuine 'add',
894 # not a synthetic 'add' from '--rejoin'.
895 git read-tree --prefix="$dir" $rev || exit $?
896 fi
227f7811 897 git checkout -- "$dir" || exit $?
eb7b590c 898 tree=$(git write-tree) || exit $?
6ae6a233 899
a50fcc13 900 headrev=$(git rev-parse --verify HEAD) || exit $?
6ae6a233
DA
901 if test -n "$headrev" && test "$headrev" != "$rev"
902 then
eb7b590c
AP
903 headp="-p $headrev"
904 else
905 headp=
906 fi
6ae6a233 907
e2b11e42 908 if test -n "$arg_addmerge_squash"
6ae6a233 909 then
d713e2d8 910 rev=$(new_squash_commit "" "" "$rev") || exit $?
2da0969a 911 commit=$(add_squashed_msg "$rev" "$dir" |
6ae6a233 912 git commit-tree "$tree" $headp -p "$rev") || exit $?
d713e2d8 913 else
d2f0f819 914 revp=$(peel_committish "$rev") || exit $?
6ae6a233
DA
915 commit=$(add_msg "$dir" $headrev "$rev" |
916 git commit-tree "$tree" $headp -p "$revp") || exit $?
d713e2d8 917 fi
eb7b590c 918 git reset "$commit" || exit $?
6ae6a233 919
6d43585a 920 say >&2 "Added dir '$dir'"
eb7b590c
AP
921}
922
1762382a 923# Usage: cmd_split [REV] [REPOSITORY]
d7fd792e 924cmd_split () {
e4f8baa8
LS
925 if test $# -eq 0
926 then
927 rev=$(git rev-parse HEAD)
88983946 928 elif test $# -eq 1 || test $# -eq 2
e4f8baa8
LS
929 then
930 rev=$(git rev-parse -q --verify "$1^{commit}") ||
5626a9e2 931 die "fatal: '$1' does not refer to a commit"
e4f8baa8 932 else
1762382a
PB
933 die "fatal: you must provide exactly one revision, and optionnally a repository. Got: '$*'"
934 fi
935 repository=""
936 if test "$#" = 2
937 then
938 repository="$2"
e4f8baa8
LS
939 fi
940
cb655144
LS
941 if test -n "$arg_split_rejoin"
942 then
943 ensure_clean
944 fi
945
0ca71b37
AP
946 debug "Splitting $dir..."
947 cache_setup || exit $?
6ae6a233 948
e2b11e42 949 if test -n "$arg_split_onto"
6ae6a233 950 then
e2b11e42
LS
951 debug "Reading history for --onto=$arg_split_onto..."
952 git rev-list $arg_split_onto |
6ae6a233
DA
953 while read rev
954 do
33ff583a
AP
955 # the 'onto' history is already just the subdir, so
956 # any parent we find there can be used verbatim
e9525a8a 957 debug "cache: $rev"
6ae6a233 958 cache_set "$rev" "$rev"
d2f0f819 959 done || exit $?
33ff583a 960 fi
6ae6a233 961
1762382a 962 unrevs="$(find_existing_splits "$dir" "$rev" "$repository")" || exit $?
6ae6a233 963
1f73862f
AP
964 # We can't restrict rev-list to only $dir here, because some of our
965 # parents have the $dir contents the root, and those won't match.
966 # (and rev-list --follow doesn't seem to solve this)
e4f8baa8 967 grl='git rev-list --topo-order --reverse --parents $rev $unrevs'
942dce55
AP
968 revmax=$(eval "$grl" | wc -l)
969 revcount=0
970 createcount=0
315a84f9 971 extracount=0
942dce55 972 eval "$grl" |
6ae6a233
DA
973 while read rev parents
974 do
e9525a8a 975 process_split_commit "$rev" "$parents"
2573354e 976 done || exit $?
6ae6a233 977
d2f0f819 978 latest_new=$(cache_get latest_new) || exit $?
6ae6a233
DA
979 if test -z "$latest_new"
980 then
5626a9e2 981 die "fatal: no new revisions were found"
e25a6bf8 982 fi
6ae6a233 983
e2b11e42 984 if test -n "$arg_split_rejoin"
6ae6a233 985 then
b77172f8 986 debug "Merging split branch into HEAD..."
d2f0f819 987 latest_old=$(cache_get latest_old) || exit $?
cb655144
LS
988 arg_addmerge_message="$(rejoin_msg "$dir" "$latest_old" "$latest_new")" || exit $?
989 if test -z "$(find_latest_squash "$dir")"
990 then
991 cmd_add "$latest_new" >&2 || exit $?
992 else
993 cmd_merge "$latest_new" >&2 || exit $?
994 fi
6ae6a233 995 fi
e2b11e42 996 if test -n "$arg_split_branch"
6ae6a233 997 then
e2b11e42 998 if rev_exists "refs/heads/$arg_split_branch"
6ae6a233 999 then
e2b11e42 1000 if ! git merge-base --is-ancestor "$arg_split_branch" "$latest_new"
6ae6a233 1001 then
5626a9e2 1002 die "fatal: branch '$arg_split_branch' is not an ancestor of commit '$latest_new'."
0a562948
JS
1003 fi
1004 action='Updated'
1005 else
1006 action='Created'
1007 fi
6ae6a233 1008 git update-ref -m 'subtree split' \
e2b11e42
LS
1009 "refs/heads/$arg_split_branch" "$latest_new" || exit $?
1010 say >&2 "$action branch '$arg_split_branch'"
43a39512 1011 fi
6ae6a233 1012 echo "$latest_new"
0ca71b37
AP
1013 exit 0
1014}
1015
0d330673 1016# Usage: cmd_merge REV [REPOSITORY]
d7fd792e 1017cmd_merge () {
88983946
PS
1018 if test $# -lt 1 || test $# -gt 2
1019 then
0d330673 1020 die "fatal: you must provide exactly one revision, and optionally a repository. Got: '$*'"
88983946
PS
1021 fi
1022
e4f8baa8 1023 rev=$(git rev-parse -q --verify "$1^{commit}") ||
5626a9e2 1024 die "fatal: '$1' does not refer to a commit"
0d330673
PB
1025 repository=""
1026 if test "$#" = 2
1027 then
1028 repository="$2"
1029 fi
13648af5 1030 ensure_clean
6ae6a233 1031
e2b11e42 1032 if test -n "$arg_addmerge_squash"
6ae6a233 1033 then
0d330673 1034 first_split="$(find_latest_squash "$dir" "$repository")" || exit $?
6ae6a233
DA
1035 if test -z "$first_split"
1036 then
5626a9e2 1037 die "fatal: can't squash-merge: '$dir' was never added."
1cc2cfff
AP
1038 fi
1039 set $first_split
1040 old=$1
1041 sub=$2
6ae6a233
DA
1042 if test "$sub" = "$rev"
1043 then
6d43585a 1044 say >&2 "Subtree is already at commit $rev."
eb4fb910
AP
1045 exit 0
1046 fi
1cc2cfff
AP
1047 new=$(new_squash_commit "$old" "$sub" "$rev") || exit $?
1048 debug "New squash commit: $new"
1049 rev="$new"
1050 fi
448e71e2 1051
e2b11e42 1052 if test -n "$arg_addmerge_message"
6ae6a233 1053 then
9158a356 1054 git merge --no-ff -Xsubtree="$arg_prefix" \
e2b11e42 1055 --message="$arg_addmerge_message" "$rev"
349a70d5 1056 else
9158a356 1057 git merge --no-ff -Xsubtree="$arg_prefix" $rev
349a70d5 1058 fi
13648af5
AP
1059}
1060
5cdae0f6 1061# Usage: cmd_pull REPOSITORY REMOTEREF
d7fd792e 1062cmd_pull () {
6ae6a233
DA
1063 if test $# -ne 2
1064 then
5626a9e2 1065 die "fatal: you must provide <repository> <ref>"
1c3e0f00 1066 fi
7990142e
PB
1067 repository="$1"
1068 ref="$2"
13648af5 1069 ensure_clean
7990142e
PB
1070 ensure_valid_ref_format "$ref"
1071 git fetch "$repository" "$ref" || exit $?
0d330673 1072 cmd_merge FETCH_HEAD "$repository"
c00d1d11
WW
1073}
1074
49470cd4 1075# Usage: cmd_push REPOSITORY [+][LOCALREV:]REMOTEREF
d7fd792e 1076cmd_push () {
6ae6a233
DA
1077 if test $# -ne 2
1078 then
5626a9e2 1079 die "fatal: you must provide <repository> <refspec>"
c00d1d11 1080 fi
6ae6a233
DA
1081 if test -e "$dir"
1082 then
1083 repository=$1
49470cd4
LS
1084 refspec=${2#+}
1085 remoteref=${refspec#*:}
1086 if test "$remoteref" = "$refspec"
1087 then
1088 localrevname_presplit=HEAD
1089 else
1090 localrevname_presplit=${refspec%%:*}
1091 fi
1092 ensure_valid_ref_format "$remoteref"
1093 localrev_presplit=$(git rev-parse -q --verify "$localrevname_presplit^{commit}") ||
5626a9e2 1094 die "fatal: '$localrevname_presplit' does not refer to a commit"
49470cd4 1095
6ae6a233 1096 echo "git push using: " "$repository" "$refspec"
1762382a 1097 localrev=$(cmd_split "$localrev_presplit" "$repository") || die
49470cd4 1098 git push "$repository" "$localrev":"refs/heads/$remoteref"
c00d1d11 1099 else
5626a9e2 1100 die "fatal: '$dir' must already exist. Try 'git subtree add'."
c00d1d11 1101 fi
0ca71b37
AP
1102}
1103
5a356977 1104main "$@"