Code

Add 'contrib/subtree/' from commit 'd3a04e06c77d57978bb5230361c64946232cc346'
[git.git] / contrib / subtree / git-subtree.sh
1 #!/bin/bash
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 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...>
16 --
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
30 "
31 eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
33 PATH=$PATH:$(git --exec-path)
34 . git-sh-setup
36 require_work_tree
38 quiet=
39 branch=
40 debug=
41 command=
42 onto=
43 rejoin=
44 ignore_joins=
45 annotate=
46 squash=
47 message=
49 debug()
50 {
51         if [ -n "$debug" ]; then
52                 echo "$@" >&2
53         fi
54 }
56 say()
57 {
58         if [ -z "$quiet" ]; then
59                 echo "$@" >&2
60         fi
61 }
63 assert()
64 {
65         if "$@"; then
66                 :
67         else
68                 die "assertion failed: " "$@"
69         fi
70 }
73 #echo "Options: $*"
75 while [ $# -gt 0 ]; do
76         opt="$1"
77         shift
78         case "$opt" in
79                 -q) quiet=1 ;;
80                 -d) debug=1 ;;
81                 --annotate) annotate="$1"; shift ;;
82                 --no-annotate) annotate= ;;
83                 -b) branch="$1"; shift ;;
84                 -P) prefix="$1"; shift ;;
85                 -m) message="$1"; shift ;;
86                 --no-prefix) prefix= ;;
87                 --onto) onto="$1"; shift ;;
88                 --no-onto) onto= ;;
89                 --rejoin) rejoin=1 ;;
90                 --no-rejoin) rejoin= ;;
91                 --ignore-joins) ignore_joins=1 ;;
92                 --no-ignore-joins) ignore_joins= ;;
93                 --squash) squash=1 ;;
94                 --no-squash) squash= ;;
95                 --) break ;;
96                 *) die "Unexpected option: $opt" ;;
97         esac
98 done
100 command="$1"
101 shift
102 case "$command" in
103         add|merge|pull) default= ;;
104         split|push) default="--default HEAD" ;;
105         *) die "Unknown command '$command'" ;;
106 esac
108 if [ -z "$prefix" ]; then
109         die "You must provide the --prefix option."
110 fi
112 case "$command" in
113         add) [ -e "$prefix" ] && 
114                 die "prefix '$prefix' already exists." ;;
115         *)   [ -e "$prefix" ] || 
116                 die "'$prefix' does not exist; use 'git subtree add'" ;;
117 esac
119 dir="$(dirname "$prefix/.")"
121 if [ "$command" != "pull" -a "$command" != "add" -a "$command" != "push" ]; then
122         revs=$(git rev-parse $default --revs-only "$@") || exit $?
123         dirs="$(git rev-parse --no-revs --no-flags "$@")" || exit $?
124         if [ -n "$dirs" ]; then
125                 die "Error: Use --prefix instead of bare filenames."
126         fi
127 fi
129 debug "command: {$command}"
130 debug "quiet: {$quiet}"
131 debug "revs: {$revs}"
132 debug "dir: {$dir}"
133 debug "opts: {$*}"
134 debug
136 cache_setup()
138         cachedir="$GIT_DIR/subtree-cache/$$"
139         rm -rf "$cachedir" || die "Can't delete old cachedir: $cachedir"
140         mkdir -p "$cachedir" || die "Can't create new cachedir: $cachedir"
141         mkdir -p "$cachedir/notree" || die "Can't create new cachedir: $cachedir/notree"
142         debug "Using cachedir: $cachedir" >&2
145 cache_get()
147         for oldrev in $*; do
148                 if [ -r "$cachedir/$oldrev" ]; then
149                         read newrev <"$cachedir/$oldrev"
150                         echo $newrev
151                 fi
152         done
155 cache_miss()
157         for oldrev in $*; do
158                 if [ ! -r "$cachedir/$oldrev" ]; then
159                         echo $oldrev
160                 fi
161         done
164 check_parents()
166         missed=$(cache_miss $*)
167         for miss in $missed; do
168                 if [ ! -r "$cachedir/notree/$miss" ]; then
169                         debug "  incorrect order: $miss"
170                 fi
171         done
174 set_notree()
176         echo "1" > "$cachedir/notree/$1"
179 cache_set()
181         oldrev="$1"
182         newrev="$2"
183         if [ "$oldrev" != "latest_old" \
184              -a "$oldrev" != "latest_new" \
185              -a -e "$cachedir/$oldrev" ]; then
186                 die "cache for $oldrev already exists!"
187         fi
188         echo "$newrev" >"$cachedir/$oldrev"
191 rev_exists()
193         if git rev-parse "$1" >/dev/null 2>&1; then
194                 return 0
195         else
196                 return 1
197         fi
200 rev_is_descendant_of_branch()
202         newrev="$1"
203         branch="$2"
204         branch_hash=$(git rev-parse $branch)
205         match=$(git rev-list -1 $branch_hash ^$newrev)
207         if [ -z "$match" ]; then
208                 return 0
209         else
210                 return 1
211         fi
214 # if a commit doesn't have a parent, this might not work.  But we only want
215 # to remove the parent from the rev-list, and since it doesn't exist, it won't
216 # be there anyway, so do nothing in that case.
217 try_remove_previous()
219         if rev_exists "$1^"; then
220                 echo "^$1^"
221         fi
224 find_latest_squash()
226         debug "Looking for latest squash ($dir)..."
227         dir="$1"
228         sq=
229         main=
230         sub=
231         git log --grep="^git-subtree-dir: $dir/*\$" \
232                 --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD |
233         while read a b junk; do
234                 debug "$a $b $junk"
235                 debug "{{$sq/$main/$sub}}"
236                 case "$a" in
237                         START) sq="$b" ;;
238                         git-subtree-mainline:) main="$b" ;;
239                         git-subtree-split:) sub="$b" ;;
240                         END)
241                                 if [ -n "$sub" ]; then
242                                         if [ -n "$main" ]; then
243                                                 # a rejoin commit?
244                                                 # Pretend its sub was a squash.
245                                                 sq="$sub"
246                                         fi
247                                         debug "Squash found: $sq $sub"
248                                         echo "$sq" "$sub"
249                                         break
250                                 fi
251                                 sq=
252                                 main=
253                                 sub=
254                                 ;;
255                 esac
256         done
259 find_existing_splits()
261         debug "Looking for prior splits..."
262         dir="$1"
263         revs="$2"
264         main=
265         sub=
266         git log --grep="^git-subtree-dir: $dir/*\$" \
267                 --pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs |
268         while read a b junk; do
269                 case "$a" in
270                         START) sq="$b" ;;
271                         git-subtree-mainline:) main="$b" ;;
272                         git-subtree-split:) sub="$b" ;;
273                         END)
274                                 debug "  Main is: '$main'"
275                                 if [ -z "$main" -a -n "$sub" ]; then
276                                         # squash commits refer to a subtree
277                                         debug "  Squash: $sq from $sub"
278                                         cache_set "$sq" "$sub"
279                                 fi
280                                 if [ -n "$main" -a -n "$sub" ]; then
281                                         debug "  Prior: $main -> $sub"
282                                         cache_set $main $sub
283                                         cache_set $sub $sub
284                                         try_remove_previous "$main"
285                                         try_remove_previous "$sub"
286                                 fi
287                                 main=
288                                 sub=
289                                 ;;
290                 esac
291         done
294 copy_commit()
296         # We're going to set some environment vars here, so
297         # do it in a subshell to get rid of them safely later
298         debug copy_commit "{$1}" "{$2}" "{$3}"
299         git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" |
300         (
301                 read GIT_AUTHOR_NAME
302                 read GIT_AUTHOR_EMAIL
303                 read GIT_AUTHOR_DATE
304                 read GIT_COMMITTER_NAME
305                 read GIT_COMMITTER_EMAIL
306                 read GIT_COMMITTER_DATE
307                 export  GIT_AUTHOR_NAME \
308                         GIT_AUTHOR_EMAIL \
309                         GIT_AUTHOR_DATE \
310                         GIT_COMMITTER_NAME \
311                         GIT_COMMITTER_EMAIL \
312                         GIT_COMMITTER_DATE
313                 (echo -n "$annotate"; cat ) |
314                 git commit-tree "$2" $3  # reads the rest of stdin
315         ) || die "Can't copy commit $1"
318 add_msg()
320         dir="$1"
321         latest_old="$2"
322         latest_new="$3"
323         if [ -n "$message" ]; then
324                 commit_message="$message"
325         else
326                 commit_message="Add '$dir/' from commit '$latest_new'"
327         fi
328         cat <<-EOF
329                 $commit_message
330                 
331                 git-subtree-dir: $dir
332                 git-subtree-mainline: $latest_old
333                 git-subtree-split: $latest_new
334         EOF
337 add_squashed_msg()
339         if [ -n "$message" ]; then
340                 echo "$message"
341         else
342                 echo "Merge commit '$1' as '$2'"
343         fi
346 rejoin_msg()
348         dir="$1"
349         latest_old="$2"
350         latest_new="$3"
351         if [ -n "$message" ]; then
352                 commit_message="$message"
353         else
354                 commit_message="Split '$dir/' into commit '$latest_new'"
355         fi
356         cat <<-EOF
357                 $commit_message
358                 
359                 git-subtree-dir: $dir
360                 git-subtree-mainline: $latest_old
361                 git-subtree-split: $latest_new
362         EOF
365 squash_msg()
367         dir="$1"
368         oldsub="$2"
369         newsub="$3"
370         newsub_short=$(git rev-parse --short "$newsub")
371         
372         if [ -n "$oldsub" ]; then
373                 oldsub_short=$(git rev-parse --short "$oldsub")
374                 echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short"
375                 echo
376                 git log --pretty=tformat:'%h %s' "$oldsub..$newsub"
377                 git log --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub"
378         else
379                 echo "Squashed '$dir/' content from commit $newsub_short"
380         fi
381         
382         echo
383         echo "git-subtree-dir: $dir"
384         echo "git-subtree-split: $newsub"
387 toptree_for_commit()
389         commit="$1"
390         git log -1 --pretty=format:'%T' "$commit" -- || exit $?
393 subtree_for_commit()
395         commit="$1"
396         dir="$2"
397         git ls-tree "$commit" -- "$dir" |
398         while read mode type tree name; do
399                 assert [ "$name" = "$dir" ]
400                 assert [ "$type" = "tree" -o "$type" = "commit" ]
401                 [ "$type" = "commit" ] && continue  # ignore submodules
402                 echo $tree
403                 break
404         done
407 tree_changed()
409         tree=$1
410         shift
411         if [ $# -ne 1 ]; then
412                 return 0   # weird parents, consider it changed
413         else
414                 ptree=$(toptree_for_commit $1)
415                 if [ "$ptree" != "$tree" ]; then
416                         return 0   # changed
417                 else
418                         return 1   # not changed
419                 fi
420         fi
423 new_squash_commit()
425         old="$1"
426         oldsub="$2"
427         newsub="$3"
428         tree=$(toptree_for_commit $newsub) || exit $?
429         if [ -n "$old" ]; then
430                 squash_msg "$dir" "$oldsub" "$newsub" | 
431                         git commit-tree "$tree" -p "$old" || exit $?
432         else
433                 squash_msg "$dir" "" "$newsub" |
434                         git commit-tree "$tree" || exit $?
435         fi
438 copy_or_skip()
440         rev="$1"
441         tree="$2"
442         newparents="$3"
443         assert [ -n "$tree" ]
445         identical=
446         nonidentical=
447         p=
448         gotparents=
449         for parent in $newparents; do
450                 ptree=$(toptree_for_commit $parent) || exit $?
451                 [ -z "$ptree" ] && continue
452                 if [ "$ptree" = "$tree" ]; then
453                         # an identical parent could be used in place of this rev.
454                         identical="$parent"
455                 else
456                         nonidentical="$parent"
457                 fi
458                 
459                 # sometimes both old parents map to the same newparent;
460                 # eliminate duplicates
461                 is_new=1
462                 for gp in $gotparents; do
463                         if [ "$gp" = "$parent" ]; then
464                                 is_new=
465                                 break
466                         fi
467                 done
468                 if [ -n "$is_new" ]; then
469                         gotparents="$gotparents $parent"
470                         p="$p -p $parent"
471                 fi
472         done
473         
474         if [ -n "$identical" ]; then
475                 echo $identical
476         else
477                 copy_commit $rev $tree "$p" || exit $?
478         fi
481 ensure_clean()
483         if ! git diff-index HEAD --exit-code --quiet 2>&1; then
484                 die "Working tree has modifications.  Cannot add."
485         fi
486         if ! git diff-index --cached HEAD --exit-code --quiet 2>&1; then
487                 die "Index has modifications.  Cannot add."
488         fi
491 cmd_add()
493         if [ -e "$dir" ]; then
494                 die "'$dir' already exists.  Cannot add."
495         fi
497         ensure_clean
498         
499         if [ $# -eq 1 ]; then
500                 "cmd_add_commit" "$@"
501         elif [ $# -eq 2 ]; then
502                 "cmd_add_repository" "$@"
503         else
504             say "error: parameters were '$@'"
505             die "Provide either a refspec or a repository and refspec."
506         fi
509 cmd_add_repository()
511         echo "git fetch" "$@"
512         repository=$1
513         refspec=$2
514         git fetch "$@" || exit $?
515         revs=FETCH_HEAD
516         set -- $revs
517         cmd_add_commit "$@"
520 cmd_add_commit()
522         revs=$(git rev-parse $default --revs-only "$@") || exit $?
523         set -- $revs
524         rev="$1"
525         
526         debug "Adding $dir as '$rev'..."
527         git read-tree --prefix="$dir" $rev || exit $?
528         git checkout -- "$dir" || exit $?
529         tree=$(git write-tree) || exit $?
530         
531         headrev=$(git rev-parse HEAD) || exit $?
532         if [ -n "$headrev" -a "$headrev" != "$rev" ]; then
533                 headp="-p $headrev"
534         else
535                 headp=
536         fi
537         
538         if [ -n "$squash" ]; then
539                 rev=$(new_squash_commit "" "" "$rev") || exit $?
540                 commit=$(add_squashed_msg "$rev" "$dir" |
541                          git commit-tree $tree $headp -p "$rev") || exit $?
542         else
543                 commit=$(add_msg "$dir" "$headrev" "$rev" |
544                          git commit-tree $tree $headp -p "$rev") || exit $?
545         fi
546         git reset "$commit" || exit $?
547         
548         say "Added dir '$dir'"
551 cmd_split()
553         debug "Splitting $dir..."
554         cache_setup || exit $?
555         
556         if [ -n "$onto" ]; then
557                 debug "Reading history for --onto=$onto..."
558                 git rev-list $onto |
559                 while read rev; do
560                         # the 'onto' history is already just the subdir, so
561                         # any parent we find there can be used verbatim
562                         debug "  cache: $rev"
563                         cache_set $rev $rev
564                 done
565         fi
566         
567         if [ -n "$ignore_joins" ]; then
568                 unrevs=
569         else
570                 unrevs="$(find_existing_splits "$dir" "$revs")"
571         fi
572         
573         # We can't restrict rev-list to only $dir here, because some of our
574         # parents have the $dir contents the root, and those won't match.
575         # (and rev-list --follow doesn't seem to solve this)
576         grl='git rev-list --topo-order --reverse --parents $revs $unrevs'
577         revmax=$(eval "$grl" | wc -l)
578         revcount=0
579         createcount=0
580         eval "$grl" |
581         while read rev parents; do
582                 revcount=$(($revcount + 1))
583                 say -n "$revcount/$revmax ($createcount)\r"
584                 debug "Processing commit: $rev"
585                 exists=$(cache_get $rev)
586                 if [ -n "$exists" ]; then
587                         debug "  prior: $exists"
588                         continue
589                 fi
590                 createcount=$(($createcount + 1))
591                 debug "  parents: $parents"
592                 newparents=$(cache_get $parents)
593                 debug "  newparents: $newparents"
594                 
595                 tree=$(subtree_for_commit $rev "$dir")
596                 debug "  tree is: $tree"
598                 check_parents $parents
599                 
600                 # ugly.  is there no better way to tell if this is a subtree
601                 # vs. a mainline commit?  Does it matter?
602                 if [ -z $tree ]; then
603                         set_notree $rev
604                         if [ -n "$newparents" ]; then
605                                 cache_set $rev $rev
606                         fi
607                         continue
608                 fi
610                 newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
611                 debug "  newrev is: $newrev"
612                 cache_set $rev $newrev
613                 cache_set latest_new $newrev
614                 cache_set latest_old $rev
615         done || exit $?
616         latest_new=$(cache_get latest_new)
617         if [ -z "$latest_new" ]; then
618                 die "No new revisions were found"
619         fi
620         
621         if [ -n "$rejoin" ]; then
622                 debug "Merging split branch into HEAD..."
623                 latest_old=$(cache_get latest_old)
624                 git merge -s ours \
625                         -m "$(rejoin_msg $dir $latest_old $latest_new)" \
626                         $latest_new >&2 || exit $?
627         fi
628         if [ -n "$branch" ]; then
629                 if rev_exists "refs/heads/$branch"; then
630                         if ! rev_is_descendant_of_branch $latest_new $branch; then
631                                 die "Branch '$branch' is not an ancestor of commit '$latest_new'."
632                         fi
633                         action='Updated'
634                 else
635                         action='Created'
636                 fi
637                 git update-ref -m 'subtree split' "refs/heads/$branch" $latest_new || exit $?
638                 say "$action branch '$branch'"
639         fi
640         echo $latest_new
641         exit 0
644 cmd_merge()
646         revs=$(git rev-parse $default --revs-only "$@") || exit $?
647         ensure_clean
648         
649         set -- $revs
650         if [ $# -ne 1 ]; then
651                 die "You must provide exactly one revision.  Got: '$revs'"
652         fi
653         rev="$1"
654         
655         if [ -n "$squash" ]; then
656                 first_split="$(find_latest_squash "$dir")"
657                 if [ -z "$first_split" ]; then
658                         die "Can't squash-merge: '$dir' was never added."
659                 fi
660                 set $first_split
661                 old=$1
662                 sub=$2
663                 if [ "$sub" = "$rev" ]; then
664                         say "Subtree is already at commit $rev."
665                         exit 0
666                 fi
667                 new=$(new_squash_commit "$old" "$sub" "$rev") || exit $?
668                 debug "New squash commit: $new"
669                 rev="$new"
670         fi
672         version=$(git version)
673         if [ "$version" \< "git version 1.7" ]; then
674                 if [ -n "$message" ]; then
675                         git merge -s subtree --message="$message" $rev
676                 else
677                         git merge -s subtree $rev
678                 fi
679         else
680                 if [ -n "$message" ]; then
681                         git merge -Xsubtree="$prefix" --message="$message" $rev
682                 else
683                         git merge -Xsubtree="$prefix" $rev
684                 fi
685         fi
688 cmd_pull()
690         ensure_clean
691         git fetch "$@" || exit $?
692         revs=FETCH_HEAD
693         set -- $revs
694         cmd_merge "$@"
697 cmd_push()
699         if [ $# -ne 2 ]; then
700             die "You must provide <repository> <refspec>"
701         fi
702         if [ -e "$dir" ]; then
703             repository=$1
704             refspec=$2
705             echo "git push using: " $repository $refspec
706             git push $repository $(git subtree split --prefix=$prefix):refs/heads/$refspec
707         else
708             die "'$dir' must already exist. Try 'git subtree add'."
709         fi
712 "cmd_$command" "$@"