| 1 | #!/bin/sh |
| 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 | |
| 8 | if test -z "$GIT_EXEC_PATH" || ! test -f "$GIT_EXEC_PATH/git-sh-setup" || { |
| 9 | test "${PATH#"${GIT_EXEC_PATH}:"}" = "$PATH" && |
| 10 | test ! "$GIT_EXEC_PATH" -ef "${PATH%%:*}" 2>/dev/null |
| 11 | } |
| 12 | then |
| 13 | basename=${0##*[/\\]} |
| 14 | echo >&2 'It looks like either your git installation or your' |
| 15 | echo >&2 'git-subtree installation is broken.' |
| 16 | echo >&2 |
| 17 | echo >&2 "Tips:" |
| 18 | echo >&2 " - If \`git --exec-path\` does not print the correct path to" |
| 19 | echo >&2 " your git install directory, then set the GIT_EXEC_PATH" |
| 20 | echo >&2 " environment variable to the correct directory." |
| 21 | echo >&2 " - Make sure that your \`$basename\` file is either in your" |
| 22 | echo >&2 " PATH or in your git exec path (\`$(git --exec-path)\`)." |
| 23 | echo >&2 " - You should run git-subtree as \`git ${basename#git-}\`," |
| 24 | echo >&2 " not as \`$basename\`." >&2 |
| 25 | exit 126 |
| 26 | fi |
| 27 | |
| 28 | OPTS_SPEC="\ |
| 29 | git subtree add --prefix=<prefix> [-S[=<key-id>]] <commit> |
| 30 | git subtree add --prefix=<prefix> [-S[=<key-id>]] <repository> <ref> |
| 31 | git subtree merge --prefix=<prefix> [-S[=<key-id>]] <commit> |
| 32 | git subtree split --prefix=<prefix> [-S[=<key-id>]] [<commit>] |
| 33 | git subtree pull --prefix=<prefix> [-S[=<key-id>]] <repository> <ref> |
| 34 | git subtree push --prefix=<prefix> [-S[=<key-id>]] <repository> <refspec> |
| 35 | -- |
| 36 | h,help! show the help |
| 37 | q,quiet! quiet |
| 38 | d,debug! show debug messages |
| 39 | P,prefix= the name of the subdir to split out |
| 40 | options for 'split' (also: 'push') |
| 41 | annotate= add a prefix to commit message of new commits |
| 42 | b,branch!= create a new branch from the split subtree |
| 43 | ignore-joins ignore prior --rejoin commits |
| 44 | onto= try connecting new tree to an existing one |
| 45 | rejoin merge the new branch back into HEAD |
| 46 | options for 'add' and 'merge' (also: 'pull', 'split --rejoin', and 'push --rejoin') |
| 47 | squash merge subtree changes as a single commit |
| 48 | m,message!= use the given message as the commit message for the merge commit |
| 49 | S,gpg-sign?key-id GPG-sign commits. The keyid argument is optional and defaults to the committer identity |
| 50 | " |
| 51 | |
| 52 | indent=0 |
| 53 | |
| 54 | # Usage: say [MSG...] |
| 55 | say () { |
| 56 | if test -z "$arg_quiet" |
| 57 | then |
| 58 | printf '%s\n' "$*" |
| 59 | fi |
| 60 | } |
| 61 | |
| 62 | # Usage: debug [MSG...] |
| 63 | debug () { |
| 64 | if test -n "$arg_debug" |
| 65 | then |
| 66 | printf "%$(($indent * 2))s%s\n" '' "$*" >&2 |
| 67 | fi |
| 68 | } |
| 69 | |
| 70 | # Usage: progress [MSG...] |
| 71 | progress () { |
| 72 | if test -z "$arg_quiet" |
| 73 | then |
| 74 | if test -z "$arg_debug" |
| 75 | then |
| 76 | # Debug mode is off. |
| 77 | # |
| 78 | # Print one progress line that we keep updating (use |
| 79 | # "\r" to return to the beginning of the line, rather |
| 80 | # than "\n" to start a new line). This only really |
| 81 | # works when stderr is a terminal. |
| 82 | printf "%s\r" "$*" >&2 |
| 83 | else |
| 84 | # Debug mode is on. The `debug` function is regularly |
| 85 | # printing to stderr. |
| 86 | # |
| 87 | # Don't do the one-line-with-"\r" thing, because on a |
| 88 | # terminal the debug output would overwrite and hide the |
| 89 | # progress output. Add a "progress:" prefix to make the |
| 90 | # progress output and the debug output easy to |
| 91 | # distinguish. This ensures maximum readability whether |
| 92 | # stderr is a terminal or a file. |
| 93 | printf "progress: %s\n" "$*" >&2 |
| 94 | fi |
| 95 | fi |
| 96 | } |
| 97 | |
| 98 | # Usage: assert CMD... |
| 99 | assert () { |
| 100 | if ! "$@" |
| 101 | then |
| 102 | die "fatal: assertion failed: $*" |
| 103 | fi |
| 104 | } |
| 105 | |
| 106 | # Usage: die_incompatible_opt OPTION COMMAND |
| 107 | die_incompatible_opt () { |
| 108 | assert test "$#" = 2 |
| 109 | opt="$1" |
| 110 | arg_command="$2" |
| 111 | die "fatal: the '$opt' flag does not make sense with 'git subtree $arg_command'." |
| 112 | } |
| 113 | |
| 114 | main () { |
| 115 | if test $# -eq 0 |
| 116 | then |
| 117 | set -- -h |
| 118 | fi |
| 119 | set_args="$(echo "$OPTS_SPEC" | git rev-parse --parseopt --stuck-long -- "$@" || echo exit $?)" |
| 120 | eval "$set_args" |
| 121 | . git-sh-setup |
| 122 | require_work_tree |
| 123 | |
| 124 | # First figure out the command and whether we use --rejoin, so |
| 125 | # that we can provide more helpful validation when we do the |
| 126 | # "real" flag parsing. |
| 127 | arg_split_rejoin= |
| 128 | allow_split= |
| 129 | allow_addmerge= |
| 130 | while test $# -gt 0 |
| 131 | do |
| 132 | opt="$1" |
| 133 | shift |
| 134 | case "$opt" in |
| 135 | --rejoin) |
| 136 | arg_split_rejoin=1 |
| 137 | ;; |
| 138 | --no-rejoin) |
| 139 | arg_split_rejoin= |
| 140 | ;; |
| 141 | --) |
| 142 | break |
| 143 | ;; |
| 144 | esac |
| 145 | done |
| 146 | arg_command=$1 |
| 147 | case "$arg_command" in |
| 148 | add|merge|pull) |
| 149 | allow_addmerge=1 |
| 150 | ;; |
| 151 | split|push) |
| 152 | allow_split=1 |
| 153 | allow_addmerge=$arg_split_rejoin |
| 154 | ;; |
| 155 | *) |
| 156 | die "fatal: unknown command '$arg_command'" |
| 157 | ;; |
| 158 | esac |
| 159 | # Reset the arguments array for "real" flag parsing. |
| 160 | eval "$set_args" |
| 161 | |
| 162 | # Begin "real" flag parsing. |
| 163 | arg_quiet= |
| 164 | arg_debug= |
| 165 | arg_prefix= |
| 166 | arg_split_branch= |
| 167 | arg_split_onto= |
| 168 | arg_split_ignore_joins= |
| 169 | arg_split_annotate= |
| 170 | arg_addmerge_squash= |
| 171 | arg_addmerge_message= |
| 172 | arg_gpg_sign= |
| 173 | while test $# -gt 0 |
| 174 | do |
| 175 | opt="$1" |
| 176 | shift |
| 177 | |
| 178 | case "$opt" in |
| 179 | --quiet) |
| 180 | arg_quiet=1 |
| 181 | ;; |
| 182 | --debug) |
| 183 | arg_debug=1 |
| 184 | ;; |
| 185 | --annotate=*) |
| 186 | test -n "$allow_split" || die_incompatible_opt "$opt" "$arg_command" |
| 187 | arg_split_annotate="${opt#*=}" |
| 188 | ;; |
| 189 | --no-annotate) |
| 190 | test -n "$allow_split" || die_incompatible_opt "$opt" "$arg_command" |
| 191 | arg_split_annotate= |
| 192 | ;; |
| 193 | --branch=*) |
| 194 | test -n "$allow_split" || die_incompatible_opt "$opt" "$arg_command" |
| 195 | arg_split_branch="${opt#*=}" |
| 196 | ;; |
| 197 | --prefix=*) |
| 198 | arg_prefix="${opt#*=}" |
| 199 | ;; |
| 200 | --message=*) |
| 201 | test -n "$allow_addmerge" || die_incompatible_opt "$opt" "$arg_command" |
| 202 | arg_addmerge_message="${opt#*=}" |
| 203 | ;; |
| 204 | --no-prefix) |
| 205 | arg_prefix= |
| 206 | ;; |
| 207 | --onto=*) |
| 208 | test -n "$allow_split" || die_incompatible_opt "$opt" "$arg_command" |
| 209 | arg_split_onto="${opt#*=}" |
| 210 | ;; |
| 211 | --no-onto) |
| 212 | test -n "$allow_split" || die_incompatible_opt "$opt" "$arg_command" |
| 213 | arg_split_onto= |
| 214 | ;; |
| 215 | --rejoin) |
| 216 | test -n "$allow_split" || die_incompatible_opt "$opt" "$arg_command" |
| 217 | ;; |
| 218 | --no-rejoin) |
| 219 | test -n "$allow_split" || die_incompatible_opt "$opt" "$arg_command" |
| 220 | ;; |
| 221 | --ignore-joins) |
| 222 | test -n "$allow_split" || die_incompatible_opt "$opt" "$arg_command" |
| 223 | arg_split_ignore_joins=1 |
| 224 | ;; |
| 225 | --no-ignore-joins) |
| 226 | test -n "$allow_split" || die_incompatible_opt "$opt" "$arg_command" |
| 227 | arg_split_ignore_joins= |
| 228 | ;; |
| 229 | --squash) |
| 230 | test -n "$allow_addmerge" || die_incompatible_opt "$opt" "$arg_command" |
| 231 | arg_addmerge_squash=1 |
| 232 | ;; |
| 233 | --no-squash) |
| 234 | test -n "$allow_addmerge" || die_incompatible_opt "$opt" "$arg_command" |
| 235 | arg_addmerge_squash= |
| 236 | ;; |
| 237 | --gpg-sign=* | --gpg-sign | --no-gpg-sign) |
| 238 | arg_gpg_sign="$opt" |
| 239 | ;; |
| 240 | --) |
| 241 | break |
| 242 | ;; |
| 243 | *) |
| 244 | die "fatal: unexpected option: $opt" |
| 245 | ;; |
| 246 | esac |
| 247 | done |
| 248 | shift |
| 249 | |
| 250 | if test -z "$arg_prefix" |
| 251 | then |
| 252 | die "fatal: you must provide the --prefix option." |
| 253 | fi |
| 254 | |
| 255 | case "$arg_command" in |
| 256 | add) |
| 257 | test -e "$arg_prefix" && |
| 258 | die "fatal: prefix '$arg_prefix' already exists." |
| 259 | ;; |
| 260 | split) |
| 261 | # checked later against the commit, not the working tree |
| 262 | ;; |
| 263 | *) |
| 264 | test -e "$arg_prefix" || |
| 265 | die "fatal: '$arg_prefix' does not exist; use 'git subtree add'" |
| 266 | ;; |
| 267 | esac |
| 268 | |
| 269 | dir="$(dirname "$arg_prefix/.")" |
| 270 | |
| 271 | debug "command: {$arg_command}" |
| 272 | debug "quiet: {$arg_quiet}" |
| 273 | debug "dir: {$dir}" |
| 274 | debug "opts: {$*}" |
| 275 | debug "gpg-sign: {$arg_gpg_sign}" |
| 276 | debug |
| 277 | |
| 278 | "cmd_$arg_command" "$@" |
| 279 | } |
| 280 | |
| 281 | # Usage: cache_setup |
| 282 | cache_setup () { |
| 283 | assert test $# = 0 |
| 284 | cachedir="$GIT_DIR/subtree-cache/$$" |
| 285 | rm -rf "$cachedir" || |
| 286 | die "fatal: can't delete old cachedir: $cachedir" |
| 287 | mkdir -p "$cachedir" || |
| 288 | die "fatal: can't create new cachedir: $cachedir" |
| 289 | mkdir -p "$cachedir/notree" || |
| 290 | die "fatal: can't create new cachedir: $cachedir/notree" |
| 291 | debug "Using cachedir: $cachedir" >&2 |
| 292 | } |
| 293 | |
| 294 | # Usage: cache_get [REVS...] |
| 295 | cache_get () { |
| 296 | for oldrev in "$@" |
| 297 | do |
| 298 | if test -r "$cachedir/$oldrev" |
| 299 | then |
| 300 | read newrev <"$cachedir/$oldrev" |
| 301 | echo $newrev |
| 302 | fi |
| 303 | done |
| 304 | } |
| 305 | |
| 306 | # Usage: cache_miss [REVS...] |
| 307 | cache_miss () { |
| 308 | for oldrev in "$@" |
| 309 | do |
| 310 | if ! test -r "$cachedir/$oldrev" |
| 311 | then |
| 312 | echo $oldrev |
| 313 | fi |
| 314 | done |
| 315 | } |
| 316 | |
| 317 | # Usage: check_parents [REVS...] |
| 318 | check_parents () { |
| 319 | missed=$(cache_miss "$@") || exit $? |
| 320 | local indent=$(($indent + 1)) |
| 321 | for miss in $missed |
| 322 | do |
| 323 | if ! test -r "$cachedir/notree/$miss" |
| 324 | then |
| 325 | debug "incorrect order: $miss" |
| 326 | process_split_commit "$miss" "" |
| 327 | fi |
| 328 | done |
| 329 | } |
| 330 | |
| 331 | # Usage: set_notree REV |
| 332 | set_notree () { |
| 333 | assert test $# = 1 |
| 334 | echo "1" > "$cachedir/notree/$1" |
| 335 | } |
| 336 | |
| 337 | # Usage: cache_set OLDREV NEWREV |
| 338 | cache_set () { |
| 339 | assert test $# = 2 |
| 340 | oldrev="$1" |
| 341 | newrev="$2" |
| 342 | if test "$oldrev" != "latest_old" && |
| 343 | test "$oldrev" != "latest_new" && |
| 344 | test -e "$cachedir/$oldrev" |
| 345 | then |
| 346 | die "fatal: cache for $oldrev already exists!" |
| 347 | fi |
| 348 | echo "$newrev" >"$cachedir/$oldrev" |
| 349 | } |
| 350 | |
| 351 | # Usage: rev_exists REV |
| 352 | rev_exists () { |
| 353 | assert test $# = 1 |
| 354 | if git rev-parse "$1" >/dev/null 2>&1 |
| 355 | then |
| 356 | return 0 |
| 357 | else |
| 358 | return 1 |
| 359 | fi |
| 360 | } |
| 361 | |
| 362 | # Usage: try_remove_previous REV |
| 363 | # |
| 364 | # If a commit doesn't have a parent, this might not work. But we only want |
| 365 | # to remove the parent from the rev-list, and since it doesn't exist, it won't |
| 366 | # be there anyway, so do nothing in that case. |
| 367 | try_remove_previous () { |
| 368 | assert test $# = 1 |
| 369 | if rev_exists "$1^" |
| 370 | then |
| 371 | echo "^$1^" |
| 372 | fi |
| 373 | } |
| 374 | |
| 375 | # Usage: process_subtree_split_trailer SPLIT_HASH MAIN_HASH [REPOSITORY] |
| 376 | process_subtree_split_trailer () { |
| 377 | assert test $# -ge 2 |
| 378 | assert test $# -le 3 |
| 379 | b="$1" |
| 380 | sq="$2" |
| 381 | repository="" |
| 382 | if test "$#" = 3 |
| 383 | then |
| 384 | repository="$3" |
| 385 | fi |
| 386 | fail_msg="fatal: could not rev-parse split hash $b from commit $sq" |
| 387 | if ! sub="$(git rev-parse --verify --quiet "$b^{commit}")" |
| 388 | then |
| 389 | # if 'repository' was given, try to fetch the 'git-subtree-split' hash |
| 390 | # before 'rev-parse'-ing it again, as it might be a tag that we do not have locally |
| 391 | if test -n "${repository}" |
| 392 | then |
| 393 | git fetch "$repository" "$b" |
| 394 | sub="$(git rev-parse --verify --quiet "$b^{commit}")" || |
| 395 | die "$fail_msg" |
| 396 | else |
| 397 | hint1=$(printf "hint: hash might be a tag, try fetching it from the subtree repository:") |
| 398 | hint2=$(printf "hint: git fetch <subtree-repository> $b") |
| 399 | fail_msg=$(printf "$fail_msg\n$hint1\n$hint2") |
| 400 | die "$fail_msg" |
| 401 | fi |
| 402 | fi |
| 403 | } |
| 404 | |
| 405 | # Usage: find_latest_squash DIR [REPOSITORY] |
| 406 | find_latest_squash () { |
| 407 | assert test $# -ge 1 |
| 408 | assert test $# -le 2 |
| 409 | dir="$1" |
| 410 | repository="" |
| 411 | if test "$#" = 2 |
| 412 | then |
| 413 | repository="$2" |
| 414 | fi |
| 415 | debug "Looking for latest squash (dir=$dir, repository=$repository)..." |
| 416 | local indent=$(($indent + 1)) |
| 417 | |
| 418 | sq= |
| 419 | main= |
| 420 | sub= |
| 421 | git log --grep="^git-subtree-dir: $dir/*\$" \ |
| 422 | --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD | |
| 423 | while read a b junk |
| 424 | do |
| 425 | debug "$a $b $junk" |
| 426 | debug "{{$sq/$main/$sub}}" |
| 427 | case "$a" in |
| 428 | START) |
| 429 | sq="$b" |
| 430 | ;; |
| 431 | git-subtree-mainline:) |
| 432 | main="$b" |
| 433 | ;; |
| 434 | git-subtree-split:) |
| 435 | process_subtree_split_trailer "$b" "$sq" "$repository" |
| 436 | ;; |
| 437 | END) |
| 438 | if test -n "$sub" |
| 439 | then |
| 440 | if test -n "$main" |
| 441 | then |
| 442 | # a rejoin commit? |
| 443 | # Pretend its sub was a squash. |
| 444 | sq=$(git rev-parse --verify "$sq^2") || |
| 445 | die |
| 446 | fi |
| 447 | debug "Squash found: $sq $sub" |
| 448 | echo "$sq" "$sub" |
| 449 | break |
| 450 | fi |
| 451 | sq= |
| 452 | main= |
| 453 | sub= |
| 454 | ;; |
| 455 | esac |
| 456 | done || exit $? |
| 457 | } |
| 458 | |
| 459 | # Usage: find_existing_splits DIR REV [REPOSITORY] |
| 460 | find_existing_splits () { |
| 461 | assert test $# -ge 2 |
| 462 | assert test $# -le 3 |
| 463 | debug "Looking for prior splits..." |
| 464 | local indent=$(($indent + 1)) |
| 465 | |
| 466 | dir="$1" |
| 467 | rev="$2" |
| 468 | repository="" |
| 469 | if test "$#" = 3 |
| 470 | then |
| 471 | repository="$3" |
| 472 | fi |
| 473 | main= |
| 474 | sub= |
| 475 | local grep_format="^git-subtree-dir: $dir/*\$" |
| 476 | if test -n "$arg_split_ignore_joins" |
| 477 | then |
| 478 | grep_format="^Add '$dir/' from commit '" |
| 479 | fi |
| 480 | git log --grep="$grep_format" \ |
| 481 | --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' "$rev" | |
| 482 | while read a b junk |
| 483 | do |
| 484 | case "$a" in |
| 485 | START) |
| 486 | sq="$b" |
| 487 | ;; |
| 488 | git-subtree-mainline:) |
| 489 | main="$b" |
| 490 | ;; |
| 491 | git-subtree-split:) |
| 492 | process_subtree_split_trailer "$b" "$sq" "$repository" |
| 493 | ;; |
| 494 | END) |
| 495 | debug "Main is: '$main'" |
| 496 | if test -z "$main" && test -n "$sub" |
| 497 | then |
| 498 | # squash commits refer to a subtree |
| 499 | debug " Squash: $sq from $sub" |
| 500 | cache_set "$sq" "$sub" |
| 501 | fi |
| 502 | if test -n "$main" && test -n "$sub" |
| 503 | then |
| 504 | debug " Prior: $main -> $sub" |
| 505 | cache_set $main $sub |
| 506 | cache_set $sub $sub |
| 507 | try_remove_previous "$main" |
| 508 | try_remove_previous "$sub" |
| 509 | fi |
| 510 | main= |
| 511 | sub= |
| 512 | ;; |
| 513 | esac |
| 514 | done || exit $? |
| 515 | } |
| 516 | |
| 517 | # Usage: copy_commit REV TREE FLAGS_STR |
| 518 | copy_commit () { |
| 519 | assert test $# = 3 |
| 520 | # We're going to set some environment vars here, so |
| 521 | # do it in a subshell to get rid of them safely later |
| 522 | debug copy_commit "{$1}" "{$2}" "{$3}" |
| 523 | git log -1 --no-show-signature --pretty=format:'%an%n%ae%n%aD%n%cn%n%ce%n%cD%n%B' "$1" | |
| 524 | ( |
| 525 | read GIT_AUTHOR_NAME |
| 526 | read GIT_AUTHOR_EMAIL |
| 527 | read GIT_AUTHOR_DATE |
| 528 | read GIT_COMMITTER_NAME |
| 529 | read GIT_COMMITTER_EMAIL |
| 530 | read GIT_COMMITTER_DATE |
| 531 | export GIT_AUTHOR_NAME \ |
| 532 | GIT_AUTHOR_EMAIL \ |
| 533 | GIT_AUTHOR_DATE \ |
| 534 | GIT_COMMITTER_NAME \ |
| 535 | GIT_COMMITTER_EMAIL \ |
| 536 | GIT_COMMITTER_DATE |
| 537 | ( |
| 538 | printf "%s" "$arg_split_annotate" |
| 539 | cat |
| 540 | ) | |
| 541 | git commit-tree $arg_gpg_sign "$2" $3 # reads the rest of stdin |
| 542 | ) || die "fatal: can't copy commit $1" |
| 543 | } |
| 544 | |
| 545 | # Usage: add_msg DIR LATEST_OLD LATEST_NEW |
| 546 | add_msg () { |
| 547 | assert test $# = 3 |
| 548 | dir="$1" |
| 549 | latest_old="$2" |
| 550 | latest_new="$3" |
| 551 | if test -n "$arg_addmerge_message" |
| 552 | then |
| 553 | commit_message="$arg_addmerge_message" |
| 554 | else |
| 555 | commit_message="Add '$dir/' from commit '$latest_new'" |
| 556 | fi |
| 557 | if test -n "$arg_split_rejoin" |
| 558 | then |
| 559 | # If this is from a --rejoin, then rejoin_msg has |
| 560 | # already inserted the `git-subtree-xxx:` tags |
| 561 | echo "$commit_message" |
| 562 | return |
| 563 | fi |
| 564 | cat <<-EOF |
| 565 | $commit_message |
| 566 | |
| 567 | git-subtree-dir: $dir |
| 568 | git-subtree-mainline: $latest_old |
| 569 | git-subtree-split: $latest_new |
| 570 | EOF |
| 571 | } |
| 572 | |
| 573 | # Usage: add_squashed_msg REV DIR |
| 574 | add_squashed_msg () { |
| 575 | assert test $# = 2 |
| 576 | if test -n "$arg_addmerge_message" |
| 577 | then |
| 578 | echo "$arg_addmerge_message" |
| 579 | else |
| 580 | echo "Merge commit '$1' as '$2'" |
| 581 | fi |
| 582 | } |
| 583 | |
| 584 | # Usage: rejoin_msg DIR LATEST_OLD LATEST_NEW |
| 585 | rejoin_msg () { |
| 586 | assert test $# = 3 |
| 587 | dir="$1" |
| 588 | latest_old="$2" |
| 589 | latest_new="$3" |
| 590 | if test -n "$arg_addmerge_message" |
| 591 | then |
| 592 | commit_message="$arg_addmerge_message" |
| 593 | else |
| 594 | commit_message="Split '$dir/' into commit '$latest_new'" |
| 595 | fi |
| 596 | cat <<-EOF |
| 597 | $commit_message |
| 598 | |
| 599 | git-subtree-dir: $dir |
| 600 | git-subtree-mainline: $latest_old |
| 601 | git-subtree-split: $latest_new |
| 602 | EOF |
| 603 | } |
| 604 | |
| 605 | # Usage: squash_msg DIR OLD_SUBTREE_COMMIT NEW_SUBTREE_COMMIT |
| 606 | squash_msg () { |
| 607 | assert test $# = 3 |
| 608 | dir="$1" |
| 609 | oldsub="$2" |
| 610 | newsub="$3" |
| 611 | newsub_short=$(git rev-parse --short "$newsub") |
| 612 | |
| 613 | if test -n "$oldsub" |
| 614 | then |
| 615 | oldsub_short=$(git rev-parse --short "$oldsub") |
| 616 | echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short" |
| 617 | echo |
| 618 | git log --no-show-signature --pretty=tformat:'%h %s' "$oldsub..$newsub" |
| 619 | git log --no-show-signature --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub" |
| 620 | else |
| 621 | echo "Squashed '$dir/' content from commit $newsub_short" |
| 622 | fi |
| 623 | |
| 624 | echo |
| 625 | echo "git-subtree-dir: $dir" |
| 626 | echo "git-subtree-split: $newsub" |
| 627 | } |
| 628 | |
| 629 | # Usage: toptree_for_commit COMMIT |
| 630 | toptree_for_commit () { |
| 631 | assert test $# = 1 |
| 632 | commit="$1" |
| 633 | git rev-parse --verify "$commit^{tree}" || exit $? |
| 634 | } |
| 635 | |
| 636 | # Usage: subtree_for_commit COMMIT DIR |
| 637 | subtree_for_commit () { |
| 638 | assert test $# = 2 |
| 639 | commit="$1" |
| 640 | dir="$2" |
| 641 | git ls-tree "$commit" -- "$dir" | |
| 642 | while read mode type tree name |
| 643 | do |
| 644 | assert test "$name" = "$dir" |
| 645 | |
| 646 | case "$type" in |
| 647 | commit) |
| 648 | continue;; # ignore submodules |
| 649 | tree) |
| 650 | echo $tree |
| 651 | break;; |
| 652 | *) |
| 653 | die "fatal: tree entry is of type ${type}, expected tree or commit";; |
| 654 | esac |
| 655 | done || exit $? |
| 656 | } |
| 657 | |
| 658 | # Usage: tree_changed TREE [PARENTS...] |
| 659 | tree_changed () { |
| 660 | assert test $# -gt 0 |
| 661 | tree=$1 |
| 662 | shift |
| 663 | if test $# -ne 1 |
| 664 | then |
| 665 | return 0 # weird parents, consider it changed |
| 666 | else |
| 667 | ptree=$(toptree_for_commit $1) || exit $? |
| 668 | if test "$ptree" != "$tree" |
| 669 | then |
| 670 | return 0 # changed |
| 671 | else |
| 672 | return 1 # not changed |
| 673 | fi |
| 674 | fi |
| 675 | } |
| 676 | |
| 677 | # Usage: new_squash_commit OLD_SQUASHED_COMMIT OLD_NONSQUASHED_COMMIT NEW_NONSQUASHED_COMMIT |
| 678 | new_squash_commit () { |
| 679 | assert test $# = 3 |
| 680 | old="$1" |
| 681 | oldsub="$2" |
| 682 | newsub="$3" |
| 683 | tree=$(toptree_for_commit $newsub) || exit $? |
| 684 | if test -n "$old" |
| 685 | then |
| 686 | squash_msg "$dir" "$oldsub" "$newsub" | |
| 687 | git commit-tree $arg_gpg_sign "$tree" -p "$old" || exit $? |
| 688 | else |
| 689 | squash_msg "$dir" "" "$newsub" | |
| 690 | git commit-tree $arg_gpg_sign "$tree" || exit $? |
| 691 | fi |
| 692 | } |
| 693 | |
| 694 | # Usage: copy_or_skip REV TREE NEWPARENTS |
| 695 | copy_or_skip () { |
| 696 | assert test $# = 3 |
| 697 | rev="$1" |
| 698 | tree="$2" |
| 699 | newparents="$3" |
| 700 | assert test -n "$tree" |
| 701 | |
| 702 | identical= |
| 703 | nonidentical= |
| 704 | p= |
| 705 | gotparents= |
| 706 | copycommit= |
| 707 | for parent in $newparents |
| 708 | do |
| 709 | ptree=$(toptree_for_commit $parent) || exit $? |
| 710 | test -z "$ptree" && continue |
| 711 | if test "$ptree" = "$tree" |
| 712 | then |
| 713 | # an identical parent could be used in place of this rev. |
| 714 | if test -n "$identical" |
| 715 | then |
| 716 | # if a previous identical parent was found, check whether |
| 717 | # one is already an ancestor of the other |
| 718 | mergebase=$(git merge-base $identical $parent) |
| 719 | if test "$identical" = "$mergebase" |
| 720 | then |
| 721 | # current identical commit is an ancestor of parent |
| 722 | identical="$parent" |
| 723 | elif test "$parent" != "$mergebase" |
| 724 | then |
| 725 | # no common history; commit must be copied |
| 726 | copycommit=1 |
| 727 | fi |
| 728 | else |
| 729 | # first identical parent detected |
| 730 | identical="$parent" |
| 731 | fi |
| 732 | else |
| 733 | nonidentical="$parent" |
| 734 | fi |
| 735 | |
| 736 | # sometimes both old parents map to the same newparent; |
| 737 | # eliminate duplicates |
| 738 | is_new=1 |
| 739 | for gp in $gotparents |
| 740 | do |
| 741 | if test "$gp" = "$parent" |
| 742 | then |
| 743 | is_new= |
| 744 | break |
| 745 | fi |
| 746 | done |
| 747 | if test -n "$is_new" |
| 748 | then |
| 749 | gotparents="$gotparents $parent" |
| 750 | p="$p -p $parent" |
| 751 | fi |
| 752 | done |
| 753 | |
| 754 | if test -n "$identical" && test -n "$nonidentical" |
| 755 | then |
| 756 | extras=$(git rev-list --count $identical..$nonidentical) |
| 757 | if test "$extras" -ne 0 |
| 758 | then |
| 759 | # we need to preserve history along the other branch |
| 760 | copycommit=1 |
| 761 | fi |
| 762 | fi |
| 763 | if test -n "$identical" && test -z "$copycommit" |
| 764 | then |
| 765 | echo $identical |
| 766 | else |
| 767 | copy_commit "$rev" "$tree" "$p" || exit $? |
| 768 | fi |
| 769 | } |
| 770 | |
| 771 | # Usage: ensure_clean |
| 772 | ensure_clean () { |
| 773 | assert test $# = 0 |
| 774 | if ! git diff-index HEAD --exit-code --quiet 2>&1 |
| 775 | then |
| 776 | die "fatal: working tree has modifications. Cannot add." |
| 777 | fi |
| 778 | if ! git diff-index --cached HEAD --exit-code --quiet 2>&1 |
| 779 | then |
| 780 | die "fatal: index has modifications. Cannot add." |
| 781 | fi |
| 782 | } |
| 783 | |
| 784 | # Usage: ensure_valid_ref_format REF |
| 785 | ensure_valid_ref_format () { |
| 786 | assert test $# = 1 |
| 787 | git check-ref-format "refs/heads/$1" || |
| 788 | die "fatal: '$1' does not look like a ref" |
| 789 | } |
| 790 | |
| 791 | # Usage: process_split_commit REV PARENTS |
| 792 | process_split_commit () { |
| 793 | assert test $# = 2 |
| 794 | local rev="$1" |
| 795 | local parents="$2" |
| 796 | |
| 797 | if test $indent -eq 0 |
| 798 | then |
| 799 | revcount=$(($revcount + 1)) |
| 800 | else |
| 801 | # processing commit without normal parent information; |
| 802 | # fetch from repo |
| 803 | parents=$(git rev-parse "$rev^@") |
| 804 | extracount=$(($extracount + 1)) |
| 805 | fi |
| 806 | |
| 807 | progress "$revcount/$revmax ($createcount) [$extracount]" |
| 808 | |
| 809 | debug "Processing commit: $rev" |
| 810 | local indent=$(($indent + 1)) |
| 811 | exists=$(cache_get "$rev") || exit $? |
| 812 | if test -n "$exists" |
| 813 | then |
| 814 | debug "prior: $exists" |
| 815 | return |
| 816 | fi |
| 817 | createcount=$(($createcount + 1)) |
| 818 | debug "parents: $parents" |
| 819 | check_parents $parents |
| 820 | newparents=$(cache_get $parents) || exit $? |
| 821 | debug "newparents: $newparents" |
| 822 | |
| 823 | tree=$(subtree_for_commit "$rev" "$dir") || exit $? |
| 824 | debug "tree is: $tree" |
| 825 | |
| 826 | # ugly. is there no better way to tell if this is a subtree |
| 827 | # vs. a mainline commit? Does it matter? |
| 828 | if test -z "$tree" |
| 829 | then |
| 830 | set_notree "$rev" |
| 831 | if test -n "$newparents" |
| 832 | then |
| 833 | cache_set "$rev" "$rev" |
| 834 | fi |
| 835 | return |
| 836 | fi |
| 837 | |
| 838 | newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $? |
| 839 | debug "newrev is: $newrev" |
| 840 | cache_set "$rev" "$newrev" |
| 841 | cache_set latest_new "$newrev" |
| 842 | cache_set latest_old "$rev" |
| 843 | } |
| 844 | |
| 845 | # Usage: cmd_add REV |
| 846 | # Or: cmd_add REPOSITORY REF |
| 847 | cmd_add () { |
| 848 | |
| 849 | ensure_clean |
| 850 | |
| 851 | if test $# -eq 1 |
| 852 | then |
| 853 | git rev-parse -q --verify "$1^{commit}" >/dev/null || |
| 854 | die "fatal: '$1' does not refer to a commit" |
| 855 | |
| 856 | cmd_add_commit "$@" |
| 857 | |
| 858 | elif test $# -eq 2 |
| 859 | then |
| 860 | # Technically we could accept a refspec here but we're |
| 861 | # just going to turn around and add FETCH_HEAD under the |
| 862 | # specified directory. Allowing a refspec might be |
| 863 | # misleading because we won't do anything with any other |
| 864 | # branches fetched via the refspec. |
| 865 | ensure_valid_ref_format "$2" |
| 866 | |
| 867 | cmd_add_repository "$@" |
| 868 | else |
| 869 | say >&2 "fatal: parameters were '$*'" |
| 870 | die "Provide either a commit or a repository and commit." |
| 871 | fi |
| 872 | } |
| 873 | |
| 874 | # Usage: cmd_add_repository REPOSITORY REFSPEC |
| 875 | cmd_add_repository () { |
| 876 | assert test $# = 2 |
| 877 | echo "git fetch" "$@" |
| 878 | repository=$1 |
| 879 | refspec=$2 |
| 880 | git fetch "$@" || exit $? |
| 881 | cmd_add_commit FETCH_HEAD |
| 882 | } |
| 883 | |
| 884 | # Usage: cmd_add_commit REV |
| 885 | cmd_add_commit () { |
| 886 | # The rev has already been validated by cmd_add(), we just |
| 887 | # need to normalize it. |
| 888 | assert test $# = 1 |
| 889 | rev=$(git rev-parse --verify "$1^{commit}") || exit $? |
| 890 | |
| 891 | debug "Adding $dir as '$rev'..." |
| 892 | if test -z "$arg_split_rejoin" |
| 893 | then |
| 894 | # Only bother doing this if this is a genuine 'add', |
| 895 | # not a synthetic 'add' from '--rejoin'. |
| 896 | git read-tree --prefix="$dir" $rev || exit $? |
| 897 | fi |
| 898 | git checkout -- "$dir" || exit $? |
| 899 | tree=$(git write-tree) || exit $? |
| 900 | |
| 901 | headrev=$(git rev-parse --verify HEAD) || exit $? |
| 902 | if test -n "$headrev" && test "$headrev" != "$rev" |
| 903 | then |
| 904 | headp="-p $headrev" |
| 905 | else |
| 906 | headp= |
| 907 | fi |
| 908 | |
| 909 | if test -n "$arg_addmerge_squash" |
| 910 | then |
| 911 | rev=$(new_squash_commit "" "" "$rev") || exit $? |
| 912 | commit=$(add_squashed_msg "$rev" "$dir" | |
| 913 | git commit-tree $arg_gpg_sign "$tree" $headp -p "$rev") || exit $? |
| 914 | else |
| 915 | revp=$(peel_committish "$rev") || exit $? |
| 916 | commit=$(add_msg "$dir" $headrev "$rev" | |
| 917 | git commit-tree $arg_gpg_sign "$tree" $headp -p "$revp") || exit $? |
| 918 | fi |
| 919 | git reset "$commit" || exit $? |
| 920 | |
| 921 | say >&2 "Added dir '$dir'" |
| 922 | } |
| 923 | |
| 924 | # Usage: cmd_split [REV] [REPOSITORY] |
| 925 | cmd_split () { |
| 926 | if test $# -eq 0 |
| 927 | then |
| 928 | rev=$(git rev-parse HEAD) |
| 929 | elif test $# -eq 1 || test $# -eq 2 |
| 930 | then |
| 931 | rev=$(git rev-parse -q --verify "$1^{commit}") || |
| 932 | die "fatal: '$1' does not refer to a commit" |
| 933 | else |
| 934 | die "fatal: you must provide exactly one revision, and optionally a repository. Got: '$*'" |
| 935 | fi |
| 936 | |
| 937 | # Now validate prefix against the commit, not the working tree |
| 938 | if ! git cat-file -e "$rev:$dir" 2>/dev/null |
| 939 | then |
| 940 | die "fatal: '$dir' does not exist; use 'git subtree add'" |
| 941 | fi |
| 942 | repository="" |
| 943 | if test "$#" = 2 |
| 944 | then |
| 945 | repository="$2" |
| 946 | fi |
| 947 | |
| 948 | if test -n "$arg_split_rejoin" |
| 949 | then |
| 950 | ensure_clean |
| 951 | fi |
| 952 | |
| 953 | debug "Splitting $dir..." |
| 954 | cache_setup || exit $? |
| 955 | |
| 956 | if test -n "$arg_split_onto" |
| 957 | then |
| 958 | debug "Reading history for --onto=$arg_split_onto..." |
| 959 | git rev-list $arg_split_onto | |
| 960 | while read rev |
| 961 | do |
| 962 | # the 'onto' history is already just the subdir, so |
| 963 | # any parent we find there can be used verbatim |
| 964 | debug "cache: $rev" |
| 965 | cache_set "$rev" "$rev" |
| 966 | done || exit $? |
| 967 | fi |
| 968 | |
| 969 | unrevs="$(find_existing_splits "$dir" "$rev" "$repository")" || exit $? |
| 970 | |
| 971 | # We can't restrict rev-list to only $dir here, because some of our |
| 972 | # parents have the $dir contents the root, and those won't match. |
| 973 | # (and rev-list --follow doesn't seem to solve this) |
| 974 | grl='git rev-list --topo-order --reverse --parents $rev $unrevs' |
| 975 | revmax=$(eval "$grl" | wc -l) |
| 976 | revcount=0 |
| 977 | createcount=0 |
| 978 | extracount=0 |
| 979 | eval "$grl" | |
| 980 | while read rev parents |
| 981 | do |
| 982 | process_split_commit "$rev" "$parents" |
| 983 | done || exit $? |
| 984 | |
| 985 | latest_new=$(cache_get latest_new) || exit $? |
| 986 | if test -z "$latest_new" |
| 987 | then |
| 988 | die "fatal: no new revisions were found" |
| 989 | fi |
| 990 | |
| 991 | if test -n "$arg_split_rejoin" |
| 992 | then |
| 993 | debug "Merging split branch into HEAD..." |
| 994 | latest_old=$(cache_get latest_old) || exit $? |
| 995 | arg_addmerge_message="$(rejoin_msg "$dir" "$latest_old" "$latest_new")" || exit $? |
| 996 | if test -z "$(find_latest_squash "$dir")" |
| 997 | then |
| 998 | cmd_add "$latest_new" >&2 || exit $? |
| 999 | else |
| 1000 | cmd_merge "$latest_new" >&2 || exit $? |
| 1001 | fi |
| 1002 | fi |
| 1003 | if test -n "$arg_split_branch" |
| 1004 | then |
| 1005 | if rev_exists "refs/heads/$arg_split_branch" |
| 1006 | then |
| 1007 | if ! git merge-base --is-ancestor "$arg_split_branch" "$latest_new" |
| 1008 | then |
| 1009 | die "fatal: branch '$arg_split_branch' is not an ancestor of commit '$latest_new'." |
| 1010 | fi |
| 1011 | action='Updated' |
| 1012 | else |
| 1013 | action='Created' |
| 1014 | fi |
| 1015 | git update-ref -m 'subtree split' \ |
| 1016 | "refs/heads/$arg_split_branch" "$latest_new" || exit $? |
| 1017 | say >&2 "$action branch '$arg_split_branch'" |
| 1018 | fi |
| 1019 | echo "$latest_new" |
| 1020 | exit 0 |
| 1021 | } |
| 1022 | |
| 1023 | # Usage: cmd_merge REV [REPOSITORY] |
| 1024 | cmd_merge () { |
| 1025 | if test $# -lt 1 || test $# -gt 2 |
| 1026 | then |
| 1027 | die "fatal: you must provide exactly one revision, and optionally a repository. Got: '$*'" |
| 1028 | fi |
| 1029 | |
| 1030 | rev=$(git rev-parse -q --verify "$1^{commit}") || |
| 1031 | die "fatal: '$1' does not refer to a commit" |
| 1032 | repository="" |
| 1033 | if test "$#" = 2 |
| 1034 | then |
| 1035 | repository="$2" |
| 1036 | fi |
| 1037 | ensure_clean |
| 1038 | |
| 1039 | if test -n "$arg_addmerge_squash" |
| 1040 | then |
| 1041 | first_split="$(find_latest_squash "$dir" "$repository")" || exit $? |
| 1042 | if test -z "$first_split" |
| 1043 | then |
| 1044 | die "fatal: can't squash-merge: '$dir' was never added." |
| 1045 | fi |
| 1046 | set $first_split |
| 1047 | old=$1 |
| 1048 | sub=$2 |
| 1049 | if test "$sub" = "$rev" |
| 1050 | then |
| 1051 | say >&2 "Subtree is already at commit $rev." |
| 1052 | exit 0 |
| 1053 | fi |
| 1054 | new=$(new_squash_commit "$old" "$sub" "$rev") || exit $? |
| 1055 | debug "New squash commit: $new" |
| 1056 | rev="$new" |
| 1057 | fi |
| 1058 | |
| 1059 | if test -n "$arg_addmerge_message" |
| 1060 | then |
| 1061 | git merge --no-ff -Xsubtree="$arg_prefix" \ |
| 1062 | --message="$arg_addmerge_message" $arg_gpg_sign "$rev" |
| 1063 | else |
| 1064 | git merge --no-ff -Xsubtree="$arg_prefix" $arg_gpg_sign $rev |
| 1065 | fi |
| 1066 | } |
| 1067 | |
| 1068 | # Usage: cmd_pull REPOSITORY REMOTEREF |
| 1069 | cmd_pull () { |
| 1070 | if test $# -ne 2 |
| 1071 | then |
| 1072 | die "fatal: you must provide <repository> <ref>" |
| 1073 | fi |
| 1074 | repository="$1" |
| 1075 | ref="$2" |
| 1076 | ensure_clean |
| 1077 | ensure_valid_ref_format "$ref" |
| 1078 | git fetch "$repository" "$ref" || exit $? |
| 1079 | cmd_merge FETCH_HEAD "$repository" |
| 1080 | } |
| 1081 | |
| 1082 | # Usage: cmd_push REPOSITORY [+][LOCALREV:]REMOTEREF |
| 1083 | cmd_push () { |
| 1084 | if test $# -ne 2 |
| 1085 | then |
| 1086 | die "fatal: you must provide <repository> <refspec>" |
| 1087 | fi |
| 1088 | if test -e "$dir" |
| 1089 | then |
| 1090 | repository=$1 |
| 1091 | refspec=${2#+} |
| 1092 | remoteref=${refspec#*:} |
| 1093 | if test "$remoteref" = "$refspec" |
| 1094 | then |
| 1095 | localrevname_presplit=HEAD |
| 1096 | else |
| 1097 | localrevname_presplit=${refspec%%:*} |
| 1098 | fi |
| 1099 | ensure_valid_ref_format "$remoteref" |
| 1100 | localrev_presplit=$(git rev-parse -q --verify "$localrevname_presplit^{commit}") || |
| 1101 | die "fatal: '$localrevname_presplit' does not refer to a commit" |
| 1102 | |
| 1103 | echo "git push using: " "$repository" "$refspec" |
| 1104 | localrev=$(cmd_split "$localrev_presplit" "$repository") || die |
| 1105 | git push "$repository" "$localrev":"refs/heads/$remoteref" |
| 1106 | else |
| 1107 | die "fatal: '$dir' must already exist. Try 'git subtree add'." |
| 1108 | fi |
| 1109 | } |
| 1110 | |
| 1111 | main "$@" |