Code

Skip over empty commits.
[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 tree_for_commit()
172         git ls-tree "$1" -- "$dir" |
173         while read mode type tree name; do
174                 assert [ "$name" = "$dir" ]
175                 echo $tree
176                 break
177         done
180 tree_changed()
182         tree=$1
183         shift
184         if [ $# -ne 1 ]; then
185                 return 0   # weird parents, consider it changed
186         else
187                 ptree=$(tree_for_commit $1)
188                 if [ "$ptree" != "$tree" ]; then
189                         return 0   # changed
190                 else
191                         return 1   # not changed
192                 fi
193         fi
196 cmd_split()
198         debug "Splitting $dir..."
199         cache_setup || exit $?
200         
201         if [ -n "$onto" ]; then
202                 echo "Reading history for --onto=$onto..."
203                 git rev-list $onto |
204                 while read rev; do
205                         # the 'onto' history is already just the subdir, so
206                         # any parent we find there can be used verbatim
207                         debug "  cache: $rev"
208                         cache_set $rev $rev
209                 done
210         fi
211         
212         unrevs="$(find_existing_splits "$dir" "$revs")"
213         
214         debug "git rev-list --reverse $revs $unrevs"
215         git rev-list --reverse --parents $revs $unrevsx |
216         while read rev parents; do
217                 debug
218                 debug "Processing commit: $rev"
219                 exists=$(cache_get $rev)
220                 if [ -n "$exists" ]; then
221                         debug "  prior: $exists"
222                         continue
223                 fi
224                 debug "  parents: $parents"
225                 newparents=$(cache_get $parents)
226                 debug "  newparents: $newparents"
227                 
228                 tree=$(tree_for_commit $rev)
229                 debug "  tree is: $tree"
230                 [ -z $tree ] && continue
232                 p=""
233                 for parent in $newparents; do
234                         p="$p -p $parent"
235                 done
236                         
237                 if tree_changed $tree $parents; then
238                         newrev=$(copy_commit $rev $tree "$p") || exit $?
239                 else
240                         newrev="$newparents"
241                 fi
242                 debug "  newrev is: $newrev"
243                 cache_set $rev $newrev
244                 cache_set latest_new $newrev
245                 cache_set latest_old $rev
246         done || exit $?
247         latest_new=$(cache_get latest_new)
248         if [ -z "$latest_new" ]; then
249                 die "No new revisions were found"
250         fi
251         
252         if [ -n "$rejoin" ]; then
253                 debug "Merging split branch into HEAD..."
254                 latest_old=$(cache_get latest_old)
255                 git merge -s ours \
256                         -m "$(merge_msg $dir $latest_old $latest_new)" \
257                         $latest_new
258         fi
259         echo $latest_new
260         exit 0
263 cmd_merge()
265         die "merge command not implemented yet"
268 "cmd_$command"