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