3 # git-subtree.sh: split/join git repositories in subdirectories of this one
5 # Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>
11 git subtree add --prefix=<prefix> <commit>
12 git subtree merge --prefix=<prefix> <commit>
13 git subtree pull --prefix=<prefix> <repository> <refspec...>
14 git subtree push --prefix=<prefix> <repository> <refspec...>
15 git subtree split --prefix=<prefix> <commit...>
20 P,prefix= the name of the subdir to split out
21 m,message= use the given message as the commit message for the merge commit
23 annotate= add a prefix to commit message of new commits
24 b,branch= create a new branch from the split subtree
25 ignore-joins ignore prior --rejoin commits
26 onto= try connecting new tree to an existing one
27 rejoin merge the new branch back into HEAD
28 options for 'add', 'merge', 'pull' and 'push'
29 squash merge subtree changes as a single commit
31 eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
33 PATH=$PATH:$(git --exec-path)
51 if [ -n "$debug" ]; then
58 if [ -z "$quiet" ]; then
68 die "assertion failed: " "$@"
75 while [ $# -gt 0 ]; do
81 --annotate) annotate="$1"; shift ;;
82 --no-annotate) annotate= ;;
83 -b) branch="$1"; shift ;;
84 -P) prefix="$1"; shift ;;
85 -m) message="$1"; shift ;;
86 --no-prefix) prefix= ;;
87 --onto) onto="$1"; shift ;;
90 --no-rejoin) rejoin= ;;
91 --ignore-joins) ignore_joins=1 ;;
92 --no-ignore-joins) ignore_joins= ;;
94 --no-squash) squash= ;;
96 *) die "Unexpected option: $opt" ;;
103 add|merge|pull) default= ;;
104 split|push) default="--default HEAD" ;;
105 *) die "Unknown command '$command'" ;;
108 if [ -z "$prefix" ]; then
109 die "You must provide the --prefix option."
113 add) [ -e "$prefix" ] &&
114 die "prefix '$prefix' already exists." ;;
115 *) [ -e "$prefix" ] ||
116 die "'$prefix' does not exist; use 'git subtree add'" ;;
119 dir="$(dirname "$prefix/.")"
121 if [ "$command" != "pull" -a "$command" != "add" -a "$command" != "push" ]; then
122 revs=$(git rev-parse $default --revs-only "$@") || exit $?
123 dirs="$(git rev-parse --no-revs --no-flags "$@")" || exit $?
124 if [ -n "$dirs" ]; then
125 die "Error: Use --prefix instead of bare filenames."
129 debug "command: {$command}"
130 debug "quiet: {$quiet}"
131 debug "revs: {$revs}"
138 cachedir="$GIT_DIR/subtree-cache/$$"
139 rm -rf "$cachedir" || die "Can't delete old cachedir: $cachedir"
140 mkdir -p "$cachedir" || die "Can't create new cachedir: $cachedir"
141 mkdir -p "$cachedir/notree" || die "Can't create new cachedir: $cachedir/notree"
142 debug "Using cachedir: $cachedir" >&2
148 if [ -r "$cachedir/$oldrev" ]; then
149 read newrev <"$cachedir/$oldrev"
158 if [ ! -r "$cachedir/$oldrev" ]; then
166 missed=$(cache_miss $*)
167 for miss in $missed; do
168 if [ ! -r "$cachedir/notree/$miss" ]; then
169 debug " incorrect order: $miss"
176 echo "1" > "$cachedir/notree/$1"
183 if [ "$oldrev" != "latest_old" \
184 -a "$oldrev" != "latest_new" \
185 -a -e "$cachedir/$oldrev" ]; then
186 die "cache for $oldrev already exists!"
188 echo "$newrev" >"$cachedir/$oldrev"
193 if git rev-parse "$1" >/dev/null 2>&1; then
200 rev_is_descendant_of_branch()
204 branch_hash=$(git rev-parse $branch)
205 match=$(git rev-list -1 $branch_hash ^$newrev)
207 if [ -z "$match" ]; then
214 # if a commit doesn't have a parent, this might not work. But we only want
215 # to remove the parent from the rev-list, and since it doesn't exist, it won't
216 # be there anyway, so do nothing in that case.
217 try_remove_previous()
219 if rev_exists "$1^"; then
226 debug "Looking for latest squash ($dir)..."
231 git log --grep="^git-subtree-dir: $dir/*\$" \
232 --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD |
233 while read a b junk; do
235 debug "{{$sq/$main/$sub}}"
238 git-subtree-mainline:) main="$b" ;;
239 git-subtree-split:) sub="$b" ;;
241 if [ -n "$sub" ]; then
242 if [ -n "$main" ]; then
244 # Pretend its sub was a squash.
247 debug "Squash found: $sq $sub"
259 find_existing_splits()
261 debug "Looking for prior splits..."
266 git log --grep="^git-subtree-dir: $dir/*\$" \
267 --pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs |
268 while read a b junk; do
271 git-subtree-mainline:) main="$b" ;;
272 git-subtree-split:) sub="$b" ;;
274 debug " Main is: '$main'"
275 if [ -z "$main" -a -n "$sub" ]; then
276 # squash commits refer to a subtree
277 debug " Squash: $sq from $sub"
278 cache_set "$sq" "$sub"
280 if [ -n "$main" -a -n "$sub" ]; then
281 debug " Prior: $main -> $sub"
284 try_remove_previous "$main"
285 try_remove_previous "$sub"
296 # We're going to set some environment vars here, so
297 # do it in a subshell to get rid of them safely later
298 debug copy_commit "{$1}" "{$2}" "{$3}"
299 git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%B' "$1" |
302 read GIT_AUTHOR_EMAIL
304 read GIT_COMMITTER_NAME
305 read GIT_COMMITTER_EMAIL
306 read GIT_COMMITTER_DATE
307 export GIT_AUTHOR_NAME \
311 GIT_COMMITTER_EMAIL \
313 (echo -n "$annotate"; cat ) |
314 git commit-tree "$2" $3 # reads the rest of stdin
315 ) || die "Can't copy commit $1"
323 if [ -n "$message" ]; then
324 commit_message="$message"
326 commit_message="Add '$dir/' from commit '$latest_new'"
331 git-subtree-dir: $dir
332 git-subtree-mainline: $latest_old
333 git-subtree-split: $latest_new
339 if [ -n "$message" ]; then
342 echo "Merge commit '$1' as '$2'"
351 if [ -n "$message" ]; then
352 commit_message="$message"
354 commit_message="Split '$dir/' into commit '$latest_new'"
359 git-subtree-dir: $dir
360 git-subtree-mainline: $latest_old
361 git-subtree-split: $latest_new
370 newsub_short=$(git rev-parse --short "$newsub")
372 if [ -n "$oldsub" ]; then
373 oldsub_short=$(git rev-parse --short "$oldsub")
374 echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short"
376 git log --pretty=tformat:'%h %s' "$oldsub..$newsub"
377 git log --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub"
379 echo "Squashed '$dir/' content from commit $newsub_short"
383 echo "git-subtree-dir: $dir"
384 echo "git-subtree-split: $newsub"
390 git log -1 --pretty=format:'%T' "$commit" -- || exit $?
397 git ls-tree "$commit" -- "$dir" |
398 while read mode type tree name; do
399 assert [ "$name" = "$dir" ]
400 assert [ "$type" = "tree" -o "$type" = "commit" ]
401 [ "$type" = "commit" ] && continue # ignore submodules
411 if [ $# -ne 1 ]; then
412 return 0 # weird parents, consider it changed
414 ptree=$(toptree_for_commit $1)
415 if [ "$ptree" != "$tree" ]; then
418 return 1 # not changed
428 tree=$(toptree_for_commit $newsub) || exit $?
429 if [ -n "$old" ]; then
430 squash_msg "$dir" "$oldsub" "$newsub" |
431 git commit-tree "$tree" -p "$old" || exit $?
433 squash_msg "$dir" "" "$newsub" |
434 git commit-tree "$tree" || exit $?
443 assert [ -n "$tree" ]
449 for parent in $newparents; do
450 ptree=$(toptree_for_commit $parent) || exit $?
451 [ -z "$ptree" ] && continue
452 if [ "$ptree" = "$tree" ]; then
453 # an identical parent could be used in place of this rev.
456 nonidentical="$parent"
459 # sometimes both old parents map to the same newparent;
460 # eliminate duplicates
462 for gp in $gotparents; do
463 if [ "$gp" = "$parent" ]; then
468 if [ -n "$is_new" ]; then
469 gotparents="$gotparents $parent"
474 if [ -n "$identical" ]; then
477 copy_commit $rev $tree "$p" || exit $?
483 if ! git diff-index HEAD --exit-code --quiet 2>&1; then
484 die "Working tree has modifications. Cannot add."
486 if ! git diff-index --cached HEAD --exit-code --quiet 2>&1; then
487 die "Index has modifications. Cannot add."
493 if [ -e "$dir" ]; then
494 die "'$dir' already exists. Cannot add."
499 if [ $# -eq 1 ]; then
500 git rev-parse -q --verify "$1^{commit}" >/dev/null ||
501 die "'$1' does not refer to a commit"
503 "cmd_add_commit" "$@"
504 elif [ $# -eq 2 ]; then
505 git rev-parse -q --verify "$2^{commit}" >/dev/null ||
506 die "'$2' does not refer to a commit"
508 "cmd_add_repository" "$@"
510 say "error: parameters were '$@'"
511 die "Provide either a commit or a repository and commit."
517 echo "git fetch" "$@"
520 git fetch "$@" || exit $?
528 revs=$(git rev-parse $default --revs-only "$@") || exit $?
532 debug "Adding $dir as '$rev'..."
533 git read-tree --prefix="$dir" $rev || exit $?
534 git checkout -- "$dir" || exit $?
535 tree=$(git write-tree) || exit $?
537 headrev=$(git rev-parse HEAD) || exit $?
538 if [ -n "$headrev" -a "$headrev" != "$rev" ]; then
544 if [ -n "$squash" ]; then
545 rev=$(new_squash_commit "" "" "$rev") || exit $?
546 commit=$(add_squashed_msg "$rev" "$dir" |
547 git commit-tree $tree $headp -p "$rev") || exit $?
549 commit=$(add_msg "$dir" "$headrev" "$rev" |
550 git commit-tree $tree $headp -p "$rev") || exit $?
552 git reset "$commit" || exit $?
554 say "Added dir '$dir'"
559 debug "Splitting $dir..."
560 cache_setup || exit $?
562 if [ -n "$onto" ]; then
563 debug "Reading history for --onto=$onto..."
566 # the 'onto' history is already just the subdir, so
567 # any parent we find there can be used verbatim
573 if [ -n "$ignore_joins" ]; then
576 unrevs="$(find_existing_splits "$dir" "$revs")"
579 # We can't restrict rev-list to only $dir here, because some of our
580 # parents have the $dir contents the root, and those won't match.
581 # (and rev-list --follow doesn't seem to solve this)
582 grl='git rev-list --topo-order --reverse --parents $revs $unrevs'
583 revmax=$(eval "$grl" | wc -l)
587 while read rev parents; do
588 revcount=$(($revcount + 1))
589 say -n "$revcount/$revmax ($createcount)
\r"
590 debug "Processing commit: $rev"
591 exists=$(cache_get $rev)
592 if [ -n "$exists" ]; then
593 debug " prior: $exists"
596 createcount=$(($createcount + 1))
597 debug " parents: $parents"
598 newparents=$(cache_get $parents)
599 debug " newparents: $newparents"
601 tree=$(subtree_for_commit $rev "$dir")
602 debug " tree is: $tree"
604 check_parents $parents
606 # ugly. is there no better way to tell if this is a subtree
607 # vs. a mainline commit? Does it matter?
608 if [ -z $tree ]; then
610 if [ -n "$newparents" ]; then
616 newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
617 debug " newrev is: $newrev"
618 cache_set $rev $newrev
619 cache_set latest_new $newrev
620 cache_set latest_old $rev
622 latest_new=$(cache_get latest_new)
623 if [ -z "$latest_new" ]; then
624 die "No new revisions were found"
627 if [ -n "$rejoin" ]; then
628 debug "Merging split branch into HEAD..."
629 latest_old=$(cache_get latest_old)
631 -m "$(rejoin_msg $dir $latest_old $latest_new)" \
632 $latest_new >&2 || exit $?
634 if [ -n "$branch" ]; then
635 if rev_exists "refs/heads/$branch"; then
636 if ! rev_is_descendant_of_branch $latest_new $branch; then
637 die "Branch '$branch' is not an ancestor of commit '$latest_new'."
643 git update-ref -m 'subtree split' "refs/heads/$branch" $latest_new || exit $?
644 say "$action branch '$branch'"
652 revs=$(git rev-parse $default --revs-only "$@") || exit $?
656 if [ $# -ne 1 ]; then
657 die "You must provide exactly one revision. Got: '$revs'"
661 if [ -n "$squash" ]; then
662 first_split="$(find_latest_squash "$dir")"
663 if [ -z "$first_split" ]; then
664 die "Can't squash-merge: '$dir' was never added."
669 if [ "$sub" = "$rev" ]; then
670 say "Subtree is already at commit $rev."
673 new=$(new_squash_commit "$old" "$sub" "$rev") || exit $?
674 debug "New squash commit: $new"
678 version=$(git version)
679 if [ "$version" \< "git version 1.7" ]; then
680 if [ -n "$message" ]; then
681 git merge -s subtree --message="$message" $rev
683 git merge -s subtree $rev
686 if [ -n "$message" ]; then
687 git merge -Xsubtree="$prefix" --message="$message" $rev
689 git merge -Xsubtree="$prefix" $rev
697 git fetch "$@" || exit $?
705 if [ $# -ne 2 ]; then
706 die "You must provide <repository> <refspec>"
708 if [ -e "$dir" ]; then
711 echo "git push using: " $repository $refspec
712 git push $repository $(git subtree split --prefix=$prefix):refs/heads/$refspec
714 die "'$dir' must already exist. Try 'git subtree add'."