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