Code

Added a --onto option, but it's so complicated I can't tell if it works.
[git.git] / 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 OPTS_SPEC="\
8 git subtree split [--rejoin] [--onto rev] <commit...> -- <path>
9 git subtree merge 
11 git subtree does foo and bar!
12 --
13 h,help   show the help
14 q        quiet
15 v        verbose
16 onto=    existing subtree revision to search for parent
17 rejoin   merge the new branch back into HEAD
18 "
19 eval $(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)
20 . git-sh-setup
21 require_work_tree
23 quiet=
24 command=
25 onto=
26 rejoin=
28 debug()
29 {
30         if [ -z "$quiet" ]; then
31                 echo "$@" >&2
32         fi
33 }
35 assert()
36 {
37         if "$@"; then
38                 :
39         else
40                 die "assertion failed: " "$@"
41         fi
42 }
45 #echo "Options: $*"
47 while [ $# -gt 0 ]; do
48         opt="$1"
49         shift
50         case "$opt" in
51                 -q) quiet=1 ;;
52                 --onto) onto="$1"; shift ;;
53                 --rejoin) rejoin=1 ;;
54                 --) break ;;
55         esac
56 done
58 command="$1"
59 shift
60 case "$command" in
61         split|merge) ;;
62         *) die "Unknown command '$command'" ;;
63 esac
65 revs=$(git rev-parse --default HEAD --revs-only "$@") || exit $?
66 dirs="$(git rev-parse --sq --no-revs --no-flags "$@")" || exit $?
68 #echo "dirs is {$dirs}"
69 eval $(echo set -- $dirs)
70 if [ "$#" -ne 1 ]; then
71         die "Must provide exactly one subtree dir (got $#)"
72 fi
73 dir="$1"
75 debug "command: {$command}"
76 debug "quiet: {$quiet}"
77 debug "revs: {$revs}"
78 debug "dir: {$dir}"
80 cache_setup()
81 {
82         cachedir="$GIT_DIR/subtree-cache/$$"
83         rm -rf "$cachedir" || die "Can't delete old cachedir: $cachedir"
84         mkdir -p "$cachedir" || die "Can't create new cachedir: $cachedir"
85         debug "Using cachedir: $cachedir" >&2
86 }
88 cache_get()
89 {
90         for oldrev in $*; do
91                 if [ -r "$cachedir/$oldrev" ]; then
92                         read newrev <"$cachedir/$oldrev"
93                         echo $newrev
94                 fi
95         done
96 }
98 cache_set()
99 {
100         oldrev="$1"
101         newrev="$2"
102         if [ "$oldrev" != "latest_old" \
103              -a "$oldrev" != "latest_new" \
104              -a -e "$cachedir/$oldrev" ]; then
105                 die "cache for $oldrev already exists!"
106         fi
107         echo "$newrev" >"$cachedir/$oldrev"
110 find_existing_splits()
112         debug "Looking for prior splits..."
113         dir="$1"
114         revs="$2"
115         git log --grep="^git-subtree-dir: $dir\$" \
116                 --pretty=format:'%s%n%n%b%nEND' "$revs" |
117         while read a b junk; do
118                 case "$a" in
119                         git-subtree-mainline:) main="$b" ;;
120                         git-subtree-split:) sub="$b" ;;
121                         *)
122                                 if [ -n "$main" -a -n "$sub" ]; then
123                                         debug "  Prior: $main -> $sub"
124                                         cache_set $main $sub
125                                         echo "^$main^ ^$sub^"
126                                         main=
127                                         sub=
128                                 fi
129                                 ;;
130                 esac
131         done
134 copy_commit()
136         # We're doing to set some environment vars here, so
137         # do it in a subshell to get rid of them safely later
138         git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" |
139         (
140                 read GIT_AUTHOR_NAME
141                 read GIT_AUTHOR_EMAIL
142                 read GIT_AUTHOR_DATE
143                 read GIT_COMMITTER_NAME
144                 read GIT_COMMITTER_EMAIL
145                 read GIT_COMMITTER_DATE
146                 export  GIT_AUTHOR_NAME \
147                         GIT_AUTHOR_EMAIL \
148                         GIT_AUTHOR_DATE \
149                         GIT_COMMITTER_NAME \
150                         GIT_COMMITTER_EMAIL \
151                         GIT_COMMITTER_DATE
152                 git commit-tree "$2" $3  # reads the rest of stdin
153         ) || die "Can't copy commit $1"
156 merge_msg()
158         dir="$1"
159         latest_old="$2"
160         latest_new="$3"
161         cat <<-EOF
162                 Split '$dir/' into commit '$latest_new'
163                 
164                 git-subtree-dir: $dir
165                 git-subtree-mainline: $latest_old
166                 git-subtree-split: $latest_new
167         EOF
170 cmd_split()
172         debug "Splitting $dir..."
173         cache_setup || exit $?
174         
175         if [ -n "$onto" ]; then
176                 echo "Reading history for $onto..."
177                 git rev-list $onto |
178                 while read rev; do
179                         # the 'onto' history is already just the subdir, so
180                         # any parent we find there can be used verbatim
181                         cache_set $rev $rev
182                 done
183         fi
184         
185         unrevs="$(find_existing_splits "$dir" "$revs")"
186         
187         git rev-list --reverse --parents $revs $unrevs -- "$dir" |
188         while read rev parents; do
189                 exists=$(cache_get $rev)
190                 newparents=$(cache_get $parents)
191                 debug
192                 debug "Processing commit: $rev / $newparents"
193                 
194                 if [ -n "$exists" ]; then
195                         debug "  prior: $exists"
196                         continue
197                 fi
198                 
199                 git ls-tree $rev -- "$dir" |
200                 while read mode type tree name; do
201                         assert [ "$name" = "$dir" ]
202                         debug "  tree is: $tree"
203                         p=""
204                         for parent in $newparents; do
205                                 p="$p -p $parent"
206                         done
207                         
208                         newrev=$(copy_commit $rev $tree "$p") || exit $?
209                         debug "  newrev is: $newrev"
210                         cache_set $rev $newrev
211                         cache_set latest_new $newrev
212                         cache_set latest_old $rev
213                 done || exit $?
214         done || exit $?
215         latest_new=$(cache_get latest_new)
216         if [ -z "$latest_new" ]; then
217                 die "No new revisions were found"
218         fi
219         
220         if [ -n "$rejoin" ]; then
221                 debug "Merging split branch into HEAD..."
222                 latest_old=$(cache_get latest_old)
223                 git merge -s ours \
224                         -m "$(merge_msg $dir $latest_old $latest_new)" \
225                         $latest_new
226         fi
227         echo $latest_new
228         exit 0
231 cmd_merge()
233         die "merge command not implemented yet"
236 "cmd_$command"