Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 1 | #!/bin/bash |
| 2 | # |
| 3 | # git-subtree.sh: split/join git repositories in subdirectories of this one |
| 4 | # |
Avery Pennarun | b77172f | 2009-04-24 15:48:41 -0400 | [diff] [blame] | 5 | # Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com> |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 6 | # |
Avery Pennarun | 96db2c0 | 2009-04-24 22:36:06 -0400 | [diff] [blame] | 7 | if [ $# -eq 0 ]; then |
| 8 | set -- -h |
| 9 | fi |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 10 | OPTS_SPEC="\ |
Avery Pennarun | f4f2955 | 2009-05-30 01:10:14 -0400 | [diff] [blame] | 11 | git subtree add --prefix=<prefix> <commit> |
Avery Pennarun | 13648af | 2009-04-24 23:41:19 -0400 | [diff] [blame] | 12 | git subtree merge --prefix=<prefix> <commit> |
| 13 | git subtree pull --prefix=<prefix> <repository> <refspec...> |
Avery Pennarun | f4f2955 | 2009-05-30 01:10:14 -0400 | [diff] [blame] | 14 | git subtree split --prefix=<prefix> <commit...> |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 15 | -- |
Avery Pennarun | 96db2c0 | 2009-04-24 22:36:06 -0400 | [diff] [blame] | 16 | h,help show the help |
| 17 | q quiet |
Avery Pennarun | 942dce5 | 2009-04-26 18:06:08 -0400 | [diff] [blame] | 18 | d show debug messages |
Jakub Suder | 6da4013 | 2010-01-06 23:11:43 +0100 | [diff] [blame] | 19 | p,prefix= the name of the subdir to split out |
Jakub Suder | 2da0969 | 2010-01-09 19:55:35 +0100 | [diff] [blame] | 20 | m,message= use the given message as the commit message for the merge commit |
Avery Pennarun | 13648af | 2009-04-24 23:41:19 -0400 | [diff] [blame] | 21 | options for 'split' |
Avery Pennarun | d0eb1b1 | 2009-04-26 08:59:12 -0400 | [diff] [blame] | 22 | annotate= add a prefix to commit message of new commits |
Avery Pennarun | 43a3951 | 2009-05-30 01:05:43 -0400 | [diff] [blame] | 23 | b,branch= create a new branch from the split subtree |
| 24 | ignore-joins ignore prior --rejoin commits |
Avery Pennarun | 96db2c0 | 2009-04-24 22:36:06 -0400 | [diff] [blame] | 25 | onto= try connecting new tree to an existing one |
| 26 | rejoin merge the new branch back into HEAD |
Avery Pennarun | d713e2d | 2009-05-30 04:11:43 -0400 | [diff] [blame] | 27 | options for 'add', 'merge', and 'pull' |
Avery Pennarun | 8e79043 | 2009-05-30 00:48:07 -0400 | [diff] [blame] | 28 | squash merge subtree changes as a single commit |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 29 | " |
| 30 | eval $(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?) |
Avery Pennarun | 33aaa69 | 2009-08-26 10:41:03 -0400 | [diff] [blame] | 31 | PATH=$(git --exec-path):$PATH |
| 32 | . git-sh-setup |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 33 | require_work_tree |
| 34 | |
| 35 | quiet= |
Avery Pennarun | 43a3951 | 2009-05-30 01:05:43 -0400 | [diff] [blame] | 36 | branch= |
Avery Pennarun | 942dce5 | 2009-04-26 18:06:08 -0400 | [diff] [blame] | 37 | debug= |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 38 | command= |
Avery Pennarun | b77172f | 2009-04-24 15:48:41 -0400 | [diff] [blame] | 39 | onto= |
| 40 | rejoin= |
Avery Pennarun | 96db2c0 | 2009-04-24 22:36:06 -0400 | [diff] [blame] | 41 | ignore_joins= |
Avery Pennarun | d0eb1b1 | 2009-04-26 08:59:12 -0400 | [diff] [blame] | 42 | annotate= |
Avery Pennarun | 8e79043 | 2009-05-30 00:48:07 -0400 | [diff] [blame] | 43 | squash= |
Jakub Suder | 2da0969 | 2010-01-09 19:55:35 +0100 | [diff] [blame] | 44 | message= |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 45 | |
| 46 | debug() |
| 47 | { |
Avery Pennarun | 942dce5 | 2009-04-26 18:06:08 -0400 | [diff] [blame] | 48 | if [ -n "$debug" ]; then |
| 49 | echo "$@" >&2 |
| 50 | fi |
| 51 | } |
| 52 | |
| 53 | say() |
| 54 | { |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 55 | if [ -z "$quiet" ]; then |
| 56 | echo "$@" >&2 |
| 57 | fi |
| 58 | } |
| 59 | |
Avery Pennarun | 2573354 | 2009-04-24 14:24:38 -0400 | [diff] [blame] | 60 | assert() |
| 61 | { |
| 62 | if "$@"; then |
| 63 | : |
| 64 | else |
| 65 | die "assertion failed: " "$@" |
| 66 | fi |
| 67 | } |
| 68 | |
| 69 | |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 70 | #echo "Options: $*" |
| 71 | |
| 72 | while [ $# -gt 0 ]; do |
| 73 | opt="$1" |
| 74 | shift |
| 75 | case "$opt" in |
| 76 | -q) quiet=1 ;; |
Avery Pennarun | 942dce5 | 2009-04-26 18:06:08 -0400 | [diff] [blame] | 77 | -d) debug=1 ;; |
Avery Pennarun | d0eb1b1 | 2009-04-26 08:59:12 -0400 | [diff] [blame] | 78 | --annotate) annotate="$1"; shift ;; |
| 79 | --no-annotate) annotate= ;; |
Avery Pennarun | 43a3951 | 2009-05-30 01:05:43 -0400 | [diff] [blame] | 80 | -b) branch="$1"; shift ;; |
Jakub Suder | 6da4013 | 2010-01-06 23:11:43 +0100 | [diff] [blame] | 81 | -p) prefix="$1"; shift ;; |
Jakub Suder | 2da0969 | 2010-01-09 19:55:35 +0100 | [diff] [blame] | 82 | -m) message="$1"; shift ;; |
Avery Pennarun | 9a8821f | 2009-04-24 22:57:14 -0400 | [diff] [blame] | 83 | --no-prefix) prefix= ;; |
Avery Pennarun | b77172f | 2009-04-24 15:48:41 -0400 | [diff] [blame] | 84 | --onto) onto="$1"; shift ;; |
Avery Pennarun | 96db2c0 | 2009-04-24 22:36:06 -0400 | [diff] [blame] | 85 | --no-onto) onto= ;; |
Avery Pennarun | b77172f | 2009-04-24 15:48:41 -0400 | [diff] [blame] | 86 | --rejoin) rejoin=1 ;; |
Avery Pennarun | 96db2c0 | 2009-04-24 22:36:06 -0400 | [diff] [blame] | 87 | --no-rejoin) rejoin= ;; |
| 88 | --ignore-joins) ignore_joins=1 ;; |
| 89 | --no-ignore-joins) ignore_joins= ;; |
Avery Pennarun | 8e79043 | 2009-05-30 00:48:07 -0400 | [diff] [blame] | 90 | --squash) squash=1 ;; |
| 91 | --no-squash) squash= ;; |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 92 | --) break ;; |
Avery Pennarun | 43a3951 | 2009-05-30 01:05:43 -0400 | [diff] [blame] | 93 | *) die "Unexpected option: $opt" ;; |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 94 | esac |
| 95 | done |
| 96 | |
| 97 | command="$1" |
| 98 | shift |
| 99 | case "$command" in |
Avery Pennarun | 13648af | 2009-04-24 23:41:19 -0400 | [diff] [blame] | 100 | add|merge|pull) default= ;; |
Avery Pennarun | eb7b590 | 2009-04-24 23:28:30 -0400 | [diff] [blame] | 101 | split) default="--default HEAD" ;; |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 102 | *) die "Unknown command '$command'" ;; |
| 103 | esac |
| 104 | |
Avery Pennarun | 9a8821f | 2009-04-24 22:57:14 -0400 | [diff] [blame] | 105 | if [ -z "$prefix" ]; then |
| 106 | die "You must provide the --prefix option." |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 107 | fi |
Avery Pennarun | 6f2012c | 2009-10-02 15:22:15 -0400 | [diff] [blame] | 108 | dir="$(dirname "$prefix/.")" |
Avery Pennarun | 9a8821f | 2009-04-24 22:57:14 -0400 | [diff] [blame] | 109 | |
Avery Pennarun | 13648af | 2009-04-24 23:41:19 -0400 | [diff] [blame] | 110 | if [ "$command" != "pull" ]; then |
| 111 | revs=$(git rev-parse $default --revs-only "$@") || exit $? |
| 112 | dirs="$(git rev-parse --no-revs --no-flags "$@")" || exit $? |
| 113 | if [ -n "$dirs" ]; then |
| 114 | die "Error: Use --prefix instead of bare filenames." |
| 115 | fi |
Avery Pennarun | 9a8821f | 2009-04-24 22:57:14 -0400 | [diff] [blame] | 116 | fi |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 117 | |
| 118 | debug "command: {$command}" |
| 119 | debug "quiet: {$quiet}" |
| 120 | debug "revs: {$revs}" |
| 121 | debug "dir: {$dir}" |
Avery Pennarun | 13648af | 2009-04-24 23:41:19 -0400 | [diff] [blame] | 122 | debug "opts: {$*}" |
Avery Pennarun | eb7b590 | 2009-04-24 23:28:30 -0400 | [diff] [blame] | 123 | debug |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 124 | |
| 125 | cache_setup() |
| 126 | { |
Avery Pennarun | 2573354 | 2009-04-24 14:24:38 -0400 | [diff] [blame] | 127 | cachedir="$GIT_DIR/subtree-cache/$$" |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 128 | rm -rf "$cachedir" || die "Can't delete old cachedir: $cachedir" |
| 129 | mkdir -p "$cachedir" || die "Can't create new cachedir: $cachedir" |
| 130 | debug "Using cachedir: $cachedir" >&2 |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 131 | } |
| 132 | |
| 133 | cache_get() |
| 134 | { |
| 135 | for oldrev in $*; do |
| 136 | if [ -r "$cachedir/$oldrev" ]; then |
| 137 | read newrev <"$cachedir/$oldrev" |
| 138 | echo $newrev |
| 139 | fi |
| 140 | done |
| 141 | } |
| 142 | |
| 143 | cache_set() |
| 144 | { |
| 145 | oldrev="$1" |
| 146 | newrev="$2" |
Avery Pennarun | b77172f | 2009-04-24 15:48:41 -0400 | [diff] [blame] | 147 | if [ "$oldrev" != "latest_old" \ |
| 148 | -a "$oldrev" != "latest_new" \ |
| 149 | -a -e "$cachedir/$oldrev" ]; then |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 150 | die "cache for $oldrev already exists!" |
| 151 | fi |
| 152 | echo "$newrev" >"$cachedir/$oldrev" |
| 153 | } |
| 154 | |
Avery Pennarun | 43a3951 | 2009-05-30 01:05:43 -0400 | [diff] [blame] | 155 | rev_exists() |
| 156 | { |
| 157 | if git rev-parse "$1" >/dev/null 2>&1; then |
| 158 | return 0 |
| 159 | else |
| 160 | return 1 |
| 161 | fi |
| 162 | } |
| 163 | |
Jakub Suder | 0a56294 | 2010-01-09 19:56:05 +0100 | [diff] [blame] | 164 | rev_is_descendant_of_branch() |
| 165 | { |
| 166 | newrev="$1" |
| 167 | branch="$2" |
| 168 | branch_hash=$(git rev-parse $branch) |
| 169 | match=$(git rev-list $newrev | grep $branch_hash) |
| 170 | |
| 171 | if [ -n "$match" ]; then |
| 172 | return 0 |
| 173 | else |
| 174 | return 1 |
| 175 | fi |
| 176 | } |
| 177 | |
Avery Pennarun | b9de535 | 2009-04-25 00:06:45 -0400 | [diff] [blame] | 178 | # if a commit doesn't have a parent, this might not work. But we only want |
| 179 | # to remove the parent from the rev-list, and since it doesn't exist, it won't |
| 180 | # be there anyway, so do nothing in that case. |
| 181 | try_remove_previous() |
| 182 | { |
Avery Pennarun | 43a3951 | 2009-05-30 01:05:43 -0400 | [diff] [blame] | 183 | if rev_exists "$1^"; then |
Avery Pennarun | b9de535 | 2009-04-25 00:06:45 -0400 | [diff] [blame] | 184 | echo "^$1^" |
| 185 | fi |
| 186 | } |
| 187 | |
Avery Pennarun | 1cc2cff | 2009-05-30 03:18:27 -0400 | [diff] [blame] | 188 | find_latest_squash() |
| 189 | { |
Avery Pennarun | d713e2d | 2009-05-30 04:11:43 -0400 | [diff] [blame] | 190 | debug "Looking for latest squash ($dir)..." |
Avery Pennarun | 1cc2cff | 2009-05-30 03:18:27 -0400 | [diff] [blame] | 191 | dir="$1" |
Avery Pennarun | d713e2d | 2009-05-30 04:11:43 -0400 | [diff] [blame] | 192 | sq= |
| 193 | main= |
| 194 | sub= |
Avery Pennarun | 6f2012c | 2009-10-02 15:22:15 -0400 | [diff] [blame] | 195 | git log --grep="^git-subtree-dir: $dir/*\$" \ |
Avery Pennarun | 1cc2cff | 2009-05-30 03:18:27 -0400 | [diff] [blame] | 196 | --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD | |
| 197 | while read a b junk; do |
Avery Pennarun | d713e2d | 2009-05-30 04:11:43 -0400 | [diff] [blame] | 198 | debug "$a $b $junk" |
| 199 | debug "{{$sq/$main/$sub}}" |
Avery Pennarun | 1cc2cff | 2009-05-30 03:18:27 -0400 | [diff] [blame] | 200 | case "$a" in |
| 201 | START) sq="$b" ;; |
| 202 | git-subtree-mainline:) main="$b" ;; |
| 203 | git-subtree-split:) sub="$b" ;; |
| 204 | END) |
| 205 | if [ -n "$sub" ]; then |
| 206 | if [ -n "$main" ]; then |
| 207 | # a rejoin commit? |
| 208 | # Pretend its sub was a squash. |
| 209 | sq="$sub" |
| 210 | fi |
| 211 | debug "Squash found: $sq $sub" |
| 212 | echo "$sq" "$sub" |
| 213 | break |
| 214 | fi |
| 215 | sq= |
| 216 | main= |
| 217 | sub= |
| 218 | ;; |
| 219 | esac |
| 220 | done |
| 221 | } |
| 222 | |
Avery Pennarun | 8b4a77f | 2009-04-24 16:48:08 -0400 | [diff] [blame] | 223 | find_existing_splits() |
| 224 | { |
| 225 | debug "Looking for prior splits..." |
| 226 | dir="$1" |
| 227 | revs="$2" |
Avery Pennarun | d713e2d | 2009-05-30 04:11:43 -0400 | [diff] [blame] | 228 | main= |
| 229 | sub= |
Avery Pennarun | 6f2012c | 2009-10-02 15:22:15 -0400 | [diff] [blame] | 230 | git log --grep="^git-subtree-dir: $dir/*\$" \ |
Avery Pennarun | 1a8c36d | 2009-05-30 03:33:39 -0400 | [diff] [blame] | 231 | --pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs | |
Avery Pennarun | 8b4a77f | 2009-04-24 16:48:08 -0400 | [diff] [blame] | 232 | while read a b junk; do |
| 233 | case "$a" in |
Avery Pennarun | 2275f70 | 2009-10-02 16:09:09 -0400 | [diff] [blame] | 234 | START) sq="$b" ;; |
Avery Pennarun | 8b4a77f | 2009-04-24 16:48:08 -0400 | [diff] [blame] | 235 | git-subtree-mainline:) main="$b" ;; |
| 236 | git-subtree-split:) sub="$b" ;; |
Avery Pennarun | 7ee9eef | 2009-05-30 01:28:20 -0400 | [diff] [blame] | 237 | END) |
Avery Pennarun | 2275f70 | 2009-10-02 16:09:09 -0400 | [diff] [blame] | 238 | debug " Main is: '$main'" |
Avery Pennarun | 1a8c36d | 2009-05-30 03:33:39 -0400 | [diff] [blame] | 239 | if [ -z "$main" -a -n "$sub" ]; then |
| 240 | # squash commits refer to a subtree |
Avery Pennarun | 2275f70 | 2009-10-02 16:09:09 -0400 | [diff] [blame] | 241 | debug " Squash: $sq from $sub" |
Avery Pennarun | 1a8c36d | 2009-05-30 03:33:39 -0400 | [diff] [blame] | 242 | cache_set "$sq" "$sub" |
| 243 | fi |
Avery Pennarun | 8b4a77f | 2009-04-24 16:48:08 -0400 | [diff] [blame] | 244 | if [ -n "$main" -a -n "$sub" ]; then |
| 245 | debug " Prior: $main -> $sub" |
| 246 | cache_set $main $sub |
Avery Pennarun | b9de535 | 2009-04-25 00:06:45 -0400 | [diff] [blame] | 247 | try_remove_previous "$main" |
| 248 | try_remove_previous "$sub" |
Avery Pennarun | 8b4a77f | 2009-04-24 16:48:08 -0400 | [diff] [blame] | 249 | fi |
Avery Pennarun | 7ee9eef | 2009-05-30 01:28:20 -0400 | [diff] [blame] | 250 | main= |
| 251 | sub= |
Avery Pennarun | 8b4a77f | 2009-04-24 16:48:08 -0400 | [diff] [blame] | 252 | ;; |
| 253 | esac |
| 254 | done |
| 255 | } |
| 256 | |
Avery Pennarun | fd9500e | 2009-04-24 14:45:02 -0400 | [diff] [blame] | 257 | copy_commit() |
| 258 | { |
Avery Pennarun | f96bc79 | 2009-05-30 00:47:59 -0400 | [diff] [blame] | 259 | # We're going to set some environment vars here, so |
Avery Pennarun | fd9500e | 2009-04-24 14:45:02 -0400 | [diff] [blame] | 260 | # do it in a subshell to get rid of them safely later |
Avery Pennarun | a64f3a7 | 2009-04-26 16:53:57 -0400 | [diff] [blame] | 261 | debug copy_commit "{$1}" "{$2}" "{$3}" |
Avery Pennarun | fd9500e | 2009-04-24 14:45:02 -0400 | [diff] [blame] | 262 | git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" | |
| 263 | ( |
| 264 | read GIT_AUTHOR_NAME |
| 265 | read GIT_AUTHOR_EMAIL |
| 266 | read GIT_AUTHOR_DATE |
| 267 | read GIT_COMMITTER_NAME |
| 268 | read GIT_COMMITTER_EMAIL |
| 269 | read GIT_COMMITTER_DATE |
Avery Pennarun | b77172f | 2009-04-24 15:48:41 -0400 | [diff] [blame] | 270 | export GIT_AUTHOR_NAME \ |
| 271 | GIT_AUTHOR_EMAIL \ |
| 272 | GIT_AUTHOR_DATE \ |
| 273 | GIT_COMMITTER_NAME \ |
| 274 | GIT_COMMITTER_EMAIL \ |
| 275 | GIT_COMMITTER_DATE |
Avery Pennarun | d0eb1b1 | 2009-04-26 08:59:12 -0400 | [diff] [blame] | 276 | (echo -n "$annotate"; cat ) | |
Avery Pennarun | fd9500e | 2009-04-24 14:45:02 -0400 | [diff] [blame] | 277 | git commit-tree "$2" $3 # reads the rest of stdin |
| 278 | ) || die "Can't copy commit $1" |
| 279 | } |
| 280 | |
Avery Pennarun | eb7b590 | 2009-04-24 23:28:30 -0400 | [diff] [blame] | 281 | add_msg() |
| 282 | { |
| 283 | dir="$1" |
| 284 | latest_old="$2" |
| 285 | latest_new="$3" |
Jakub Suder | 2da0969 | 2010-01-09 19:55:35 +0100 | [diff] [blame] | 286 | if [ -n "$message" ]; then |
| 287 | commit_message="$message" |
| 288 | else |
| 289 | commit_message="Add '$dir/' from commit '$latest_new'" |
| 290 | fi |
Avery Pennarun | eb7b590 | 2009-04-24 23:28:30 -0400 | [diff] [blame] | 291 | cat <<-EOF |
Jakub Suder | 2da0969 | 2010-01-09 19:55:35 +0100 | [diff] [blame] | 292 | $commit_message |
Avery Pennarun | eb7b590 | 2009-04-24 23:28:30 -0400 | [diff] [blame] | 293 | |
| 294 | git-subtree-dir: $dir |
| 295 | git-subtree-mainline: $latest_old |
| 296 | git-subtree-split: $latest_new |
| 297 | EOF |
| 298 | } |
| 299 | |
Jakub Suder | 2da0969 | 2010-01-09 19:55:35 +0100 | [diff] [blame] | 300 | add_squashed_msg() |
| 301 | { |
| 302 | if [ -n "$message" ]; then |
| 303 | echo "$message" |
| 304 | else |
| 305 | echo "Merge commit '$1' as '$2'" |
| 306 | fi |
| 307 | } |
| 308 | |
Avery Pennarun | 7ee9eef | 2009-05-30 01:28:20 -0400 | [diff] [blame] | 309 | rejoin_msg() |
Avery Pennarun | b77172f | 2009-04-24 15:48:41 -0400 | [diff] [blame] | 310 | { |
| 311 | dir="$1" |
| 312 | latest_old="$2" |
| 313 | latest_new="$3" |
Jakub Suder | 2da0969 | 2010-01-09 19:55:35 +0100 | [diff] [blame] | 314 | if [ -n "$message" ]; then |
| 315 | commit_message="$message" |
| 316 | else |
| 317 | commit_message="Split '$dir/' into commit '$latest_new'" |
| 318 | fi |
Avery Pennarun | b77172f | 2009-04-24 15:48:41 -0400 | [diff] [blame] | 319 | cat <<-EOF |
Jakub Suder | 2da0969 | 2010-01-09 19:55:35 +0100 | [diff] [blame] | 320 | $message |
Avery Pennarun | b77172f | 2009-04-24 15:48:41 -0400 | [diff] [blame] | 321 | |
| 322 | git-subtree-dir: $dir |
Avery Pennarun | 8b4a77f | 2009-04-24 16:48:08 -0400 | [diff] [blame] | 323 | git-subtree-mainline: $latest_old |
| 324 | git-subtree-split: $latest_new |
Avery Pennarun | b77172f | 2009-04-24 15:48:41 -0400 | [diff] [blame] | 325 | EOF |
| 326 | } |
| 327 | |
Avery Pennarun | 1cc2cff | 2009-05-30 03:18:27 -0400 | [diff] [blame] | 328 | squash_msg() |
| 329 | { |
| 330 | dir="$1" |
| 331 | oldsub="$2" |
| 332 | newsub="$3" |
Avery Pennarun | 1cc2cff | 2009-05-30 03:18:27 -0400 | [diff] [blame] | 333 | newsub_short=$(git rev-parse --short "$newsub") |
Avery Pennarun | 1cc2cff | 2009-05-30 03:18:27 -0400 | [diff] [blame] | 334 | |
Avery Pennarun | d713e2d | 2009-05-30 04:11:43 -0400 | [diff] [blame] | 335 | if [ -n "$oldsub" ]; then |
| 336 | oldsub_short=$(git rev-parse --short "$oldsub") |
| 337 | echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short" |
| 338 | echo |
| 339 | git log --pretty=tformat:'%h %s' "$oldsub..$newsub" |
| 340 | git log --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub" |
| 341 | else |
| 342 | echo "Squashed '$dir/' content from commit $newsub_short" |
| 343 | fi |
Avery Pennarun | 1cc2cff | 2009-05-30 03:18:27 -0400 | [diff] [blame] | 344 | |
Avery Pennarun | d713e2d | 2009-05-30 04:11:43 -0400 | [diff] [blame] | 345 | echo |
| 346 | echo "git-subtree-dir: $dir" |
| 347 | echo "git-subtree-split: $newsub" |
Avery Pennarun | 1cc2cff | 2009-05-30 03:18:27 -0400 | [diff] [blame] | 348 | } |
| 349 | |
Avery Pennarun | 210d083 | 2009-04-24 21:49:19 -0400 | [diff] [blame] | 350 | toptree_for_commit() |
Avery Pennarun | 768d6d1 | 2009-04-24 17:53:10 -0400 | [diff] [blame] | 351 | { |
Avery Pennarun | 210d083 | 2009-04-24 21:49:19 -0400 | [diff] [blame] | 352 | commit="$1" |
| 353 | git log -1 --pretty=format:'%T' "$commit" -- || exit $? |
| 354 | } |
| 355 | |
| 356 | subtree_for_commit() |
| 357 | { |
| 358 | commit="$1" |
| 359 | dir="$2" |
| 360 | git ls-tree "$commit" -- "$dir" | |
Avery Pennarun | 768d6d1 | 2009-04-24 17:53:10 -0400 | [diff] [blame] | 361 | while read mode type tree name; do |
| 362 | assert [ "$name" = "$dir" ] |
Pelle Wessman | 8ac5eca | 2009-09-30 14:29:42 +0200 | [diff] [blame] | 363 | assert [ "$type" = "tree" ] |
Avery Pennarun | 768d6d1 | 2009-04-24 17:53:10 -0400 | [diff] [blame] | 364 | echo $tree |
| 365 | break |
| 366 | done |
| 367 | } |
| 368 | |
| 369 | tree_changed() |
| 370 | { |
| 371 | tree=$1 |
| 372 | shift |
| 373 | if [ $# -ne 1 ]; then |
| 374 | return 0 # weird parents, consider it changed |
| 375 | else |
Avery Pennarun | 210d083 | 2009-04-24 21:49:19 -0400 | [diff] [blame] | 376 | ptree=$(toptree_for_commit $1) |
Avery Pennarun | 768d6d1 | 2009-04-24 17:53:10 -0400 | [diff] [blame] | 377 | if [ "$ptree" != "$tree" ]; then |
| 378 | return 0 # changed |
| 379 | else |
| 380 | return 1 # not changed |
| 381 | fi |
| 382 | fi |
| 383 | } |
| 384 | |
Avery Pennarun | 1cc2cff | 2009-05-30 03:18:27 -0400 | [diff] [blame] | 385 | new_squash_commit() |
| 386 | { |
| 387 | old="$1" |
| 388 | oldsub="$2" |
| 389 | newsub="$3" |
| 390 | tree=$(toptree_for_commit $newsub) || exit $? |
Avery Pennarun | d713e2d | 2009-05-30 04:11:43 -0400 | [diff] [blame] | 391 | if [ -n "$old" ]; then |
| 392 | squash_msg "$dir" "$oldsub" "$newsub" | |
| 393 | git commit-tree "$tree" -p "$old" || exit $? |
| 394 | else |
| 395 | squash_msg "$dir" "" "$newsub" | |
| 396 | git commit-tree "$tree" || exit $? |
| 397 | fi |
Avery Pennarun | 1cc2cff | 2009-05-30 03:18:27 -0400 | [diff] [blame] | 398 | } |
| 399 | |
Avery Pennarun | d691265 | 2009-04-24 22:05:30 -0400 | [diff] [blame] | 400 | copy_or_skip() |
| 401 | { |
| 402 | rev="$1" |
| 403 | tree="$2" |
| 404 | newparents="$3" |
| 405 | assert [ -n "$tree" ] |
| 406 | |
Avery Pennarun | 96db2c0 | 2009-04-24 22:36:06 -0400 | [diff] [blame] | 407 | identical= |
Avery Pennarun | 49cf822 | 2009-04-26 17:07:16 -0400 | [diff] [blame] | 408 | nonidentical= |
Avery Pennarun | 96db2c0 | 2009-04-24 22:36:06 -0400 | [diff] [blame] | 409 | p= |
Avery Pennarun | a64f3a7 | 2009-04-26 16:53:57 -0400 | [diff] [blame] | 410 | gotparents= |
Avery Pennarun | d691265 | 2009-04-24 22:05:30 -0400 | [diff] [blame] | 411 | for parent in $newparents; do |
| 412 | ptree=$(toptree_for_commit $parent) || exit $? |
Avery Pennarun | a64f3a7 | 2009-04-26 16:53:57 -0400 | [diff] [blame] | 413 | [ -z "$ptree" ] && continue |
Avery Pennarun | d691265 | 2009-04-24 22:05:30 -0400 | [diff] [blame] | 414 | if [ "$ptree" = "$tree" ]; then |
Avery Pennarun | 96db2c0 | 2009-04-24 22:36:06 -0400 | [diff] [blame] | 415 | # an identical parent could be used in place of this rev. |
| 416 | identical="$parent" |
Avery Pennarun | 49cf822 | 2009-04-26 17:07:16 -0400 | [diff] [blame] | 417 | else |
| 418 | nonidentical="$parent" |
Avery Pennarun | 96db2c0 | 2009-04-24 22:36:06 -0400 | [diff] [blame] | 419 | fi |
Avery Pennarun | a64f3a7 | 2009-04-26 16:53:57 -0400 | [diff] [blame] | 420 | |
| 421 | # sometimes both old parents map to the same newparent; |
| 422 | # eliminate duplicates |
| 423 | is_new=1 |
| 424 | for gp in $gotparents; do |
| 425 | if [ "$gp" = "$parent" ]; then |
| 426 | is_new= |
| 427 | break |
| 428 | fi |
| 429 | done |
| 430 | if [ -n "$is_new" ]; then |
| 431 | gotparents="$gotparents $parent" |
Avery Pennarun | d691265 | 2009-04-24 22:05:30 -0400 | [diff] [blame] | 432 | p="$p -p $parent" |
| 433 | fi |
| 434 | done |
| 435 | |
Avery Pennarun | 795e730 | 2009-04-26 17:44:18 -0400 | [diff] [blame] | 436 | if [ -n "$identical" ]; then |
Avery Pennarun | 96db2c0 | 2009-04-24 22:36:06 -0400 | [diff] [blame] | 437 | echo $identical |
| 438 | else |
| 439 | copy_commit $rev $tree "$p" || exit $? |
| 440 | fi |
Avery Pennarun | d691265 | 2009-04-24 22:05:30 -0400 | [diff] [blame] | 441 | } |
| 442 | |
Avery Pennarun | 13648af | 2009-04-24 23:41:19 -0400 | [diff] [blame] | 443 | ensure_clean() |
Avery Pennarun | eb7b590 | 2009-04-24 23:28:30 -0400 | [diff] [blame] | 444 | { |
Avery Pennarun | eb7b590 | 2009-04-24 23:28:30 -0400 | [diff] [blame] | 445 | if ! git diff-index HEAD --exit-code --quiet; then |
| 446 | die "Working tree has modifications. Cannot add." |
| 447 | fi |
| 448 | if ! git diff-index --cached HEAD --exit-code --quiet; then |
| 449 | die "Index has modifications. Cannot add." |
| 450 | fi |
Avery Pennarun | 13648af | 2009-04-24 23:41:19 -0400 | [diff] [blame] | 451 | } |
| 452 | |
| 453 | cmd_add() |
| 454 | { |
| 455 | if [ -e "$dir" ]; then |
| 456 | die "'$dir' already exists. Cannot add." |
| 457 | fi |
| 458 | ensure_clean |
| 459 | |
Avery Pennarun | eb7b590 | 2009-04-24 23:28:30 -0400 | [diff] [blame] | 460 | set -- $revs |
| 461 | if [ $# -ne 1 ]; then |
| 462 | die "You must provide exactly one revision. Got: '$revs'" |
| 463 | fi |
| 464 | rev="$1" |
| 465 | |
| 466 | debug "Adding $dir as '$rev'..." |
| 467 | git read-tree --prefix="$dir" $rev || exit $? |
Avery Pennarun | 227f781 | 2009-08-26 10:43:43 -0400 | [diff] [blame] | 468 | git checkout -- "$dir" || exit $? |
Avery Pennarun | eb7b590 | 2009-04-24 23:28:30 -0400 | [diff] [blame] | 469 | tree=$(git write-tree) || exit $? |
| 470 | |
| 471 | headrev=$(git rev-parse HEAD) || exit $? |
| 472 | if [ -n "$headrev" -a "$headrev" != "$rev" ]; then |
| 473 | headp="-p $headrev" |
| 474 | else |
| 475 | headp= |
| 476 | fi |
Avery Pennarun | d713e2d | 2009-05-30 04:11:43 -0400 | [diff] [blame] | 477 | |
| 478 | if [ -n "$squash" ]; then |
| 479 | rev=$(new_squash_commit "" "" "$rev") || exit $? |
Jakub Suder | 2da0969 | 2010-01-09 19:55:35 +0100 | [diff] [blame] | 480 | commit=$(add_squashed_msg "$rev" "$dir" | |
Avery Pennarun | d713e2d | 2009-05-30 04:11:43 -0400 | [diff] [blame] | 481 | git commit-tree $tree $headp -p "$rev") || exit $? |
| 482 | else |
| 483 | commit=$(add_msg "$dir" "$headrev" "$rev" | |
| 484 | git commit-tree $tree $headp -p "$rev") || exit $? |
| 485 | fi |
Avery Pennarun | eb7b590 | 2009-04-24 23:28:30 -0400 | [diff] [blame] | 486 | git reset "$commit" || exit $? |
Avery Pennarun | d713e2d | 2009-05-30 04:11:43 -0400 | [diff] [blame] | 487 | |
| 488 | say "Added dir '$dir'" |
Avery Pennarun | eb7b590 | 2009-04-24 23:28:30 -0400 | [diff] [blame] | 489 | } |
| 490 | |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 491 | cmd_split() |
| 492 | { |
| 493 | debug "Splitting $dir..." |
| 494 | cache_setup || exit $? |
| 495 | |
Avery Pennarun | 33ff583 | 2009-04-24 17:05:14 -0400 | [diff] [blame] | 496 | if [ -n "$onto" ]; then |
Avery Pennarun | 847e868 | 2009-04-24 21:35:50 -0400 | [diff] [blame] | 497 | debug "Reading history for --onto=$onto..." |
Avery Pennarun | 33ff583 | 2009-04-24 17:05:14 -0400 | [diff] [blame] | 498 | git rev-list $onto | |
| 499 | while read rev; do |
| 500 | # the 'onto' history is already just the subdir, so |
| 501 | # any parent we find there can be used verbatim |
Avery Pennarun | 2c71b7c | 2009-04-24 17:42:33 -0400 | [diff] [blame] | 502 | debug " cache: $rev" |
Avery Pennarun | 33ff583 | 2009-04-24 17:05:14 -0400 | [diff] [blame] | 503 | cache_set $rev $rev |
| 504 | done |
| 505 | fi |
| 506 | |
Avery Pennarun | 96db2c0 | 2009-04-24 22:36:06 -0400 | [diff] [blame] | 507 | if [ -n "$ignore_joins" ]; then |
| 508 | unrevs= |
| 509 | else |
| 510 | unrevs="$(find_existing_splits "$dir" "$revs")" |
| 511 | fi |
Avery Pennarun | 8b4a77f | 2009-04-24 16:48:08 -0400 | [diff] [blame] | 512 | |
Avery Pennarun | 1f73862 | 2009-04-26 15:54:42 -0400 | [diff] [blame] | 513 | # We can't restrict rev-list to only $dir here, because some of our |
| 514 | # parents have the $dir contents the root, and those won't match. |
| 515 | # (and rev-list --follow doesn't seem to solve this) |
Avery Pennarun | 942dce5 | 2009-04-26 18:06:08 -0400 | [diff] [blame] | 516 | grl='git rev-list --reverse --parents $revs $unrevs' |
| 517 | revmax=$(eval "$grl" | wc -l) |
| 518 | revcount=0 |
| 519 | createcount=0 |
| 520 | eval "$grl" | |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 521 | while read rev parents; do |
Avery Pennarun | 942dce5 | 2009-04-26 18:06:08 -0400 | [diff] [blame] | 522 | revcount=$(($revcount + 1)) |
Jakub Suder | 0a56294 | 2010-01-09 19:56:05 +0100 | [diff] [blame] | 523 | say -n "$revcount/$revmax ($createcount) |
| 524 | " |
Avery Pennarun | 2c71b7c | 2009-04-24 17:42:33 -0400 | [diff] [blame] | 525 | debug "Processing commit: $rev" |
| 526 | exists=$(cache_get $rev) |
Avery Pennarun | 8b4a77f | 2009-04-24 16:48:08 -0400 | [diff] [blame] | 527 | if [ -n "$exists" ]; then |
| 528 | debug " prior: $exists" |
| 529 | continue |
| 530 | fi |
Avery Pennarun | 942dce5 | 2009-04-26 18:06:08 -0400 | [diff] [blame] | 531 | createcount=$(($createcount + 1)) |
Avery Pennarun | 2c71b7c | 2009-04-24 17:42:33 -0400 | [diff] [blame] | 532 | debug " parents: $parents" |
| 533 | newparents=$(cache_get $parents) |
| 534 | debug " newparents: $newparents" |
Avery Pennarun | 8b4a77f | 2009-04-24 16:48:08 -0400 | [diff] [blame] | 535 | |
Avery Pennarun | 210d083 | 2009-04-24 21:49:19 -0400 | [diff] [blame] | 536 | tree=$(subtree_for_commit $rev "$dir") |
Avery Pennarun | 768d6d1 | 2009-04-24 17:53:10 -0400 | [diff] [blame] | 537 | debug " tree is: $tree" |
Avery Pennarun | 7ee9eef | 2009-05-30 01:28:20 -0400 | [diff] [blame] | 538 | |
| 539 | # ugly. is there no better way to tell if this is a subtree |
| 540 | # vs. a mainline commit? Does it matter? |
Jakub Suder | da949cc | 2010-01-09 23:01:39 +0100 | [diff] [blame^] | 541 | if [ -z $tree ]; then |
| 542 | cache_set $rev $rev |
| 543 | continue |
| 544 | fi |
Avery Pennarun | 768d6d1 | 2009-04-24 17:53:10 -0400 | [diff] [blame] | 545 | |
Avery Pennarun | d691265 | 2009-04-24 22:05:30 -0400 | [diff] [blame] | 546 | newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $? |
Avery Pennarun | 768d6d1 | 2009-04-24 17:53:10 -0400 | [diff] [blame] | 547 | debug " newrev is: $newrev" |
| 548 | cache_set $rev $newrev |
| 549 | cache_set latest_new $newrev |
| 550 | cache_set latest_old $rev |
Avery Pennarun | 2573354 | 2009-04-24 14:24:38 -0400 | [diff] [blame] | 551 | done || exit $? |
Avery Pennarun | b77172f | 2009-04-24 15:48:41 -0400 | [diff] [blame] | 552 | latest_new=$(cache_get latest_new) |
| 553 | if [ -z "$latest_new" ]; then |
Avery Pennarun | e25a6bf | 2009-04-24 14:52:27 -0400 | [diff] [blame] | 554 | die "No new revisions were found" |
| 555 | fi |
Avery Pennarun | b77172f | 2009-04-24 15:48:41 -0400 | [diff] [blame] | 556 | |
| 557 | if [ -n "$rejoin" ]; then |
| 558 | debug "Merging split branch into HEAD..." |
| 559 | latest_old=$(cache_get latest_old) |
| 560 | git merge -s ours \ |
Avery Pennarun | 7ee9eef | 2009-05-30 01:28:20 -0400 | [diff] [blame] | 561 | -m "$(rejoin_msg $dir $latest_old $latest_new)" \ |
Avery Pennarun | ea28d67 | 2009-04-30 21:57:32 -0400 | [diff] [blame] | 562 | $latest_new >&2 || exit $? |
Avery Pennarun | b77172f | 2009-04-24 15:48:41 -0400 | [diff] [blame] | 563 | fi |
Avery Pennarun | 43a3951 | 2009-05-30 01:05:43 -0400 | [diff] [blame] | 564 | if [ -n "$branch" ]; then |
Jakub Suder | 0a56294 | 2010-01-09 19:56:05 +0100 | [diff] [blame] | 565 | if rev_exists "refs/heads/$branch"; then |
| 566 | if ! rev_is_descendant_of_branch $latest_new $branch; then |
| 567 | die "Branch '$branch' is not an ancestor of commit '$latest_new'." |
| 568 | fi |
| 569 | action='Updated' |
| 570 | else |
| 571 | action='Created' |
| 572 | fi |
| 573 | git update-ref -m 'subtree split' "refs/heads/$branch" $latest_new || exit $? |
| 574 | say "$action branch '$branch'" |
Avery Pennarun | 43a3951 | 2009-05-30 01:05:43 -0400 | [diff] [blame] | 575 | fi |
Avery Pennarun | b77172f | 2009-04-24 15:48:41 -0400 | [diff] [blame] | 576 | echo $latest_new |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 577 | exit 0 |
| 578 | } |
| 579 | |
| 580 | cmd_merge() |
| 581 | { |
Avery Pennarun | 13648af | 2009-04-24 23:41:19 -0400 | [diff] [blame] | 582 | ensure_clean |
| 583 | |
| 584 | set -- $revs |
| 585 | if [ $# -ne 1 ]; then |
| 586 | die "You must provide exactly one revision. Got: '$revs'" |
| 587 | fi |
| 588 | rev="$1" |
| 589 | |
Avery Pennarun | 1cc2cff | 2009-05-30 03:18:27 -0400 | [diff] [blame] | 590 | if [ -n "$squash" ]; then |
| 591 | first_split="$(find_latest_squash "$dir")" |
| 592 | if [ -z "$first_split" ]; then |
| 593 | die "Can't squash-merge: '$dir' was never added." |
| 594 | fi |
| 595 | set $first_split |
| 596 | old=$1 |
| 597 | sub=$2 |
Avery Pennarun | eb4fb91 | 2009-05-30 03:33:17 -0400 | [diff] [blame] | 598 | if [ "$sub" = "$rev" ]; then |
| 599 | say "Subtree is already at commit $rev." |
| 600 | exit 0 |
| 601 | fi |
Avery Pennarun | 1cc2cff | 2009-05-30 03:18:27 -0400 | [diff] [blame] | 602 | new=$(new_squash_commit "$old" "$sub" "$rev") || exit $? |
| 603 | debug "New squash commit: $new" |
| 604 | rev="$new" |
| 605 | fi |
| 606 | |
Jakub Suder | 2da0969 | 2010-01-09 19:55:35 +0100 | [diff] [blame] | 607 | git merge -s subtree --message="$message" $rev |
Avery Pennarun | 0ca71b3 | 2009-04-24 14:13:34 -0400 | [diff] [blame] | 608 | } |
| 609 | |
Avery Pennarun | 13648af | 2009-04-24 23:41:19 -0400 | [diff] [blame] | 610 | cmd_pull() |
| 611 | { |
| 612 | ensure_clean |
Avery Pennarun | e31d1e2 | 2009-10-02 18:23:54 -0400 | [diff] [blame] | 613 | git fetch "$@" || exit $? |
| 614 | revs=FETCH_HEAD |
| 615 | cmd_merge |
Avery Pennarun | 13648af | 2009-04-24 23:41:19 -0400 | [diff] [blame] | 616 | } |
| 617 | |
| 618 | "cmd_$command" "$@" |