Merge remote branch 'origin/master'
[git-subtree.git] / git-subtree.sh
bloba15d91ffb1b3d3318409619449745dbcfb393abf
1 #!/bin/bash
3 # git-subtree.sh: split/join git repositories in subdirectories of this one
5 # Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>
7 if [ $# -eq 0 ]; then
8 set -- -h
9 fi
10 OPTS_SPEC="\
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...>
17 h,help show the help
18 q quiet
19 d show debug messages
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
22 options for 'split'
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 OPATH=$PATH
34 PATH=$(git --exec-path):$PATH
35 . git-sh-setup
36 PATH=$OPATH # apparently needed for some versions of msysgit
38 require_work_tree
40 quiet=
41 branch=
42 debug=
43 command=
44 onto=
45 rejoin=
46 ignore_joins=
47 annotate=
48 squash=
49 message=
51 debug()
53 if [ -n "$debug" ]; then
54 echo "$@" >&2
58 say()
60 if [ -z "$quiet" ]; then
61 echo "$@" >&2
65 assert()
67 if "$@"; then
69 else
70 die "assertion failed: " "$@"
75 #echo "Options: $*"
77 while [ $# -gt 0 ]; do
78 opt="$1"
79 shift
80 case "$opt" in
81 -q) quiet=1 ;;
82 -d) debug=1 ;;
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 ;;
90 --no-onto) onto= ;;
91 --rejoin) rejoin=1 ;;
92 --no-rejoin) rejoin= ;;
93 --ignore-joins) ignore_joins=1 ;;
94 --no-ignore-joins) ignore_joins= ;;
95 --squash) squash=1 ;;
96 --no-squash) squash= ;;
97 --) break ;;
98 *) die "Unexpected option: $opt" ;;
99 esac
100 done
102 command="$1"
103 shift
104 case "$command" in
105 add|merge|pull) default= ;;
106 split|push) default="--default HEAD" ;;
107 *) die "Unknown command '$command'" ;;
108 esac
110 if [ -z "$prefix" ]; then
111 die "You must provide the --prefix option."
114 case "$command" in
115 add) [ -e "$prefix" ] &&
116 die "prefix '$prefix' already exists." ;;
117 *) [ -e "$prefix" ] ||
118 die "'$prefix' does not exist; use 'git subtree add'" ;;
119 esac
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}"
134 debug "dir: {$dir}"
135 debug "opts: {$*}"
136 debug
138 cache_setup()
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
146 cache_get()
148 for oldrev in $*; do
149 if [ -r "$cachedir/$oldrev" ]; then
150 read newrev <"$cachedir/$oldrev"
151 echo $newrev
153 done
156 cache_set()
158 oldrev="$1"
159 newrev="$2"
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"
168 rev_exists()
170 if git rev-parse "$1" >/dev/null 2>&1; then
171 return 0
172 else
173 return 1
177 rev_is_descendant_of_branch()
179 newrev="$1"
180 branch="$2"
181 branch_hash=$(git rev-parse $branch)
182 match=$(git rev-list -1 $branch_hash ^$newrev)
184 if [ -z "$match" ]; then
185 return 0
186 else
187 return 1
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
197 echo "^$1^"
201 find_latest_squash()
203 debug "Looking for latest squash ($dir)..."
204 dir="$1"
206 main=
207 sub=
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
211 debug "$a $b $junk"
212 debug "{{$sq/$main/$sub}}"
213 case "$a" in
214 START) sq="$b" ;;
215 git-subtree-mainline:) main="$b" ;;
216 git-subtree-split:) sub="$b" ;;
217 END)
218 if [ -n "$sub" ]; then
219 if [ -n "$main" ]; then
220 # a rejoin commit?
221 # Pretend its sub was a squash.
222 sq="$sub"
224 debug "Squash found: $sq $sub"
225 echo "$sq" "$sub"
226 break
229 main=
230 sub=
232 esac
233 done
236 find_existing_splits()
238 debug "Looking for prior splits..."
239 dir="$1"
240 revs="$2"
241 main=
242 sub=
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
246 case "$a" in
247 START) sq="$b" ;;
248 git-subtree-mainline:) main="$b" ;;
249 git-subtree-split:) sub="$b" ;;
250 END)
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"
259 cache_set $main $sub
260 cache_set $sub $sub
261 try_remove_previous "$main"
262 try_remove_previous "$sub"
264 main=
265 sub=
267 esac
268 done
271 copy_commit()
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" |
278 read GIT_AUTHOR_NAME
279 read GIT_AUTHOR_EMAIL
280 read GIT_AUTHOR_DATE
281 read GIT_COMMITTER_NAME
282 read GIT_COMMITTER_EMAIL
283 read GIT_COMMITTER_DATE
284 export GIT_AUTHOR_NAME \
285 GIT_AUTHOR_EMAIL \
286 GIT_AUTHOR_DATE \
287 GIT_COMMITTER_NAME \
288 GIT_COMMITTER_EMAIL \
289 GIT_COMMITTER_DATE
290 (echo -n "$annotate"; cat ) |
291 git commit-tree "$2" $3 # reads the rest of stdin
292 ) || die "Can't copy commit $1"
295 add_msg()
297 dir="$1"
298 latest_old="$2"
299 latest_new="$3"
300 if [ -n "$message" ]; then
301 commit_message="$message"
302 else
303 commit_message="Add '$dir/' from commit '$latest_new'"
305 cat <<-EOF
306 $commit_message
308 git-subtree-dir: $dir
309 git-subtree-mainline: $latest_old
310 git-subtree-split: $latest_new
314 add_squashed_msg()
316 if [ -n "$message" ]; then
317 echo "$message"
318 else
319 echo "Merge commit '$1' as '$2'"
323 rejoin_msg()
325 dir="$1"
326 latest_old="$2"
327 latest_new="$3"
328 if [ -n "$message" ]; then
329 commit_message="$message"
330 else
331 commit_message="Split '$dir/' into commit '$latest_new'"
333 cat <<-EOF
334 $commit_message
336 git-subtree-dir: $dir
337 git-subtree-mainline: $latest_old
338 git-subtree-split: $latest_new
342 squash_msg()
344 dir="$1"
345 oldsub="$2"
346 newsub="$3"
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"
352 echo
353 git log --pretty=tformat:'%h %s' "$oldsub..$newsub"
354 git log --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub"
355 else
356 echo "Squashed '$dir/' content from commit $newsub_short"
359 echo
360 echo "git-subtree-dir: $dir"
361 echo "git-subtree-split: $newsub"
364 toptree_for_commit()
366 commit="$1"
367 git log -1 --pretty=format:'%T' "$commit" -- || exit $?
370 subtree_for_commit()
372 commit="$1"
373 dir="$2"
374 git ls-tree "$commit" -- "$dir" |
375 while read mode type tree name; do
376 assert [ "$name" = "$dir" ]
377 assert [ "$type" = "tree" ]
378 echo $tree
379 break
380 done
383 tree_changed()
385 tree=$1
386 shift
387 if [ $# -ne 1 ]; then
388 return 0 # weird parents, consider it changed
389 else
390 ptree=$(toptree_for_commit $1)
391 if [ "$ptree" != "$tree" ]; then
392 return 0 # changed
393 else
394 return 1 # not changed
399 new_squash_commit()
401 old="$1"
402 oldsub="$2"
403 newsub="$3"
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 $?
408 else
409 squash_msg "$dir" "" "$newsub" |
410 git commit-tree "$tree" || exit $?
414 copy_or_skip()
416 rev="$1"
417 tree="$2"
418 newparents="$3"
419 assert [ -n "$tree" ]
421 identical=
422 nonidentical=
424 gotparents=
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.
430 identical="$parent"
431 else
432 nonidentical="$parent"
435 # sometimes both old parents map to the same newparent;
436 # eliminate duplicates
437 is_new=1
438 for gp in $gotparents; do
439 if [ "$gp" = "$parent" ]; then
440 is_new=
441 break
443 done
444 if [ -n "$is_new" ]; then
445 gotparents="$gotparents $parent"
446 p="$p -p $parent"
448 done
450 if [ -n "$identical" ]; then
451 echo $identical
452 else
453 copy_commit $rev $tree "$p" || exit $?
457 ensure_clean()
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."
467 cmd_add()
469 if [ -e "$dir" ]; then
470 die "'$dir' already exists. Cannot add."
473 ensure_clean
475 if [ $# -eq 1 ]; then
476 "cmd_add_commit" "$@"
477 elif [ $# -eq 2 ]; then
478 "cmd_add_repository" "$@"
479 else
480 say "error: parameters were '$@'"
481 die "Provide either a refspec or a repository and refspec."
485 cmd_add_repository()
487 echo "git fetch" "$@"
488 repository=$1
489 refspec=$2
490 git fetch "$@" || exit $?
491 revs=FETCH_HEAD
492 set -- $revs
493 cmd_add_commit "$@"
496 cmd_add_commit()
498 revs=$(git rev-parse $default --revs-only "$@") || exit $?
499 set -- $revs
500 rev="$1"
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
509 headp="-p $headrev"
510 else
511 headp=
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 $?
518 else
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'"
527 cmd_split()
529 debug "Splitting $dir..."
530 cache_setup || exit $?
532 if [ -n "$onto" ]; then
533 debug "Reading history for --onto=$onto..."
534 git rev-list $onto |
535 while read rev; do
536 # the 'onto' history is already just the subdir, so
537 # any parent we find there can be used verbatim
538 debug " cache: $rev"
539 cache_set $rev $rev
540 done
543 if [ -n "$ignore_joins" ]; then
544 unrevs=
545 else
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)
554 revcount=0
555 createcount=0
556 eval "$grl" |
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"
564 continue
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
578 cache_set $rev $rev
580 continue
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
588 done || exit $?
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)
597 git merge -s ours \
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'."
606 action='Updated'
607 else
608 action='Created'
610 git update-ref -m 'subtree split' "refs/heads/$branch" $latest_new || exit $?
611 say "$action branch '$branch'"
613 echo $latest_new
614 exit 0
617 cmd_merge()
619 revs=$(git rev-parse $default --revs-only "$@") || exit $?
620 ensure_clean
622 set -- $revs
623 if [ $# -ne 1 ]; then
624 die "You must provide exactly one revision. Got: '$revs'"
626 rev="$1"
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."
633 set $first_split
634 old=$1
635 sub=$2
636 if [ "$sub" = "$rev" ]; then
637 say "Subtree is already at commit $rev."
638 exit 0
640 new=$(new_squash_commit "$old" "$sub" "$rev") || exit $?
641 debug "New squash commit: $new"
642 rev="$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
649 else
650 git merge -s subtree $rev
652 else
653 if [ -n "$message" ]; then
654 git merge -Xsubtree="$prefix" --message="$message" $rev
655 else
656 git merge -Xsubtree="$prefix" $rev
661 cmd_pull()
663 ensure_clean
664 git fetch "$@" || exit $?
665 revs=FETCH_HEAD
666 set -- $revs
667 cmd_merge "$@"
670 cmd_push()
672 if [ $# -ne 2 ]; then
673 die "You must provide <repository> <refspec>"
675 if [ -e "$dir" ]; then
676 repository=$1
677 refspec=$2
678 echo "git push using: " $repository $refspec
679 git push $repository $(git subtree split --prefix=$prefix):refs/heads/$refspec
680 else
681 die "'$dir' must already exist. Try 'git subtree add'."
685 "cmd_$command" "$@"