Merge branch 'js/diff-highlight-avoid-sigpipe'
[git] / contrib / subtree / git-subtree.sh
1 #!/bin/sh
2 #
3 # git-subtree.sh: split/join git repositories in subdirectories of this one
4 #
5 # Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>
6 #
7 if [ $# -eq 0 ]; then
8     set -- -h
9 fi
10 OPTS_SPEC="\
11 git subtree add   --prefix=<prefix> <commit>
12 git subtree add   --prefix=<prefix> <repository> <ref>
13 git subtree merge --prefix=<prefix> <commit>
14 git subtree pull  --prefix=<prefix> <repository> <ref>
15 git subtree push  --prefix=<prefix> <repository> <ref>
16 git subtree split --prefix=<prefix> <commit...>
17 --
18 h,help        show the help
19 q             quiet
20 d             show debug messages
21 P,prefix=     the name of the subdir to split out
22 m,message=    use the given message as the commit message for the merge commit
23  options for 'split'
24 annotate=     add a prefix to commit message of new commits
25 b,branch=     create a new branch from the split subtree
26 ignore-joins  ignore prior --rejoin commits
27 onto=         try connecting new tree to an existing one
28 rejoin        merge the new branch back into HEAD
29  options for 'add', 'merge', 'pull' and 'push'
30 squash        merge subtree changes as a single commit
31 "
32 eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
33
34 PATH=$PATH:$(git --exec-path)
35 . git-sh-setup
36
37 require_work_tree
38
39 quiet=
40 branch=
41 debug=
42 command=
43 onto=
44 rejoin=
45 ignore_joins=
46 annotate=
47 squash=
48 message=
49 prefix=
50
51 debug()
52 {
53         if [ -n "$debug" ]; then
54                 echo "$@" >&2
55         fi
56 }
57
58 say()
59 {
60         if [ -z "$quiet" ]; then
61                 echo "$@" >&2
62         fi
63 }
64
65 assert()
66 {
67         if "$@"; then
68                 :
69         else
70                 die "assertion failed: " "$@"
71         fi
72 }
73
74
75 #echo "Options: $*"
76
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
101
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
109
110 if [ -z "$prefix" ]; then
111         die "You must provide the --prefix option."
112 fi
113
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
120
121 dir="$(dirname "$prefix/.")"
122
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."
128         fi
129 fi
130
131 debug "command: {$command}"
132 debug "quiet: {$quiet}"
133 debug "revs: {$revs}"
134 debug "dir: {$dir}"
135 debug "opts: {$*}"
136 debug
137
138 cache_setup()
139 {
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         mkdir -p "$cachedir/notree" || die "Can't create new cachedir: $cachedir/notree"
144         debug "Using cachedir: $cachedir" >&2
145 }
146
147 cache_get()
148 {
149         for oldrev in $*; do
150                 if [ -r "$cachedir/$oldrev" ]; then
151                         read newrev <"$cachedir/$oldrev"
152                         echo $newrev
153                 fi
154         done
155 }
156
157 cache_miss()
158 {
159         for oldrev in $*; do
160                 if [ ! -r "$cachedir/$oldrev" ]; then
161                         echo $oldrev
162                 fi
163         done
164 }
165
166 check_parents()
167 {
168         missed=$(cache_miss $*)
169         for miss in $missed; do
170                 if [ ! -r "$cachedir/notree/$miss" ]; then
171                         debug "  incorrect order: $miss"
172                 fi
173         done
174 }
175
176 set_notree()
177 {
178         echo "1" > "$cachedir/notree/$1"
179 }
180
181 cache_set()
182 {
183         oldrev="$1"
184         newrev="$2"
185         if [ "$oldrev" != "latest_old" \
186              -a "$oldrev" != "latest_new" \
187              -a -e "$cachedir/$oldrev" ]; then
188                 die "cache for $oldrev already exists!"
189         fi
190         echo "$newrev" >"$cachedir/$oldrev"
191 }
192
193 rev_exists()
194 {
195         if git rev-parse "$1" >/dev/null 2>&1; then
196                 return 0
197         else
198                 return 1
199         fi
200 }
201
202 rev_is_descendant_of_branch()
203 {
204         newrev="$1"
205         branch="$2"
206         branch_hash=$(git rev-parse $branch)
207         match=$(git rev-list -1 $branch_hash ^$newrev)
208
209         if [ -z "$match" ]; then
210                 return 0
211         else
212                 return 1
213         fi
214 }
215
216 # if a commit doesn't have a parent, this might not work.  But we only want
217 # to remove the parent from the rev-list, and since it doesn't exist, it won't
218 # be there anyway, so do nothing in that case.
219 try_remove_previous()
220 {
221         if rev_exists "$1^"; then
222                 echo "^$1^"
223         fi
224 }
225
226 find_latest_squash()
227 {
228         debug "Looking for latest squash ($dir)..."
229         dir="$1"
230         sq=
231         main=
232         sub=
233         git log --grep="^git-subtree-dir: $dir/*\$" \
234                 --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD |
235         while read a b junk; do
236                 debug "$a $b $junk"
237                 debug "{{$sq/$main/$sub}}"
238                 case "$a" in
239                         START) sq="$b" ;;
240                         git-subtree-mainline:) main="$b" ;;
241                         git-subtree-split:) sub="$b" ;;
242                         END)
243                                 if [ -n "$sub" ]; then
244                                         if [ -n "$main" ]; then
245                                                 # a rejoin commit?
246                                                 # Pretend its sub was a squash.
247                                                 sq="$sub"
248                                         fi
249                                         debug "Squash found: $sq $sub"
250                                         echo "$sq" "$sub"
251                                         break
252                                 fi
253                                 sq=
254                                 main=
255                                 sub=
256                                 ;;
257                 esac
258         done
259 }
260
261 find_existing_splits()
262 {
263         debug "Looking for prior splits..."
264         dir="$1"
265         revs="$2"
266         main=
267         sub=
268         git log --grep="^git-subtree-dir: $dir/*\$" \
269                 --pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs |
270         while read a b junk; do
271                 case "$a" in
272                         START) sq="$b" ;;
273                         git-subtree-mainline:) main="$b" ;;
274                         git-subtree-split:) sub="$b" ;;
275                         END)
276                                 debug "  Main is: '$main'"
277                                 if [ -z "$main" -a -n "$sub" ]; then
278                                         # squash commits refer to a subtree
279                                         debug "  Squash: $sq from $sub"
280                                         cache_set "$sq" "$sub"
281                                 fi
282                                 if [ -n "$main" -a -n "$sub" ]; then
283                                         debug "  Prior: $main -> $sub"
284                                         cache_set $main $sub
285                                         cache_set $sub $sub
286                                         try_remove_previous "$main"
287                                         try_remove_previous "$sub"
288                                 fi
289                                 main=
290                                 sub=
291                                 ;;
292                 esac
293         done
294 }
295
296 copy_commit()
297 {
298         # We're going to set some environment vars here, so
299         # do it in a subshell to get rid of them safely later
300         debug copy_commit "{$1}" "{$2}" "{$3}"
301         git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%B' "$1" |
302         (
303                 read GIT_AUTHOR_NAME
304                 read GIT_AUTHOR_EMAIL
305                 read GIT_AUTHOR_DATE
306                 read GIT_COMMITTER_NAME
307                 read GIT_COMMITTER_EMAIL
308                 read GIT_COMMITTER_DATE
309                 export  GIT_AUTHOR_NAME \
310                         GIT_AUTHOR_EMAIL \
311                         GIT_AUTHOR_DATE \
312                         GIT_COMMITTER_NAME \
313                         GIT_COMMITTER_EMAIL \
314                         GIT_COMMITTER_DATE
315                 (printf "%s" "$annotate"; cat ) |
316                 git commit-tree "$2" $3  # reads the rest of stdin
317         ) || die "Can't copy commit $1"
318 }
319
320 add_msg()
321 {
322         dir="$1"
323         latest_old="$2"
324         latest_new="$3"
325         if [ -n "$message" ]; then
326                 commit_message="$message"
327         else
328                 commit_message="Add '$dir/' from commit '$latest_new'"
329         fi
330         cat <<-EOF
331                 $commit_message
332                 
333                 git-subtree-dir: $dir
334                 git-subtree-mainline: $latest_old
335                 git-subtree-split: $latest_new
336         EOF
337 }
338
339 add_squashed_msg()
340 {
341         if [ -n "$message" ]; then
342                 echo "$message"
343         else
344                 echo "Merge commit '$1' as '$2'"
345         fi
346 }
347
348 rejoin_msg()
349 {
350         dir="$1"
351         latest_old="$2"
352         latest_new="$3"
353         if [ -n "$message" ]; then
354                 commit_message="$message"
355         else
356                 commit_message="Split '$dir/' into commit '$latest_new'"
357         fi
358         cat <<-EOF
359                 $commit_message
360                 
361                 git-subtree-dir: $dir
362                 git-subtree-mainline: $latest_old
363                 git-subtree-split: $latest_new
364         EOF
365 }
366
367 squash_msg()
368 {
369         dir="$1"
370         oldsub="$2"
371         newsub="$3"
372         newsub_short=$(git rev-parse --short "$newsub")
373         
374         if [ -n "$oldsub" ]; then
375                 oldsub_short=$(git rev-parse --short "$oldsub")
376                 echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short"
377                 echo
378                 git log --pretty=tformat:'%h %s' "$oldsub..$newsub"
379                 git log --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub"
380         else
381                 echo "Squashed '$dir/' content from commit $newsub_short"
382         fi
383         
384         echo
385         echo "git-subtree-dir: $dir"
386         echo "git-subtree-split: $newsub"
387 }
388
389 toptree_for_commit()
390 {
391         commit="$1"
392         git log -1 --pretty=format:'%T' "$commit" -- || exit $?
393 }
394
395 subtree_for_commit()
396 {
397         commit="$1"
398         dir="$2"
399         git ls-tree "$commit" -- "$dir" |
400         while read mode type tree name; do
401                 assert [ "$name" = "$dir" ]
402                 assert [ "$type" = "tree" -o "$type" = "commit" ]
403                 [ "$type" = "commit" ] && continue  # ignore submodules
404                 echo $tree
405                 break
406         done
407 }
408
409 tree_changed()
410 {
411         tree=$1
412         shift
413         if [ $# -ne 1 ]; then
414                 return 0   # weird parents, consider it changed
415         else
416                 ptree=$(toptree_for_commit $1)
417                 if [ "$ptree" != "$tree" ]; then
418                         return 0   # changed
419                 else
420                         return 1   # not changed
421                 fi
422         fi
423 }
424
425 new_squash_commit()
426 {
427         old="$1"
428         oldsub="$2"
429         newsub="$3"
430         tree=$(toptree_for_commit $newsub) || exit $?
431         if [ -n "$old" ]; then
432                 squash_msg "$dir" "$oldsub" "$newsub" | 
433                         git commit-tree "$tree" -p "$old" || exit $?
434         else
435                 squash_msg "$dir" "" "$newsub" |
436                         git commit-tree "$tree" || exit $?
437         fi
438 }
439
440 copy_or_skip()
441 {
442         rev="$1"
443         tree="$2"
444         newparents="$3"
445         assert [ -n "$tree" ]
446
447         identical=
448         nonidentical=
449         p=
450         gotparents=
451         for parent in $newparents; do
452                 ptree=$(toptree_for_commit $parent) || exit $?
453                 [ -z "$ptree" ] && continue
454                 if [ "$ptree" = "$tree" ]; then
455                         # an identical parent could be used in place of this rev.
456                         identical="$parent"
457                 else
458                         nonidentical="$parent"
459                 fi
460                 
461                 # sometimes both old parents map to the same newparent;
462                 # eliminate duplicates
463                 is_new=1
464                 for gp in $gotparents; do
465                         if [ "$gp" = "$parent" ]; then
466                                 is_new=
467                                 break
468                         fi
469                 done
470                 if [ -n "$is_new" ]; then
471                         gotparents="$gotparents $parent"
472                         p="$p -p $parent"
473                 fi
474         done
475         
476         if [ -n "$identical" ]; then
477                 echo $identical
478         else
479                 copy_commit $rev $tree "$p" || exit $?
480         fi
481 }
482
483 ensure_clean()
484 {
485         if ! git diff-index HEAD --exit-code --quiet 2>&1; then
486                 die "Working tree has modifications.  Cannot add."
487         fi
488         if ! git diff-index --cached HEAD --exit-code --quiet 2>&1; then
489                 die "Index has modifications.  Cannot add."
490         fi
491 }
492
493 ensure_valid_ref_format()
494 {
495         git check-ref-format "refs/heads/$1" ||
496             die "'$1' does not look like a ref"
497 }
498
499 cmd_add()
500 {
501         if [ -e "$dir" ]; then
502                 die "'$dir' already exists.  Cannot add."
503         fi
504
505         ensure_clean
506         
507         if [ $# -eq 1 ]; then
508             git rev-parse -q --verify "$1^{commit}" >/dev/null ||
509             die "'$1' does not refer to a commit"
510
511             "cmd_add_commit" "$@"
512         elif [ $# -eq 2 ]; then
513             # Technically we could accept a refspec here but we're
514             # just going to turn around and add FETCH_HEAD under the
515             # specified directory.  Allowing a refspec might be
516             # misleading because we won't do anything with any other
517             # branches fetched via the refspec.
518             ensure_valid_ref_format "$2"
519
520             "cmd_add_repository" "$@"
521         else
522             say "error: parameters were '$@'"
523             die "Provide either a commit or a repository and commit."
524         fi
525 }
526
527 cmd_add_repository()
528 {
529         echo "git fetch" "$@"
530         repository=$1
531         refspec=$2
532         git fetch "$@" || exit $?
533         revs=FETCH_HEAD
534         set -- $revs
535         cmd_add_commit "$@"
536 }
537
538 cmd_add_commit()
539 {
540         revs=$(git rev-parse $default --revs-only "$@") || exit $?
541         set -- $revs
542         rev="$1"
543         
544         debug "Adding $dir as '$rev'..."
545         git read-tree --prefix="$dir" $rev || exit $?
546         git checkout -- "$dir" || exit $?
547         tree=$(git write-tree) || exit $?
548         
549         headrev=$(git rev-parse HEAD) || exit $?
550         if [ -n "$headrev" -a "$headrev" != "$rev" ]; then
551                 headp="-p $headrev"
552         else
553                 headp=
554         fi
555         
556         if [ -n "$squash" ]; then
557                 rev=$(new_squash_commit "" "" "$rev") || exit $?
558                 commit=$(add_squashed_msg "$rev" "$dir" |
559                          git commit-tree $tree $headp -p "$rev") || exit $?
560         else
561                 revp=$(peel_committish "$rev") &&
562                 commit=$(add_msg "$dir" "$headrev" "$rev" |
563                          git commit-tree $tree $headp -p "$revp") || exit $?
564         fi
565         git reset "$commit" || exit $?
566         
567         say "Added dir '$dir'"
568 }
569
570 cmd_split()
571 {
572         debug "Splitting $dir..."
573         cache_setup || exit $?
574         
575         if [ -n "$onto" ]; then
576                 debug "Reading history for --onto=$onto..."
577                 git rev-list $onto |
578                 while read rev; do
579                         # the 'onto' history is already just the subdir, so
580                         # any parent we find there can be used verbatim
581                         debug "  cache: $rev"
582                         cache_set $rev $rev
583                 done
584         fi
585         
586         if [ -n "$ignore_joins" ]; then
587                 unrevs=
588         else
589                 unrevs="$(find_existing_splits "$dir" "$revs")"
590         fi
591         
592         # We can't restrict rev-list to only $dir here, because some of our
593         # parents have the $dir contents the root, and those won't match.
594         # (and rev-list --follow doesn't seem to solve this)
595         grl='git rev-list --topo-order --reverse --parents $revs $unrevs'
596         revmax=$(eval "$grl" | wc -l)
597         revcount=0
598         createcount=0
599         eval "$grl" |
600         while read rev parents; do
601                 revcount=$(($revcount + 1))
602                 say -n "$revcount/$revmax ($createcount)\r"
603                 debug "Processing commit: $rev"
604                 exists=$(cache_get $rev)
605                 if [ -n "$exists" ]; then
606                         debug "  prior: $exists"
607                         continue
608                 fi
609                 createcount=$(($createcount + 1))
610                 debug "  parents: $parents"
611                 newparents=$(cache_get $parents)
612                 debug "  newparents: $newparents"
613                 
614                 tree=$(subtree_for_commit $rev "$dir")
615                 debug "  tree is: $tree"
616
617                 check_parents $parents
618                 
619                 # ugly.  is there no better way to tell if this is a subtree
620                 # vs. a mainline commit?  Does it matter?
621                 if [ -z $tree ]; then
622                         set_notree $rev
623                         if [ -n "$newparents" ]; then
624                                 cache_set $rev $rev
625                         fi
626                         continue
627                 fi
628
629                 newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
630                 debug "  newrev is: $newrev"
631                 cache_set $rev $newrev
632                 cache_set latest_new $newrev
633                 cache_set latest_old $rev
634         done || exit $?
635         latest_new=$(cache_get latest_new)
636         if [ -z "$latest_new" ]; then
637                 die "No new revisions were found"
638         fi
639         
640         if [ -n "$rejoin" ]; then
641                 debug "Merging split branch into HEAD..."
642                 latest_old=$(cache_get latest_old)
643                 git merge -s ours \
644                         -m "$(rejoin_msg $dir $latest_old $latest_new)" \
645                         $latest_new >&2 || exit $?
646         fi
647         if [ -n "$branch" ]; then
648                 if rev_exists "refs/heads/$branch"; then
649                         if ! rev_is_descendant_of_branch $latest_new $branch; then
650                                 die "Branch '$branch' is not an ancestor of commit '$latest_new'."
651                         fi
652                         action='Updated'
653                 else
654                         action='Created'
655                 fi
656                 git update-ref -m 'subtree split' "refs/heads/$branch" $latest_new || exit $?
657                 say "$action branch '$branch'"
658         fi
659         echo $latest_new
660         exit 0
661 }
662
663 cmd_merge()
664 {
665         revs=$(git rev-parse $default --revs-only "$@") || exit $?
666         ensure_clean
667         
668         set -- $revs
669         if [ $# -ne 1 ]; then
670                 die "You must provide exactly one revision.  Got: '$revs'"
671         fi
672         rev="$1"
673         
674         if [ -n "$squash" ]; then
675                 first_split="$(find_latest_squash "$dir")"
676                 if [ -z "$first_split" ]; then
677                         die "Can't squash-merge: '$dir' was never added."
678                 fi
679                 set $first_split
680                 old=$1
681                 sub=$2
682                 if [ "$sub" = "$rev" ]; then
683                         say "Subtree is already at commit $rev."
684                         exit 0
685                 fi
686                 new=$(new_squash_commit "$old" "$sub" "$rev") || exit $?
687                 debug "New squash commit: $new"
688                 rev="$new"
689         fi
690
691         version=$(git version)
692         if [ "$version" \< "git version 1.7" ]; then
693                 if [ -n "$message" ]; then
694                         git merge -s subtree --message="$message" $rev
695                 else
696                         git merge -s subtree $rev
697                 fi
698         else
699                 if [ -n "$message" ]; then
700                         git merge -Xsubtree="$prefix" --message="$message" $rev
701                 else
702                         git merge -Xsubtree="$prefix" $rev
703                 fi
704         fi
705 }
706
707 cmd_pull()
708 {
709         if [ $# -ne 2 ]; then
710             die "You must provide <repository> <ref>"
711         fi
712         ensure_clean
713         ensure_valid_ref_format "$2"
714         git fetch "$@" || exit $?
715         revs=FETCH_HEAD
716         set -- $revs
717         cmd_merge "$@"
718 }
719
720 cmd_push()
721 {
722         if [ $# -ne 2 ]; then
723             die "You must provide <repository> <ref>"
724         fi
725         ensure_valid_ref_format "$2"
726         if [ -e "$dir" ]; then
727             repository=$1
728             refspec=$2
729             echo "git push using: " $repository $refspec
730             localrev=$(git subtree split --prefix="$prefix") || die
731             git push $repository $localrev:refs/heads/$refspec
732         else
733             die "'$dir' must already exist. Try 'git subtree add'."
734         fi
735 }
736
737 "cmd_$command" "$@"