3 # git-subtree.sh: split/join git repositories in subdirectories of this one
5 # Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>
8 if test -z "$GIT_EXEC_PATH" || test "${PATH#"${GIT_EXEC_PATH}:"}" = "$PATH" || ! test -f "$GIT_EXEC_PATH/git-sh-setup"
10 echo >&2 'It looks like either your git installation or your'
11 echo >&2 'git-subtree installation is broken.'
14 echo >&2 " - If \`git --exec-path\` does not print the correct path to"
15 echo >&2 " your git install directory, then set the GIT_EXEC_PATH"
16 echo >&2 " environment variable to the correct directory."
17 echo >&2 " - Make sure that your \`${0##*/}\` file is either in your"
18 echo >&2 " PATH or in your git exec path (\`$(git --exec-path)\`)."
19 echo >&2 " - You should run git-subtree as \`git ${0##*/git-}\`,"
20 echo >&2 " not as \`${0##*/}\`." >&2
25 git subtree add --prefix=<prefix> <commit>
26 git subtree add --prefix=<prefix> <repository> <ref>
27 git subtree merge --prefix=<prefix> <commit>
28 git subtree pull --prefix=<prefix> <repository> <ref>
29 git subtree push --prefix=<prefix> <repository> <ref>
30 git subtree split --prefix=<prefix> <commit>
35 P,prefix= the name of the subdir to split out
36 m,message= use the given message as the commit message for the merge commit
38 annotate= add a prefix to commit message of new commits
39 b,branch= create a new branch from the split subtree
40 ignore-joins ignore prior --rejoin commits
41 onto= try connecting new tree to an existing one
42 rejoin merge the new branch back into HEAD
43 options for 'add', 'merge', and 'pull'
44 squash merge subtree changes as a single commit
53 arg_split_ignore_joins=
58 # Usage: debug [MSG...]
60 if test -n "$arg_debug"
62 printf "%s\n" "$*" >&2
66 # Usage: progress [MSG...]
68 if test -z "$GIT_QUIET"
70 printf "%s\r" "$*" >&2
74 # Usage: assert CMD...
78 die "assertion failed: $*"
87 eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
104 arg_split_annotate="$1"
111 arg_split_branch="$1"
119 arg_addmerge_message="$1"
139 arg_split_ignore_joins=1
142 arg_split_ignore_joins=
145 arg_addmerge_squash=1
154 die "Unexpected option: $opt"
162 case "$arg_command" in
163 add|merge|pull|split|push)
167 die "Unknown command '$arg_command'"
171 if test -z "$arg_prefix"
173 die "You must provide the --prefix option."
176 case "$arg_command" in
178 test -e "$arg_prefix" &&
179 die "prefix '$arg_prefix' already exists."
182 test -e "$arg_prefix" ||
183 die "'$arg_prefix' does not exist; use 'git subtree add'"
187 dir="$(dirname "$arg_prefix/.")"
189 debug "command: {$arg_command}"
190 debug "quiet: {$GIT_QUIET}"
195 "cmd_$arg_command" "$@"
201 cachedir="$GIT_DIR/subtree-cache/$$"
202 rm -rf "$cachedir" ||
203 die "Can't delete old cachedir: $cachedir"
204 mkdir -p "$cachedir" ||
205 die "Can't create new cachedir: $cachedir"
206 mkdir -p "$cachedir/notree" ||
207 die "Can't create new cachedir: $cachedir/notree"
208 debug "Using cachedir: $cachedir" >&2
211 # Usage: cache_get [REVS...]
215 if test -r "$cachedir/$oldrev"
217 read newrev <"$cachedir/$oldrev"
223 # Usage: cache_miss [REVS...]
227 if ! test -r "$cachedir/$oldrev"
234 # Usage: check_parents PARENTS_EXPR INDENT
237 missed=$(cache_miss "$1") || exit $?
238 local indent=$(($2 + 1))
241 if ! test -r "$cachedir/notree/$miss"
243 debug " incorrect order: $miss"
244 process_split_commit "$miss" "" "$indent"
249 # Usage: set_notree REV
252 echo "1" > "$cachedir/notree/$1"
255 # Usage: cache_set OLDREV NEWREV
260 if test "$oldrev" != "latest_old" &&
261 test "$oldrev" != "latest_new" &&
262 test -e "$cachedir/$oldrev"
264 die "cache for $oldrev already exists!"
266 echo "$newrev" >"$cachedir/$oldrev"
269 # Usage: rev_exists REV
272 if git rev-parse "$1" >/dev/null 2>&1
280 # Usage: try_remove_previous REV
282 # If a commit doesn't have a parent, this might not work. But we only want
283 # to remove the parent from the rev-list, and since it doesn't exist, it won't
284 # be there anyway, so do nothing in that case.
285 try_remove_previous () {
293 # Usage: find_latest_squash DIR
294 find_latest_squash () {
296 debug "Looking for latest squash ($dir)..."
301 git log --grep="^git-subtree-dir: $dir/*\$" \
302 --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD |
306 debug "{{$sq/$main/$sub}}"
311 git-subtree-mainline:)
315 sub="$(git rev-parse "$b^{commit}")" ||
316 die "could not rev-parse split hash $b from commit $sq"
324 # Pretend its sub was a squash.
327 debug "Squash found: $sq $sub"
339 # Usage: find_existing_splits DIR REV
340 find_existing_splits () {
342 debug "Looking for prior splits..."
347 local grep_format="^git-subtree-dir: $dir/*\$"
348 if test -n "$arg_split_ignore_joins"
350 grep_format="^Add '$dir/' from commit '"
352 git log --grep="$grep_format" \
353 --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' "$rev" |
360 git-subtree-mainline:)
364 sub="$(git rev-parse "$b^{commit}")" ||
365 die "could not rev-parse split hash $b from commit $sq"
368 debug " Main is: '$main'"
369 if test -z "$main" -a -n "$sub"
371 # squash commits refer to a subtree
372 debug " Squash: $sq from $sub"
373 cache_set "$sq" "$sub"
375 if test -n "$main" -a -n "$sub"
377 debug " Prior: $main -> $sub"
380 try_remove_previous "$main"
381 try_remove_previous "$sub"
390 # Usage: copy_commit REV TREE FLAGS_STR
393 # We're going to set some environment vars here, so
394 # do it in a subshell to get rid of them safely later
395 debug copy_commit "{$1}" "{$2}" "{$3}"
396 git log -1 --no-show-signature --pretty=format:'%an%n%ae%n%aD%n%cn%n%ce%n%cD%n%B' "$1" |
399 read GIT_AUTHOR_EMAIL
401 read GIT_COMMITTER_NAME
402 read GIT_COMMITTER_EMAIL
403 read GIT_COMMITTER_DATE
404 export GIT_AUTHOR_NAME \
408 GIT_COMMITTER_EMAIL \
411 printf "%s" "$arg_split_annotate"
414 git commit-tree "$2" $3 # reads the rest of stdin
415 ) || die "Can't copy commit $1"
418 # Usage: add_msg DIR LATEST_OLD LATEST_NEW
424 if test -n "$arg_addmerge_message"
426 commit_message="$arg_addmerge_message"
428 commit_message="Add '$dir/' from commit '$latest_new'"
433 git-subtree-dir: $dir
434 git-subtree-mainline: $latest_old
435 git-subtree-split: $latest_new
439 # Usage: add_squashed_msg REV DIR
440 add_squashed_msg () {
442 if test -n "$arg_addmerge_message"
444 echo "$arg_addmerge_message"
446 echo "Merge commit '$1' as '$2'"
450 # Usage: rejoin_msg DIR LATEST_OLD LATEST_NEW
456 if test -n "$arg_addmerge_message"
458 commit_message="$arg_addmerge_message"
460 commit_message="Split '$dir/' into commit '$latest_new'"
465 git-subtree-dir: $dir
466 git-subtree-mainline: $latest_old
467 git-subtree-split: $latest_new
471 # Usage: squash_msg DIR OLD_SUBTREE_COMMIT NEW_SUBTREE_COMMIT
477 newsub_short=$(git rev-parse --short "$newsub")
481 oldsub_short=$(git rev-parse --short "$oldsub")
482 echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short"
484 git log --no-show-signature --pretty=tformat:'%h %s' "$oldsub..$newsub"
485 git log --no-show-signature --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub"
487 echo "Squashed '$dir/' content from commit $newsub_short"
491 echo "git-subtree-dir: $dir"
492 echo "git-subtree-split: $newsub"
495 # Usage: toptree_for_commit COMMIT
496 toptree_for_commit () {
499 git rev-parse --verify "$commit^{tree}" || exit $?
502 # Usage: subtree_for_commit COMMIT DIR
503 subtree_for_commit () {
507 git ls-tree "$commit" -- "$dir" |
508 while read mode type tree name
510 assert test "$name" = "$dir"
511 assert test "$type" = "tree" -o "$type" = "commit"
512 test "$type" = "commit" && continue # ignore submodules
518 # Usage: tree_changed TREE [PARENTS...]
525 return 0 # weird parents, consider it changed
527 ptree=$(toptree_for_commit $1) || exit $?
528 if test "$ptree" != "$tree"
532 return 1 # not changed
537 # Usage: new_squash_commit OLD_SQUASHED_COMMIT OLD_NONSQUASHED_COMMIT NEW_NONSQUASHED_COMMIT
538 new_squash_commit () {
543 tree=$(toptree_for_commit $newsub) || exit $?
546 squash_msg "$dir" "$oldsub" "$newsub" |
547 git commit-tree "$tree" -p "$old" || exit $?
549 squash_msg "$dir" "" "$newsub" |
550 git commit-tree "$tree" || exit $?
554 # Usage: copy_or_skip REV TREE NEWPARENTS
560 assert test -n "$tree"
567 for parent in $newparents
569 ptree=$(toptree_for_commit $parent) || exit $?
570 test -z "$ptree" && continue
571 if test "$ptree" = "$tree"
573 # an identical parent could be used in place of this rev.
574 if test -n "$identical"
576 # if a previous identical parent was found, check whether
577 # one is already an ancestor of the other
578 mergebase=$(git merge-base $identical $parent)
579 if test "$identical" = "$mergebase"
581 # current identical commit is an ancestor of parent
583 elif test "$parent" != "$mergebase"
585 # no common history; commit must be copied
589 # first identical parent detected
593 nonidentical="$parent"
596 # sometimes both old parents map to the same newparent;
597 # eliminate duplicates
599 for gp in $gotparents
601 if test "$gp" = "$parent"
609 gotparents="$gotparents $parent"
614 if test -n "$identical" && test -n "$nonidentical"
616 extras=$(git rev-list --count $identical..$nonidentical)
617 if test "$extras" -ne 0
619 # we need to preserve history along the other branch
623 if test -n "$identical" && test -z "$copycommit"
627 copy_commit "$rev" "$tree" "$p" || exit $?
631 # Usage: ensure_clean
634 if ! git diff-index HEAD --exit-code --quiet 2>&1
636 die "Working tree has modifications. Cannot add."
638 if ! git diff-index --cached HEAD --exit-code --quiet 2>&1
640 die "Index has modifications. Cannot add."
644 # Usage: ensure_valid_ref_format REF
645 ensure_valid_ref_format () {
647 git check-ref-format "refs/heads/$1" ||
648 die "'$1' does not look like a ref"
651 # Usage: process_split_commit REV PARENTS INDENT
652 process_split_commit () {
658 if test $indent -eq 0
660 revcount=$(($revcount + 1))
662 # processing commit without normal parent information;
664 parents=$(git rev-parse "$rev^@")
665 extracount=$(($extracount + 1))
668 progress "$revcount/$revmax ($createcount) [$extracount]"
670 debug "Processing commit: $rev"
671 exists=$(cache_get "$rev") || exit $?
674 debug " prior: $exists"
677 createcount=$(($createcount + 1))
678 debug " parents: $parents"
679 check_parents "$parents" "$indent"
680 newparents=$(cache_get $parents) || exit $?
681 debug " newparents: $newparents"
683 tree=$(subtree_for_commit "$rev" "$dir") || exit $?
684 debug " tree is: $tree"
686 # ugly. is there no better way to tell if this is a subtree
687 # vs. a mainline commit? Does it matter?
691 if test -n "$newparents"
693 cache_set "$rev" "$rev"
698 newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
699 debug " newrev is: $newrev"
700 cache_set "$rev" "$newrev"
701 cache_set latest_new "$newrev"
702 cache_set latest_old "$rev"
706 # Or: cmd_add REPOSITORY REF
713 git rev-parse -q --verify "$1^{commit}" >/dev/null ||
714 die "'$1' does not refer to a commit"
720 # Technically we could accept a refspec here but we're
721 # just going to turn around and add FETCH_HEAD under the
722 # specified directory. Allowing a refspec might be
723 # misleading because we won't do anything with any other
724 # branches fetched via the refspec.
725 ensure_valid_ref_format "$2"
727 cmd_add_repository "$@"
729 say >&2 "error: parameters were '$*'"
730 die "Provide either a commit or a repository and commit."
734 # Usage: cmd_add_repository REPOSITORY REFSPEC
735 cmd_add_repository () {
737 echo "git fetch" "$@"
740 git fetch "$@" || exit $?
741 cmd_add_commit FETCH_HEAD
744 # Usage: cmd_add_commit REV
746 # The rev has already been validated by cmd_add(), we just
747 # need to normalize it.
749 rev=$(git rev-parse --verify "$1^{commit}") || exit $?
751 debug "Adding $dir as '$rev'..."
752 git read-tree --prefix="$dir" $rev || exit $?
753 git checkout -- "$dir" || exit $?
754 tree=$(git write-tree) || exit $?
756 headrev=$(git rev-parse HEAD) || exit $?
757 if test -n "$headrev" && test "$headrev" != "$rev"
764 if test -n "$arg_addmerge_squash"
766 rev=$(new_squash_commit "" "" "$rev") || exit $?
767 commit=$(add_squashed_msg "$rev" "$dir" |
768 git commit-tree "$tree" $headp -p "$rev") || exit $?
770 revp=$(peel_committish "$rev") || exit $?
771 commit=$(add_msg "$dir" $headrev "$rev" |
772 git commit-tree "$tree" $headp -p "$revp") || exit $?
774 git reset "$commit" || exit $?
776 say >&2 "Added dir '$dir'"
779 # Usage: cmd_split [REV]
783 rev=$(git rev-parse HEAD)
786 rev=$(git rev-parse -q --verify "$1^{commit}") ||
787 die "'$1' does not refer to a commit"
789 die "You must provide exactly one revision. Got: '$*'"
792 debug "Splitting $dir..."
793 cache_setup || exit $?
795 if test -n "$arg_split_onto"
797 debug "Reading history for --onto=$arg_split_onto..."
798 git rev-list $arg_split_onto |
801 # the 'onto' history is already just the subdir, so
802 # any parent we find there can be used verbatim
804 cache_set "$rev" "$rev"
808 unrevs="$(find_existing_splits "$dir" "$rev")" || exit $?
810 # We can't restrict rev-list to only $dir here, because some of our
811 # parents have the $dir contents the root, and those won't match.
812 # (and rev-list --follow doesn't seem to solve this)
813 grl='git rev-list --topo-order --reverse --parents $rev $unrevs'
814 revmax=$(eval "$grl" | wc -l)
819 while read rev parents
821 process_split_commit "$rev" "$parents" 0
824 latest_new=$(cache_get latest_new) || exit $?
825 if test -z "$latest_new"
827 die "No new revisions were found"
830 if test -n "$arg_split_rejoin"
832 debug "Merging split branch into HEAD..."
833 latest_old=$(cache_get latest_old) || exit $?
835 --allow-unrelated-histories \
836 -m "$(rejoin_msg "$dir" "$latest_old" "$latest_new")" \
837 "$latest_new" >&2 || exit $?
839 if test -n "$arg_split_branch"
841 if rev_exists "refs/heads/$arg_split_branch"
843 if ! git merge-base --is-ancestor "$arg_split_branch" "$latest_new"
845 die "Branch '$arg_split_branch' is not an ancestor of commit '$latest_new'."
851 git update-ref -m 'subtree split' \
852 "refs/heads/$arg_split_branch" "$latest_new" || exit $?
853 say >&2 "$action branch '$arg_split_branch'"
859 # Usage: cmd_merge REV
862 die "You must provide exactly one revision. Got: '$*'"
863 rev=$(git rev-parse -q --verify "$1^{commit}") ||
864 die "'$1' does not refer to a commit"
867 if test -n "$arg_addmerge_squash"
869 first_split="$(find_latest_squash "$dir")" || exit $?
870 if test -z "$first_split"
872 die "Can't squash-merge: '$dir' was never added."
877 if test "$sub" = "$rev"
879 say >&2 "Subtree is already at commit $rev."
882 new=$(new_squash_commit "$old" "$sub" "$rev") || exit $?
883 debug "New squash commit: $new"
887 if test -n "$arg_addmerge_message"
889 git merge -Xsubtree="$arg_prefix" \
890 --message="$arg_addmerge_message" "$rev"
892 git merge -Xsubtree="$arg_prefix" $rev
896 # Usage: cmd_pull REPOSITORY REMOTEREF
900 die "You must provide <repository> <ref>"
903 ensure_valid_ref_format "$2"
904 git fetch "$@" || exit $?
908 # Usage: cmd_push REPOSITORY REMOTEREF
912 die "You must provide <repository> <ref>"
914 ensure_valid_ref_format "$2"
919 echo "git push using: " "$repository" "$refspec"
920 localrev=$(git subtree split --prefix="$arg_prefix") || die
921 git push "$repository" "$localrev":"refs/heads/$refspec"
923 die "'$dir' must already exist. Try 'git subtree add'."