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 $?
)
32 PATH
=$
(git
--exec-path):$PATH
49 if [ -n "$debug" ]; then
56 if [ -z "$quiet" ]; then
66 die
"assertion failed: " "$@"
73 while [ $# -gt 0 ]; do
79 --annotate) annotate
="$1"; shift ;;
80 --no-annotate) annotate
= ;;
81 -b) branch
="$1"; shift ;;
82 -P) prefix
="$1"; shift ;;
83 -m) message
="$1"; shift ;;
84 --no-prefix) prefix
= ;;
85 --onto) onto
="$1"; shift ;;
88 --no-rejoin) rejoin
= ;;
89 --ignore-joins) ignore_joins
=1 ;;
90 --no-ignore-joins) ignore_joins
= ;;
92 --no-squash) squash
= ;;
94 *) die
"Unexpected option: $opt" ;;
101 add|merge|pull
) default
= ;;
102 split|push
) default
="--default HEAD" ;;
103 *) die
"Unknown command '$command'" ;;
106 if [ -z "$prefix" ]; then
107 die
"You must provide the --prefix option."
111 add
) [ -e "$prefix" ] &&
112 die
"prefix '$prefix' already exists." ;;
113 *) [ -e "$prefix" ] ||
114 die
"'$prefix' does not exist; use 'git subtree add'" ;;
117 dir
="$(dirname "$prefix/.
")"
119 if [ "$command" != "pull" -a "$command" != "add" -a "$command" != "push" ]; then
120 revs
=$
(git rev-parse
$default --revs-only "$@") ||
exit $?
121 dirs="$(git rev-parse --no-revs --no-flags "$@
")" ||
exit $?
122 if [ -n "$dirs" ]; then
123 die
"Error: Use --prefix instead of bare filenames."
127 debug
"command: {$command}"
128 debug
"quiet: {$quiet}"
129 debug
"revs: {$revs}"
136 cachedir
="$GIT_DIR/subtree-cache/$$"
137 rm -rf "$cachedir" || die
"Can't delete old cachedir: $cachedir"
138 mkdir
-p "$cachedir" || die
"Can't create new cachedir: $cachedir"
139 debug
"Using cachedir: $cachedir" >&2
145 if [ -r "$cachedir/$oldrev" ]; then
146 read newrev
<"$cachedir/$oldrev"
156 if [ "$oldrev" != "latest_old" \
157 -a "$oldrev" != "latest_new" \
158 -a -e "$cachedir/$oldrev" ]; then
159 die
"cache for $oldrev already exists!"
161 echo "$newrev" >"$cachedir/$oldrev"
166 if git rev-parse
"$1" >/dev
/null
2>&1; then
173 rev_is_descendant_of_branch
()
177 branch_hash
=$
(git rev-parse
$branch)
178 match
=$
(git rev-list
-1 $branch_hash ^
$newrev)
180 if [ -z "$match" ]; then
187 # if a commit doesn't have a parent, this might not work. But we only want
188 # to remove the parent from the rev-list, and since it doesn't exist, it won't
189 # be there anyway, so do nothing in that case.
190 try_remove_previous
()
192 if rev_exists
"$1^"; then
199 debug
"Looking for latest squash ($dir)..."
204 git log
--grep="^git-subtree-dir: $dir/*\$" \
205 --pretty=format
:'START %H%n%s%n%n%b%nEND%n' HEAD |
206 while read a b junk
; do
208 debug
"{{$sq/$main/$sub}}"
211 git-subtree-mainline
:) main
="$b" ;;
212 git-subtree-split
:) sub
="$b" ;;
214 if [ -n "$sub" ]; then
215 if [ -n "$main" ]; then
217 # Pretend its sub was a squash.
220 debug
"Squash found: $sq $sub"
232 find_existing_splits
()
234 debug
"Looking for prior splits..."
239 git log
--grep="^git-subtree-dir: $dir/*\$" \
240 --pretty=format
:'START %H%n%s%n%n%b%nEND%n' $revs |
241 while read a b junk
; do
244 git-subtree-mainline
:) main
="$b" ;;
245 git-subtree-split
:) sub
="$b" ;;
247 debug
" Main is: '$main'"
248 if [ -z "$main" -a -n "$sub" ]; then
249 # squash commits refer to a subtree
250 debug
" Squash: $sq from $sub"
251 cache_set
"$sq" "$sub"
253 if [ -n "$main" -a -n "$sub" ]; then
254 debug
" Prior: $main -> $sub"
256 try_remove_previous
"$main"
257 try_remove_previous
"$sub"
268 # We're going to set some environment vars here, so
269 # do it in a subshell to get rid of them safely later
270 debug copy_commit
"{$1}" "{$2}" "{$3}"
271 git log
-1 --pretty=format
:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" |
274 read GIT_AUTHOR_EMAIL
276 read GIT_COMMITTER_NAME
277 read GIT_COMMITTER_EMAIL
278 read GIT_COMMITTER_DATE
279 export GIT_AUTHOR_NAME \
283 GIT_COMMITTER_EMAIL \
285 (echo -n "$annotate"; cat ) |
286 git commit-tree
"$2" $3 # reads the rest of stdin
287 ) || die
"Can't copy commit $1"
295 if [ -n "$message" ]; then
296 commit_message
="$message"
298 commit_message
="Add '$dir/' from commit '$latest_new'"
303 git-subtree-dir: $dir
304 git-subtree-mainline: $latest_old
305 git-subtree-split: $latest_new
311 if [ -n "$message" ]; then
314 echo "Merge commit '$1' as '$2'"
323 if [ -n "$message" ]; then
324 commit_message
="$message"
326 commit_message
="Split '$dir/' into commit '$latest_new'"
331 git-subtree-dir: $dir
332 git-subtree-mainline: $latest_old
333 git-subtree-split: $latest_new
342 newsub_short
=$
(git rev-parse
--short "$newsub")
344 if [ -n "$oldsub" ]; then
345 oldsub_short
=$
(git rev-parse
--short "$oldsub")
346 echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short"
348 git log
--pretty=tformat
:'%h %s' "$oldsub..$newsub"
349 git log
--pretty=tformat
:'REVERT: %h %s' "$newsub..$oldsub"
351 echo "Squashed '$dir/' content from commit $newsub_short"
355 echo "git-subtree-dir: $dir"
356 echo "git-subtree-split: $newsub"
362 git log
-1 --pretty=format
:'%T' "$commit" -- ||
exit $?
369 git ls-tree
"$commit" -- "$dir" |
370 while read mode
type tree name
; do
371 assert
[ "$name" = "$dir" ]
372 assert
[ "$type" = "tree" ]
382 if [ $# -ne 1 ]; then
383 return 0 # weird parents, consider it changed
385 ptree
=$
(toptree_for_commit
$1)
386 if [ "$ptree" != "$tree" ]; then
389 return 1 # not changed
399 tree
=$
(toptree_for_commit
$newsub) ||
exit $?
400 if [ -n "$old" ]; then
401 squash_msg
"$dir" "$oldsub" "$newsub" |
402 git commit-tree
"$tree" -p "$old" ||
exit $?
404 squash_msg
"$dir" "" "$newsub" |
405 git commit-tree
"$tree" ||
exit $?
414 assert
[ -n "$tree" ]
420 for parent
in $newparents; do
421 ptree
=$
(toptree_for_commit
$parent) ||
exit $?
422 [ -z "$ptree" ] && continue
423 if [ "$ptree" = "$tree" ]; then
424 # an identical parent could be used in place of this rev.
427 nonidentical
="$parent"
430 # sometimes both old parents map to the same newparent;
431 # eliminate duplicates
433 for gp
in $gotparents; do
434 if [ "$gp" = "$parent" ]; then
439 if [ -n "$is_new" ]; then
440 gotparents
="$gotparents $parent"
445 if [ -n "$identical" ]; then
448 copy_commit
$rev $tree "$p" ||
exit $?
454 if ! git diff-index HEAD
--exit-code --quiet 2>&1; then
455 die
"Working tree has modifications. Cannot add."
457 if ! git diff-index
--cached HEAD
--exit-code --quiet 2>&1; then
458 die
"Index has modifications. Cannot add."
464 if [ -e "$dir" ]; then
465 die
"'$dir' already exists. Cannot add."
470 if [ $# -eq 1 ]; then
471 "cmd_add_commit" "$@"
472 elif [ $# -eq 2 ]; then
473 "cmd_add_repository" "$@"
475 say
"error: parameters were '$@'"
476 die
"Provide either a refspec or a repository and refspec."
482 echo "git fetch" "$@"
485 git fetch
"$@" ||
exit $?
493 revs
=$
(git rev-parse
$default --revs-only "$@") ||
exit $?
497 debug
"Adding $dir as '$rev'..."
498 git read-tree
--prefix="$dir" $rev ||
exit $?
499 git checkout
-- "$dir" ||
exit $?
500 tree
=$
(git write-tree
) ||
exit $?
502 headrev
=$
(git rev-parse HEAD
) ||
exit $?
503 if [ -n "$headrev" -a "$headrev" != "$rev" ]; then
509 if [ -n "$squash" ]; then
510 rev=$
(new_squash_commit
"" "" "$rev") ||
exit $?
511 commit
=$
(add_squashed_msg
"$rev" "$dir" |
512 git commit-tree
$tree $headp -p "$rev") ||
exit $?
514 commit
=$
(add_msg
"$dir" "$headrev" "$rev" |
515 git commit-tree
$tree $headp -p "$rev") ||
exit $?
517 git
reset "$commit" ||
exit $?
519 say
"Added dir '$dir'"
524 debug
"Splitting $dir..."
525 cache_setup ||
exit $?
527 if [ -n "$onto" ]; then
528 debug
"Reading history for --onto=$onto..."
531 # the 'onto' history is already just the subdir, so
532 # any parent we find there can be used verbatim
538 if [ -n "$ignore_joins" ]; then
541 unrevs
="$(find_existing_splits "$dir" "$revs")"
544 # We can't restrict rev-list to only $dir here, because some of our
545 # parents have the $dir contents the root, and those won't match.
546 # (and rev-list --follow doesn't seem to solve this)
547 grl
='git rev-list --reverse --parents $revs $unrevs'
548 revmax
=$
(eval "$grl" |
wc -l)
552 while read rev parents
; do
553 revcount
=$
(($revcount + 1))
554 say
-n "$revcount/$revmax ($createcount)
"
555 debug
"Processing commit: $rev"
556 exists
=$
(cache_get
$rev)
557 if [ -n "$exists" ]; then
558 debug
" prior: $exists"
561 createcount
=$
(($createcount + 1))
562 debug
" parents: $parents"
563 newparents
=$
(cache_get
$parents)
564 debug
" newparents: $newparents"
566 tree
=$
(subtree_for_commit
$rev "$dir")
567 debug
" tree is: $tree"
569 # ugly. is there no better way to tell if this is a subtree
570 # vs. a mainline commit? Does it matter?
571 if [ -z $tree ]; then
576 newrev
=$
(copy_or_skip
"$rev" "$tree" "$newparents") ||
exit $?
577 debug
" newrev is: $newrev"
578 cache_set
$rev $newrev
579 cache_set latest_new
$newrev
580 cache_set latest_old
$rev
582 latest_new
=$
(cache_get latest_new
)
583 if [ -z "$latest_new" ]; then
584 die
"No new revisions were found"
587 if [ -n "$rejoin" ]; then
588 debug
"Merging split branch into HEAD..."
589 latest_old
=$
(cache_get latest_old
)
591 -m "$(rejoin_msg $dir $latest_old $latest_new)" \
592 $latest_new >&2 ||
exit $?
594 if [ -n "$branch" ]; then
595 if rev_exists
"refs/heads/$branch"; then
596 if ! rev_is_descendant_of_branch
$latest_new $branch; then
597 die
"Branch '$branch' is not an ancestor of commit '$latest_new'."
603 git update-ref
-m 'subtree split' "refs/heads/$branch" $latest_new ||
exit $?
604 say
"$action branch '$branch'"
612 revs
=$
(git rev-parse
$default --revs-only "$@") ||
exit $?
616 if [ $# -ne 1 ]; then
617 die
"You must provide exactly one revision. Got: '$revs'"
621 if [ -n "$squash" ]; then
622 first_split
="$(find_latest_squash "$dir")"
623 if [ -z "$first_split" ]; then
624 die
"Can't squash-merge: '$dir' was never added."
629 if [ "$sub" = "$rev" ]; then
630 say
"Subtree is already at commit $rev."
633 new
=$
(new_squash_commit
"$old" "$sub" "$rev") ||
exit $?
634 debug
"New squash commit: $new"
638 version
=$
(git version
)
639 if [ "$version" \
< "git version 1.7" ]; then
640 if [ -n "$message" ]; then
641 git merge
-s subtree
--message="$message" $rev
643 git merge
-s subtree
$rev
646 if [ -n "$message" ]; then
647 git merge
-Xsubtree="$prefix" --message="$message" $rev
649 git merge
-Xsubtree="$prefix" $rev
657 git fetch
"$@" ||
exit $?
665 if [ $# -ne 2 ]; then
666 die
"You must provide <repository> <refspec>"
668 if [ -e "$dir" ]; then
671 echo "git push using: " $repository $refspec
672 git push
$repository $
(git subtree
split --prefix=$prefix):refs
/heads
/$refspec
674 die
"'$dir' must already exist. Try 'git subtree add'."