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 $?
)
34 PATH
=$
(git
--exec-path):$PATH
36 PATH
=$OPATH # apparently needed for some versions of msysgit
53 if [ -n "$debug" ]; then
60 if [ -z "$quiet" ]; then
70 die
"assertion failed: " "$@"
77 while [ $# -gt 0 ]; do
83 --annotate) annotate
="$1"; shift ;;
84 --no-annotate) annotate
= ;;
85 -b) branch
="$1"; shift ;;
86 -P) prefix
="$1"; shift ;;
87 -m) message
="$1"; shift ;;
88 --no-prefix) prefix
= ;;
89 --onto) onto
="$1"; shift ;;
92 --no-rejoin) rejoin
= ;;
93 --ignore-joins) ignore_joins
=1 ;;
94 --no-ignore-joins) ignore_joins
= ;;
96 --no-squash) squash
= ;;
98 *) die
"Unexpected option: $opt" ;;
105 add|merge|pull
) default
= ;;
106 split|push
) default
="--default HEAD" ;;
107 *) die
"Unknown command '$command'" ;;
110 if [ -z "$prefix" ]; then
111 die
"You must provide the --prefix option."
115 add
) [ -e "$prefix" ] &&
116 die
"prefix '$prefix' already exists." ;;
117 *) [ -e "$prefix" ] ||
118 die
"'$prefix' does not exist; use 'git subtree add'" ;;
121 dir
="$(dirname "$prefix/.
")"
123 if [ "$command" != "pull" -a "$command" != "add" -a "$command" != "push" ]; then
124 revs
=$
(git rev-parse
$default --revs-only "$@") ||
exit $?
125 dirs="$(git rev-parse --no-revs --no-flags "$@
")" ||
exit $?
126 if [ -n "$dirs" ]; then
127 die
"Error: Use --prefix instead of bare filenames."
131 debug
"command: {$command}"
132 debug
"quiet: {$quiet}"
133 debug
"revs: {$revs}"
140 cachedir
="$GIT_DIR/subtree-cache/$$"
141 rm -rf "$cachedir" || die
"Can't delete old cachedir: $cachedir"
142 mkdir
-p "$cachedir" || die
"Can't create new cachedir: $cachedir"
143 debug
"Using cachedir: $cachedir" >&2
149 if [ -r "$cachedir/$oldrev" ]; then
150 read newrev
<"$cachedir/$oldrev"
160 if [ "$oldrev" != "latest_old" \
161 -a "$oldrev" != "latest_new" \
162 -a -e "$cachedir/$oldrev" ]; then
163 die
"cache for $oldrev already exists!"
165 echo "$newrev" >"$cachedir/$oldrev"
170 if git rev-parse
"$1" >/dev
/null
2>&1; then
177 rev_is_descendant_of_branch
()
181 branch_hash
=$
(git rev-parse
$branch)
182 match
=$
(git rev-list
-1 $branch_hash ^
$newrev)
184 if [ -z "$match" ]; then
191 # if a commit doesn't have a parent, this might not work. But we only want
192 # to remove the parent from the rev-list, and since it doesn't exist, it won't
193 # be there anyway, so do nothing in that case.
194 try_remove_previous
()
196 if rev_exists
"$1^"; then
203 debug
"Looking for latest squash ($dir)..."
208 git log
--grep="^git-subtree-dir: $dir/*\$" \
209 --pretty=format
:'START %H%n%s%n%n%b%nEND%n' HEAD |
210 while read a b junk
; do
212 debug
"{{$sq/$main/$sub}}"
215 git-subtree-mainline
:) main
="$b" ;;
216 git-subtree-split
:) sub
="$b" ;;
218 if [ -n "$sub" ]; then
219 if [ -n "$main" ]; then
221 # Pretend its sub was a squash.
224 debug
"Squash found: $sq $sub"
236 find_existing_splits
()
238 debug
"Looking for prior splits..."
243 git log
--grep="^git-subtree-dir: $dir/*\$" \
244 --pretty=format
:'START %H%n%s%n%n%b%nEND%n' $revs |
245 while read a b junk
; do
248 git-subtree-mainline
:) main
="$b" ;;
249 git-subtree-split
:) sub
="$b" ;;
251 debug
" Main is: '$main'"
252 if [ -z "$main" -a -n "$sub" ]; then
253 # squash commits refer to a subtree
254 debug
" Squash: $sq from $sub"
255 cache_set
"$sq" "$sub"
257 if [ -n "$main" -a -n "$sub" ]; then
258 debug
" Prior: $main -> $sub"
261 try_remove_previous
"$main"
262 try_remove_previous
"$sub"
273 # We're going to set some environment vars here, so
274 # do it in a subshell to get rid of them safely later
275 debug copy_commit
"{$1}" "{$2}" "{$3}"
276 git log
-1 --pretty=format
:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" |
279 read GIT_AUTHOR_EMAIL
281 read GIT_COMMITTER_NAME
282 read GIT_COMMITTER_EMAIL
283 read GIT_COMMITTER_DATE
284 export GIT_AUTHOR_NAME \
288 GIT_COMMITTER_EMAIL \
290 (echo -n "$annotate"; cat ) |
291 git commit-tree
"$2" $3 # reads the rest of stdin
292 ) || die
"Can't copy commit $1"
300 if [ -n "$message" ]; then
301 commit_message
="$message"
303 commit_message
="Add '$dir/' from commit '$latest_new'"
308 git-subtree-dir: $dir
309 git-subtree-mainline: $latest_old
310 git-subtree-split: $latest_new
316 if [ -n "$message" ]; then
319 echo "Merge commit '$1' as '$2'"
328 if [ -n "$message" ]; then
329 commit_message
="$message"
331 commit_message
="Split '$dir/' into commit '$latest_new'"
336 git-subtree-dir: $dir
337 git-subtree-mainline: $latest_old
338 git-subtree-split: $latest_new
347 newsub_short
=$
(git rev-parse
--short "$newsub")
349 if [ -n "$oldsub" ]; then
350 oldsub_short
=$
(git rev-parse
--short "$oldsub")
351 echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short"
353 git log
--pretty=tformat
:'%h %s' "$oldsub..$newsub"
354 git log
--pretty=tformat
:'REVERT: %h %s' "$newsub..$oldsub"
356 echo "Squashed '$dir/' content from commit $newsub_short"
360 echo "git-subtree-dir: $dir"
361 echo "git-subtree-split: $newsub"
367 git log
-1 --pretty=format
:'%T' "$commit" -- ||
exit $?
374 git ls-tree
"$commit" -- "$dir" |
375 while read mode
type tree name
; do
376 assert
[ "$name" = "$dir" ]
377 assert
[ "$type" = "tree" ]
387 if [ $# -ne 1 ]; then
388 return 0 # weird parents, consider it changed
390 ptree
=$
(toptree_for_commit
$1)
391 if [ "$ptree" != "$tree" ]; then
394 return 1 # not changed
404 tree
=$
(toptree_for_commit
$newsub) ||
exit $?
405 if [ -n "$old" ]; then
406 squash_msg
"$dir" "$oldsub" "$newsub" |
407 git commit-tree
"$tree" -p "$old" ||
exit $?
409 squash_msg
"$dir" "" "$newsub" |
410 git commit-tree
"$tree" ||
exit $?
419 assert
[ -n "$tree" ]
425 for parent
in $newparents; do
426 ptree
=$
(toptree_for_commit
$parent) ||
exit $?
427 [ -z "$ptree" ] && continue
428 if [ "$ptree" = "$tree" ]; then
429 # an identical parent could be used in place of this rev.
432 nonidentical
="$parent"
435 # sometimes both old parents map to the same newparent;
436 # eliminate duplicates
438 for gp
in $gotparents; do
439 if [ "$gp" = "$parent" ]; then
444 if [ -n "$is_new" ]; then
445 gotparents
="$gotparents $parent"
450 if [ -n "$identical" ]; then
453 copy_commit
$rev $tree "$p" ||
exit $?
459 if ! git diff-index HEAD
--exit-code --quiet 2>&1; then
460 die
"Working tree has modifications. Cannot add."
462 if ! git diff-index
--cached HEAD
--exit-code --quiet 2>&1; then
463 die
"Index has modifications. Cannot add."
469 if [ -e "$dir" ]; then
470 die
"'$dir' already exists. Cannot add."
475 if [ $# -eq 1 ]; then
476 "cmd_add_commit" "$@"
477 elif [ $# -eq 2 ]; then
478 "cmd_add_repository" "$@"
480 say
"error: parameters were '$@'"
481 die
"Provide either a refspec or a repository and refspec."
487 echo "git fetch" "$@"
490 git fetch
"$@" ||
exit $?
498 revs
=$
(git rev-parse
$default --revs-only "$@") ||
exit $?
502 debug
"Adding $dir as '$rev'..."
503 git read-tree
--prefix="$dir" $rev ||
exit $?
504 git checkout
-- "$dir" ||
exit $?
505 tree
=$
(git write-tree
) ||
exit $?
507 headrev
=$
(git rev-parse HEAD
) ||
exit $?
508 if [ -n "$headrev" -a "$headrev" != "$rev" ]; then
514 if [ -n "$squash" ]; then
515 rev=$
(new_squash_commit
"" "" "$rev") ||
exit $?
516 commit
=$
(add_squashed_msg
"$rev" "$dir" |
517 git commit-tree
$tree $headp -p "$rev") ||
exit $?
519 commit
=$
(add_msg
"$dir" "$headrev" "$rev" |
520 git commit-tree
$tree $headp -p "$rev") ||
exit $?
522 git
reset "$commit" ||
exit $?
524 say
"Added dir '$dir'"
529 debug
"Splitting $dir..."
530 cache_setup ||
exit $?
532 if [ -n "$onto" ]; then
533 debug
"Reading history for --onto=$onto..."
536 # the 'onto' history is already just the subdir, so
537 # any parent we find there can be used verbatim
543 if [ -n "$ignore_joins" ]; then
546 unrevs
="$(find_existing_splits "$dir" "$revs")"
549 # We can't restrict rev-list to only $dir here, because some of our
550 # parents have the $dir contents the root, and those won't match.
551 # (and rev-list --follow doesn't seem to solve this)
552 grl
='git rev-list --reverse --parents $revs $unrevs'
553 revmax
=$
(eval "$grl" |
wc -l)
557 while read rev parents
; do
558 revcount
=$
(($revcount + 1))
559 say
-n "$revcount/$revmax ($createcount)
"
560 debug
"Processing commit: $rev"
561 exists
=$
(cache_get
$rev)
562 if [ -n "$exists" ]; then
563 debug
" prior: $exists"
566 createcount
=$
(($createcount + 1))
567 debug
" parents: $parents"
568 newparents
=$
(cache_get
$parents)
569 debug
" newparents: $newparents"
571 tree
=$
(subtree_for_commit
$rev "$dir")
572 debug
" tree is: $tree"
574 # ugly. is there no better way to tell if this is a subtree
575 # vs. a mainline commit? Does it matter?
576 if [ -z $tree ]; then
577 if [ -n "$newparents" ]; then
583 newrev
=$
(copy_or_skip
"$rev" "$tree" "$newparents") ||
exit $?
584 debug
" newrev is: $newrev"
585 cache_set
$rev $newrev
586 cache_set latest_new
$newrev
587 cache_set latest_old
$rev
589 latest_new
=$
(cache_get latest_new
)
590 if [ -z "$latest_new" ]; then
591 die
"No new revisions were found"
594 if [ -n "$rejoin" ]; then
595 debug
"Merging split branch into HEAD..."
596 latest_old
=$
(cache_get latest_old
)
598 -m "$(rejoin_msg $dir $latest_old $latest_new)" \
599 $latest_new >&2 ||
exit $?
601 if [ -n "$branch" ]; then
602 if rev_exists
"refs/heads/$branch"; then
603 if ! rev_is_descendant_of_branch
$latest_new $branch; then
604 die
"Branch '$branch' is not an ancestor of commit '$latest_new'."
610 git update-ref
-m 'subtree split' "refs/heads/$branch" $latest_new ||
exit $?
611 say
"$action branch '$branch'"
619 revs
=$
(git rev-parse
$default --revs-only "$@") ||
exit $?
623 if [ $# -ne 1 ]; then
624 die
"You must provide exactly one revision. Got: '$revs'"
628 if [ -n "$squash" ]; then
629 first_split
="$(find_latest_squash "$dir")"
630 if [ -z "$first_split" ]; then
631 die
"Can't squash-merge: '$dir' was never added."
636 if [ "$sub" = "$rev" ]; then
637 say
"Subtree is already at commit $rev."
640 new
=$
(new_squash_commit
"$old" "$sub" "$rev") ||
exit $?
641 debug
"New squash commit: $new"
645 version
=$
(git version
)
646 if [ "$version" \
< "git version 1.7" ]; then
647 if [ -n "$message" ]; then
648 git merge
-s subtree
--message="$message" $rev
650 git merge
-s subtree
$rev
653 if [ -n "$message" ]; then
654 git merge
-Xsubtree="$prefix" --message="$message" $rev
656 git merge
-Xsubtree="$prefix" $rev
664 git fetch
"$@" ||
exit $?
672 if [ $# -ne 2 ]; then
673 die
"You must provide <repository> <refspec>"
675 if [ -e "$dir" ]; then
678 echo "git push using: " $repository $refspec
679 git push
$repository $
(git subtree
split --prefix=$prefix):refs
/heads
/$refspec
681 die
"'$dir' must already exist. Try 'git subtree add'."