Add a new 'git subtree add' command for adding subtrees from a given rev.
[git-subtree.git] / git-subtree.sh
blob2dc99e82cd0aa3c23587c77dbe4eac76d355385b
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 split [options...] <--prefix=prefix> <commit...>
13 git subtree merge
15 h,help show the help
16 q quiet
17 prefix= the name of the subdir to split out
18 onto= try connecting new tree to an existing one
19 rejoin merge the new branch back into HEAD
20 ignore-joins ignore prior --rejoin commits
22 eval $(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)
23 . git-sh-setup
24 require_work_tree
26 quiet=
27 command=
28 onto=
29 rejoin=
30 ignore_joins=
32 debug()
34 if [ -z "$quiet" ]; then
35 echo "$@" >&2
39 assert()
41 if "$@"; then
43 else
44 die "assertion failed: " "$@"
49 #echo "Options: $*"
51 while [ $# -gt 0 ]; do
52 opt="$1"
53 shift
54 case "$opt" in
55 -q) quiet=1 ;;
56 --prefix) prefix="$1"; shift ;;
57 --no-prefix) prefix= ;;
58 --onto) onto="$1"; shift ;;
59 --no-onto) onto= ;;
60 --rejoin) rejoin=1 ;;
61 --no-rejoin) rejoin= ;;
62 --ignore-joins) ignore_joins=1 ;;
63 --no-ignore-joins) ignore_joins= ;;
64 --) break ;;
65 esac
66 done
68 command="$1"
69 shift
70 case "$command" in
71 add|merge) default= ;;
72 split) default="--default HEAD" ;;
73 *) die "Unknown command '$command'" ;;
74 esac
76 revs=$(git rev-parse $default --revs-only "$@") || exit $?
78 if [ -z "$prefix" ]; then
79 die "You must provide the --prefix option."
81 dir="$prefix"
83 dirs="$(git rev-parse --no-revs --no-flags "$@")" || exit $?
84 if [ -n "$dirs" ]; then
85 die "Error: Use --prefix instead of bare filenames."
88 debug "command: {$command}"
89 debug "quiet: {$quiet}"
90 debug "revs: {$revs}"
91 debug "dir: {$dir}"
92 debug
94 cache_setup()
96 cachedir="$GIT_DIR/subtree-cache/$$"
97 rm -rf "$cachedir" || die "Can't delete old cachedir: $cachedir"
98 mkdir -p "$cachedir" || die "Can't create new cachedir: $cachedir"
99 debug "Using cachedir: $cachedir" >&2
102 cache_get()
104 for oldrev in $*; do
105 if [ -r "$cachedir/$oldrev" ]; then
106 read newrev <"$cachedir/$oldrev"
107 echo $newrev
109 done
112 cache_set()
114 oldrev="$1"
115 newrev="$2"
116 if [ "$oldrev" != "latest_old" \
117 -a "$oldrev" != "latest_new" \
118 -a -e "$cachedir/$oldrev" ]; then
119 die "cache for $oldrev already exists!"
121 echo "$newrev" >"$cachedir/$oldrev"
124 find_existing_splits()
126 debug "Looking for prior splits..."
127 dir="$1"
128 revs="$2"
129 git log --grep="^git-subtree-dir: $dir\$" \
130 --pretty=format:'%s%n%n%b%nEND' "$revs" |
131 while read a b junk; do
132 case "$a" in
133 git-subtree-mainline:) main="$b" ;;
134 git-subtree-split:) sub="$b" ;;
136 if [ -n "$main" -a -n "$sub" ]; then
137 debug " Prior: $main -> $sub"
138 cache_set $main $sub
139 echo "^$main^ ^$sub^"
140 main=
141 sub=
144 esac
145 done
148 copy_commit()
150 # We're doing to set some environment vars here, so
151 # do it in a subshell to get rid of them safely later
152 git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" |
154 read GIT_AUTHOR_NAME
155 read GIT_AUTHOR_EMAIL
156 read GIT_AUTHOR_DATE
157 read GIT_COMMITTER_NAME
158 read GIT_COMMITTER_EMAIL
159 read GIT_COMMITTER_DATE
160 export GIT_AUTHOR_NAME \
161 GIT_AUTHOR_EMAIL \
162 GIT_AUTHOR_DATE \
163 GIT_COMMITTER_NAME \
164 GIT_COMMITTER_EMAIL \
165 GIT_COMMITTER_DATE
166 (echo -n '*'; cat ) | # FIXME
167 git commit-tree "$2" $3 # reads the rest of stdin
168 ) || die "Can't copy commit $1"
171 add_msg()
173 dir="$1"
174 latest_old="$2"
175 latest_new="$3"
176 cat <<-EOF
177 Add '$dir/' from commit '$latest_new'
179 git-subtree-dir: $dir
180 git-subtree-mainline: $latest_old
181 git-subtree-split: $latest_new
185 merge_msg()
187 dir="$1"
188 latest_old="$2"
189 latest_new="$3"
190 cat <<-EOF
191 Split '$dir/' into commit '$latest_new'
193 git-subtree-dir: $dir
194 git-subtree-mainline: $latest_old
195 git-subtree-split: $latest_new
199 toptree_for_commit()
201 commit="$1"
202 git log -1 --pretty=format:'%T' "$commit" -- || exit $?
205 subtree_for_commit()
207 commit="$1"
208 dir="$2"
209 git ls-tree "$commit" -- "$dir" |
210 while read mode type tree name; do
211 assert [ "$name" = "$dir" ]
212 echo $tree
213 break
214 done
217 tree_changed()
219 tree=$1
220 shift
221 if [ $# -ne 1 ]; then
222 return 0 # weird parents, consider it changed
223 else
224 ptree=$(toptree_for_commit $1)
225 if [ "$ptree" != "$tree" ]; then
226 return 0 # changed
227 else
228 return 1 # not changed
233 copy_or_skip()
235 rev="$1"
236 tree="$2"
237 newparents="$3"
238 assert [ -n "$tree" ]
240 identical=
242 for parent in $newparents; do
243 ptree=$(toptree_for_commit $parent) || exit $?
244 if [ "$ptree" = "$tree" ]; then
245 # an identical parent could be used in place of this rev.
246 identical="$parent"
248 if [ -n "$ptree" ]; then
249 parentmatch="$parentmatch$parent"
250 p="$p -p $parent"
252 done
254 if [ -n "$identical" -a "$parentmatch" = "$identical" ]; then
255 echo $identical
256 else
257 copy_commit $rev $tree "$p" || exit $?
261 cmd_add()
263 if [ -e "$dir" ]; then
264 die "'$dir' already exists. Cannot add."
266 if ! git diff-index HEAD --exit-code --quiet; then
267 die "Working tree has modifications. Cannot add."
269 if ! git diff-index --cached HEAD --exit-code --quiet; then
270 die "Index has modifications. Cannot add."
272 set -- $revs
273 if [ $# -ne 1 ]; then
274 die "You must provide exactly one revision. Got: '$revs'"
276 rev="$1"
278 debug "Adding $dir as '$rev'..."
279 git read-tree --prefix="$dir" $rev || exit $?
280 git checkout "$dir" || exit $?
281 tree=$(git write-tree) || exit $?
283 headrev=$(git rev-parse HEAD) || exit $?
284 if [ -n "$headrev" -a "$headrev" != "$rev" ]; then
285 headp="-p $headrev"
286 else
287 headp=
289 commit=$(add_msg "$dir" "$headrev" "$rev" |
290 git commit-tree $tree $headp -p "$rev") || exit $?
291 git reset "$commit" || exit $?
294 cmd_split()
296 debug "Splitting $dir..."
297 cache_setup || exit $?
299 if [ -n "$onto" ]; then
300 debug "Reading history for --onto=$onto..."
301 git rev-list $onto |
302 while read rev; do
303 # the 'onto' history is already just the subdir, so
304 # any parent we find there can be used verbatim
305 debug " cache: $rev"
306 cache_set $rev $rev
307 done
310 if [ -n "$ignore_joins" ]; then
311 unrevs=
312 else
313 unrevs="$(find_existing_splits "$dir" "$revs")"
316 # We can't restrict rev-list to only "$dir" here, because that leaves out
317 # critical information about commit parents.
318 debug "git rev-list --reverse --parents $revs $unrevs"
319 git rev-list --reverse --parents $revs $unrevs |
320 while read rev parents; do
321 debug
322 debug "Processing commit: $rev"
323 exists=$(cache_get $rev)
324 if [ -n "$exists" ]; then
325 debug " prior: $exists"
326 continue
328 debug " parents: $parents"
329 newparents=$(cache_get $parents)
330 debug " newparents: $newparents"
332 tree=$(subtree_for_commit $rev "$dir")
333 debug " tree is: $tree"
334 [ -z $tree ] && continue
336 newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
337 debug " newrev is: $newrev"
338 cache_set $rev $newrev
339 cache_set latest_new $newrev
340 cache_set latest_old $rev
341 done || exit $?
342 latest_new=$(cache_get latest_new)
343 if [ -z "$latest_new" ]; then
344 die "No new revisions were found"
347 if [ -n "$rejoin" ]; then
348 debug "Merging split branch into HEAD..."
349 latest_old=$(cache_get latest_old)
350 git merge -s ours \
351 -m "$(merge_msg $dir $latest_old $latest_new)" \
352 $latest_new >&2
354 echo $latest_new
355 exit 0
358 cmd_merge()
360 die "merge command not implemented yet"
363 "cmd_$command"