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#
7OPTS_SPEC="\
8git subtree split [--rejoin] [--onto rev] <commit...> -- <path>
9git subtree merge
10
11git subtree does foo and bar!
12--
13h,help show the help
14q quiet
15v verbose
16onto= existing subtree revision to connect, if any
17rejoin merge the new branch back into HEAD
18"
19eval $(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)
20. git-sh-setup
21require_work_tree
22
23quiet=
24command=
25onto=
26rejoin=
27
28debug()
29{
30 if [ -z "$quiet" ]; then
31 echo "$@" >&2
32 fi
33}
34
35assert()
36{
37 if "$@"; then
38 :
39 else
40 die "assertion failed: " "$@"
41 fi
42}
43
44
45#echo "Options: $*"
46
47while [ $# -gt 0 ]; do
48 opt="$1"
49 debug "option: $1"
50 shift
51 case "$opt" in
52 -q) quiet=1 ;;
53 --onto) onto="$1"; shift ;;
54 --rejoin) rejoin=1 ;;
55 --) break ;;
56 esac
57done
58
59command="$1"
60shift
61case "$command" in
62 split|merge) ;;
63 *) die "Unknown command '$command'" ;;
64esac
65
66revs=$(git rev-parse --default HEAD --revs-only "$@") || exit $?
67dirs="$(git rev-parse --sq --no-revs --no-flags "$@")" || exit $?
68
69#echo "dirs is {$dirs}"
70eval $(echo set -- $dirs)
71if [ "$#" -ne 1 ]; then
72 die "Must provide exactly one subtree dir (got $#)"
73fi
74dir="$1"
75
76debug "command: {$command}"
77debug "quiet: {$quiet}"
78debug "revs: {$revs}"
79debug "dir: {$dir}"
80
81cache_setup()
82{
83 cachedir="$GIT_DIR/subtree-cache/$$"
84 rm -rf "$cachedir" || die "Can't delete old cachedir: $cachedir"
85 mkdir -p "$cachedir" || die "Can't create new cachedir: $cachedir"
86 debug "Using cachedir: $cachedir" >&2
87}
88
89cache_get()
90{
91 for oldrev in $*; do
92 if [ -r "$cachedir/$oldrev" ]; then
93 read newrev <"$cachedir/$oldrev"
94 echo $newrev
95 fi
96 done
97}
98
99cache_set()
100{
101 oldrev="$1"
102 newrev="$2"
103 if [ "$oldrev" != "latest_old" \
104 -a "$oldrev" != "latest_new" \
105 -a -e "$cachedir/$oldrev" ]; then
106 die "cache for $oldrev already exists!"
107 fi
108 echo "$newrev" >"$cachedir/$oldrev"
109}
110
111copy_commit()
112{
113 # We're doing to set some environment vars here, so
114 # do it in a subshell to get rid of them safely later
115 git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" |
116 (
117 read GIT_AUTHOR_NAME
118 read GIT_AUTHOR_EMAIL
119 read GIT_AUTHOR_DATE
120 read GIT_COMMITTER_NAME
121 read GIT_COMMITTER_EMAIL
122 read GIT_COMMITTER_DATE
123 export GIT_AUTHOR_NAME \
124 GIT_AUTHOR_EMAIL \
125 GIT_AUTHOR_DATE \
126 GIT_COMMITTER_NAME \
127 GIT_COMMITTER_EMAIL \
128 GIT_COMMITTER_DATE
129 git commit-tree "$2" $3 # reads the rest of stdin
130 ) || die "Can't copy commit $1"
131}
132
133merge_msg()
134{
135 dir="$1"
136 latest_old="$2"
137 latest_new="$3"
138 cat <<-EOF
139 Split changes from '$dir/' into commit '$latest_new'
140
141 git-subtree-dir: $dir
142 git-subtree-includes: $latest_old
143 EOF
144}
145
146cmd_split()
147{
148 debug "Splitting $dir..."
149 cache_setup || exit $?
150
151 git rev-list --reverse --parents $revs -- "$dir" |
152 while read rev parents; do
153 newparents=$(cache_get $parents)
154 debug
155 debug "Processing commit: $rev / $newparents"
156
157 git ls-tree $rev -- "$dir" |
158 while read mode type tree name; do
159 assert [ "$name" = "$dir" ]
160 debug " tree is: $tree"
161 p=""
162 for parent in $newparents; do
163 p="$p -p $parent"
164 done
165
166 newrev=$(copy_commit $rev $tree "$p") || exit $?
167 debug " newrev is: $newrev"
168 cache_set $rev $newrev
169 cache_set latest_new $newrev
170 cache_set latest_old $rev
171 done || exit $?
172 done || exit $?
173 latest_new=$(cache_get latest_new)
174 if [ -z "$latest_new" ]; then
175 die "No new revisions were found"
176 fi
177
178 if [ -n "$rejoin" ]; then
179 debug "Merging split branch into HEAD..."
180 latest_old=$(cache_get latest_old)
181 git merge -s ours \
182 -m "$(merge_msg $dir $latest_old $latest_new)" \
183 $latest_new
184 fi
185 echo $latest_new
186 exit 0
187}
188
189cmd_merge()
190{
191 die "merge command not implemented yet"
192}
193
194"cmd_$command"