contrib / subtree / git-subtree.shon commit subtree: refactor split of a commit into standalone method (565e4b7)
   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#
   7if test $# -eq 0
   8then
   9        set -- -h
  10fi
  11OPTS_SPEC="\
  12git subtree add   --prefix=<prefix> <commit>
  13git subtree add   --prefix=<prefix> <repository> <ref>
  14git subtree merge --prefix=<prefix> <commit>
  15git subtree pull  --prefix=<prefix> <repository> <ref>
  16git subtree push  --prefix=<prefix> <repository> <ref>
  17git subtree split --prefix=<prefix> <commit...>
  18--
  19h,help        show the help
  20q             quiet
  21d             show debug messages
  22P,prefix=     the name of the subdir to split out
  23m,message=    use the given message as the commit message for the merge commit
  24 options for 'split'
  25annotate=     add a prefix to commit message of new commits
  26b,branch=     create a new branch from the split subtree
  27ignore-joins  ignore prior --rejoin commits
  28onto=         try connecting new tree to an existing one
  29rejoin        merge the new branch back into HEAD
  30 options for 'add', 'merge', and 'pull'
  31squash        merge subtree changes as a single commit
  32"
  33eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
  34
  35PATH=$PATH:$(git --exec-path)
  36. git-sh-setup
  37
  38require_work_tree
  39
  40quiet=
  41branch=
  42debug=
  43command=
  44onto=
  45rejoin=
  46ignore_joins=
  47annotate=
  48squash=
  49message=
  50prefix=
  51
  52debug () {
  53        if test -n "$debug"
  54        then
  55                printf "%s\n" "$*" >&2
  56        fi
  57}
  58
  59say () {
  60        if test -z "$quiet"
  61        then
  62                printf "%s\n" "$*" >&2
  63        fi
  64}
  65
  66progress () {
  67        if test -z "$quiet"
  68        then
  69                printf "%s\r" "$*" >&2
  70        fi
  71}
  72
  73assert () {
  74        if ! "$@"
  75        then
  76                die "assertion failed: " "$@"
  77        fi
  78}
  79
  80
  81while test $# -gt 0
  82do
  83        opt="$1"
  84        shift
  85
  86        case "$opt" in
  87        -q)
  88                quiet=1
  89                ;;
  90        -d)
  91                debug=1
  92                ;;
  93        --annotate)
  94                annotate="$1"
  95                shift
  96                ;;
  97        --no-annotate)
  98                annotate=
  99                ;;
 100        -b)
 101                branch="$1"
 102                shift
 103                ;;
 104        -P)
 105                prefix="${1%/}"
 106                shift
 107                ;;
 108        -m)
 109                message="$1"
 110                shift
 111                ;;
 112        --no-prefix)
 113                prefix=
 114                ;;
 115        --onto)
 116                onto="$1"
 117                shift
 118                ;;
 119        --no-onto)
 120                onto=
 121                ;;
 122        --rejoin)
 123                rejoin=1
 124                ;;
 125        --no-rejoin)
 126                rejoin=
 127                ;;
 128        --ignore-joins)
 129                ignore_joins=1
 130                ;;
 131        --no-ignore-joins)
 132                ignore_joins=
 133                ;;
 134        --squash)
 135                squash=1
 136                ;;
 137        --no-squash)
 138                squash=
 139                ;;
 140        --)
 141                break
 142                ;;
 143        *)
 144                die "Unexpected option: $opt"
 145                ;;
 146        esac
 147done
 148
 149command="$1"
 150shift
 151
 152case "$command" in
 153add|merge|pull)
 154        default=
 155        ;;
 156split|push)
 157        default="--default HEAD"
 158        ;;
 159*)
 160        die "Unknown command '$command'"
 161        ;;
 162esac
 163
 164if test -z "$prefix"
 165then
 166        die "You must provide the --prefix option."
 167fi
 168
 169case "$command" in
 170add)
 171        test -e "$prefix" &&
 172                die "prefix '$prefix' already exists."
 173        ;;
 174*)
 175        test -e "$prefix" ||
 176                die "'$prefix' does not exist; use 'git subtree add'"
 177        ;;
 178esac
 179
 180dir="$(dirname "$prefix/.")"
 181
 182if test "$command" != "pull" &&
 183                test "$command" != "add" &&
 184                test "$command" != "push"
 185then
 186        revs=$(git rev-parse $default --revs-only "$@") || exit $?
 187        dirs=$(git rev-parse --no-revs --no-flags "$@") || exit $?
 188        if test -n "$dirs"
 189        then
 190                die "Error: Use --prefix instead of bare filenames."
 191        fi
 192fi
 193
 194debug "command: {$command}"
 195debug "quiet: {$quiet}"
 196debug "revs: {$revs}"
 197debug "dir: {$dir}"
 198debug "opts: {$*}"
 199debug
 200
 201cache_setup () {
 202        cachedir="$GIT_DIR/subtree-cache/$$"
 203        rm -rf "$cachedir" ||
 204                die "Can't delete old cachedir: $cachedir"
 205        mkdir -p "$cachedir" ||
 206                die "Can't create new cachedir: $cachedir"
 207        mkdir -p "$cachedir/notree" ||
 208                die "Can't create new cachedir: $cachedir/notree"
 209        debug "Using cachedir: $cachedir" >&2
 210}
 211
 212cache_get () {
 213        for oldrev in "$@"
 214        do
 215                if test -r "$cachedir/$oldrev"
 216                then
 217                        read newrev <"$cachedir/$oldrev"
 218                        echo $newrev
 219                fi
 220        done
 221}
 222
 223cache_miss () {
 224        for oldrev in "$@"
 225        do
 226                if ! test -r "$cachedir/$oldrev"
 227                then
 228                        echo $oldrev
 229                fi
 230        done
 231}
 232
 233check_parents () {
 234        missed=$(cache_miss "$@")
 235        for miss in $missed
 236        do
 237                if ! test -r "$cachedir/notree/$miss"
 238                then
 239                        debug "  incorrect order: $miss"
 240                fi
 241        done
 242}
 243
 244set_notree () {
 245        echo "1" > "$cachedir/notree/$1"
 246}
 247
 248cache_set () {
 249        oldrev="$1"
 250        newrev="$2"
 251        if test "$oldrev" != "latest_old" &&
 252                test "$oldrev" != "latest_new" &&
 253                test -e "$cachedir/$oldrev"
 254        then
 255                die "cache for $oldrev already exists!"
 256        fi
 257        echo "$newrev" >"$cachedir/$oldrev"
 258}
 259
 260rev_exists () {
 261        if git rev-parse "$1" >/dev/null 2>&1
 262        then
 263                return 0
 264        else
 265                return 1
 266        fi
 267}
 268
 269rev_is_descendant_of_branch () {
 270        newrev="$1"
 271        branch="$2"
 272        branch_hash=$(git rev-parse "$branch")
 273        match=$(git rev-list -1 "$branch_hash" "^$newrev")
 274
 275        if test -z "$match"
 276        then
 277                return 0
 278        else
 279                return 1
 280        fi
 281}
 282
 283# if a commit doesn't have a parent, this might not work.  But we only want
 284# to remove the parent from the rev-list, and since it doesn't exist, it won't
 285# be there anyway, so do nothing in that case.
 286try_remove_previous () {
 287        if rev_exists "$1^"
 288        then
 289                echo "^$1^"
 290        fi
 291}
 292
 293find_latest_squash () {
 294        debug "Looking for latest squash ($dir)..."
 295        dir="$1"
 296        sq=
 297        main=
 298        sub=
 299        git log --grep="^git-subtree-dir: $dir/*\$" \
 300                --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD |
 301        while read a b junk
 302        do
 303                debug "$a $b $junk"
 304                debug "{{$sq/$main/$sub}}"
 305                case "$a" in
 306                START)
 307                        sq="$b"
 308                        ;;
 309                git-subtree-mainline:)
 310                        main="$b"
 311                        ;;
 312                git-subtree-split:)
 313                        sub="$(git rev-parse "$b^0")" ||
 314                        die "could not rev-parse split hash $b from commit $sq"
 315                        ;;
 316                END)
 317                        if test -n "$sub"
 318                        then
 319                                if test -n "$main"
 320                                then
 321                                        # a rejoin commit?
 322                                        # Pretend its sub was a squash.
 323                                        sq="$sub"
 324                                fi
 325                                debug "Squash found: $sq $sub"
 326                                echo "$sq" "$sub"
 327                                break
 328                        fi
 329                        sq=
 330                        main=
 331                        sub=
 332                        ;;
 333                esac
 334        done
 335}
 336
 337find_existing_splits () {
 338        debug "Looking for prior splits..."
 339        dir="$1"
 340        revs="$2"
 341        main=
 342        sub=
 343        git log --grep="^git-subtree-dir: $dir/*\$" \
 344                --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs |
 345        while read a b junk
 346        do
 347                case "$a" in
 348                START)
 349                        sq="$b"
 350                        ;;
 351                git-subtree-mainline:)
 352                        main="$b"
 353                        ;;
 354                git-subtree-split:)
 355                        sub="$(git rev-parse "$b^0")" ||
 356                        die "could not rev-parse split hash $b from commit $sq"
 357                        ;;
 358                END)
 359                        debug "  Main is: '$main'"
 360                        if test -z "$main" -a -n "$sub"
 361                        then
 362                                # squash commits refer to a subtree
 363                                debug "  Squash: $sq from $sub"
 364                                cache_set "$sq" "$sub"
 365                        fi
 366                        if test -n "$main" -a -n "$sub"
 367                        then
 368                                debug "  Prior: $main -> $sub"
 369                                cache_set $main $sub
 370                                cache_set $sub $sub
 371                                try_remove_previous "$main"
 372                                try_remove_previous "$sub"
 373                        fi
 374                        main=
 375                        sub=
 376                        ;;
 377                esac
 378        done
 379}
 380
 381copy_commit () {
 382        # We're going to set some environment vars here, so
 383        # do it in a subshell to get rid of them safely later
 384        debug copy_commit "{$1}" "{$2}" "{$3}"
 385        git log -1 --no-show-signature --pretty=format:'%an%n%ae%n%aD%n%cn%n%ce%n%cD%n%B' "$1" |
 386        (
 387                read GIT_AUTHOR_NAME
 388                read GIT_AUTHOR_EMAIL
 389                read GIT_AUTHOR_DATE
 390                read GIT_COMMITTER_NAME
 391                read GIT_COMMITTER_EMAIL
 392                read GIT_COMMITTER_DATE
 393                export  GIT_AUTHOR_NAME \
 394                        GIT_AUTHOR_EMAIL \
 395                        GIT_AUTHOR_DATE \
 396                        GIT_COMMITTER_NAME \
 397                        GIT_COMMITTER_EMAIL \
 398                        GIT_COMMITTER_DATE
 399                (
 400                        printf "%s" "$annotate"
 401                        cat
 402                ) |
 403                git commit-tree "$2" $3  # reads the rest of stdin
 404        ) || die "Can't copy commit $1"
 405}
 406
 407add_msg () {
 408        dir="$1"
 409        latest_old="$2"
 410        latest_new="$3"
 411        if test -n "$message"
 412        then
 413                commit_message="$message"
 414        else
 415                commit_message="Add '$dir/' from commit '$latest_new'"
 416        fi
 417        cat <<-EOF
 418                $commit_message
 419
 420                git-subtree-dir: $dir
 421                git-subtree-mainline: $latest_old
 422                git-subtree-split: $latest_new
 423        EOF
 424}
 425
 426add_squashed_msg () {
 427        if test -n "$message"
 428        then
 429                echo "$message"
 430        else
 431                echo "Merge commit '$1' as '$2'"
 432        fi
 433}
 434
 435rejoin_msg () {
 436        dir="$1"
 437        latest_old="$2"
 438        latest_new="$3"
 439        if test -n "$message"
 440        then
 441                commit_message="$message"
 442        else
 443                commit_message="Split '$dir/' into commit '$latest_new'"
 444        fi
 445        cat <<-EOF
 446                $commit_message
 447
 448                git-subtree-dir: $dir
 449                git-subtree-mainline: $latest_old
 450                git-subtree-split: $latest_new
 451        EOF
 452}
 453
 454squash_msg () {
 455        dir="$1"
 456        oldsub="$2"
 457        newsub="$3"
 458        newsub_short=$(git rev-parse --short "$newsub")
 459
 460        if test -n "$oldsub"
 461        then
 462                oldsub_short=$(git rev-parse --short "$oldsub")
 463                echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short"
 464                echo
 465                git log --no-show-signature --pretty=tformat:'%h %s' "$oldsub..$newsub"
 466                git log --no-show-signature --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub"
 467        else
 468                echo "Squashed '$dir/' content from commit $newsub_short"
 469        fi
 470
 471        echo
 472        echo "git-subtree-dir: $dir"
 473        echo "git-subtree-split: $newsub"
 474}
 475
 476toptree_for_commit () {
 477        commit="$1"
 478        git rev-parse --verify "$commit^{tree}" || exit $?
 479}
 480
 481subtree_for_commit () {
 482        commit="$1"
 483        dir="$2"
 484        git ls-tree "$commit" -- "$dir" |
 485        while read mode type tree name
 486        do
 487                assert test "$name" = "$dir"
 488                assert test "$type" = "tree" -o "$type" = "commit"
 489                test "$type" = "commit" && continue  # ignore submodules
 490                echo $tree
 491                break
 492        done
 493}
 494
 495tree_changed () {
 496        tree=$1
 497        shift
 498        if test $# -ne 1
 499        then
 500                return 0   # weird parents, consider it changed
 501        else
 502                ptree=$(toptree_for_commit $1)
 503                if test "$ptree" != "$tree"
 504                then
 505                        return 0   # changed
 506                else
 507                        return 1   # not changed
 508                fi
 509        fi
 510}
 511
 512new_squash_commit () {
 513        old="$1"
 514        oldsub="$2"
 515        newsub="$3"
 516        tree=$(toptree_for_commit $newsub) || exit $?
 517        if test -n "$old"
 518        then
 519                squash_msg "$dir" "$oldsub" "$newsub" |
 520                git commit-tree "$tree" -p "$old" || exit $?
 521        else
 522                squash_msg "$dir" "" "$newsub" |
 523                git commit-tree "$tree" || exit $?
 524        fi
 525}
 526
 527copy_or_skip () {
 528        rev="$1"
 529        tree="$2"
 530        newparents="$3"
 531        assert test -n "$tree"
 532
 533        identical=
 534        nonidentical=
 535        p=
 536        gotparents=
 537        for parent in $newparents
 538        do
 539                ptree=$(toptree_for_commit $parent) || exit $?
 540                test -z "$ptree" && continue
 541                if test "$ptree" = "$tree"
 542                then
 543                        # an identical parent could be used in place of this rev.
 544                        identical="$parent"
 545                else
 546                        nonidentical="$parent"
 547                fi
 548
 549                # sometimes both old parents map to the same newparent;
 550                # eliminate duplicates
 551                is_new=1
 552                for gp in $gotparents
 553                do
 554                        if test "$gp" = "$parent"
 555                        then
 556                                is_new=
 557                                break
 558                        fi
 559                done
 560                if test -n "$is_new"
 561                then
 562                        gotparents="$gotparents $parent"
 563                        p="$p -p $parent"
 564                fi
 565        done
 566
 567        copycommit=
 568        if test -n "$identical" && test -n "$nonidentical"
 569        then
 570                extras=$(git rev-list --count $identical..$nonidentical)
 571                if test "$extras" -ne 0
 572                then
 573                        # we need to preserve history along the other branch
 574                        copycommit=1
 575                fi
 576        fi
 577        if test -n "$identical" && test -z "$copycommit"
 578        then
 579                echo $identical
 580        else
 581                copy_commit "$rev" "$tree" "$p" || exit $?
 582        fi
 583}
 584
 585ensure_clean () {
 586        if ! git diff-index HEAD --exit-code --quiet 2>&1
 587        then
 588                die "Working tree has modifications.  Cannot add."
 589        fi
 590        if ! git diff-index --cached HEAD --exit-code --quiet 2>&1
 591        then
 592                die "Index has modifications.  Cannot add."
 593        fi
 594}
 595
 596ensure_valid_ref_format () {
 597        git check-ref-format "refs/heads/$1" ||
 598                die "'$1' does not look like a ref"
 599}
 600
 601process_split_commit () {
 602        local rev="$1"
 603        local parents="$2"
 604        revcount=$(($revcount + 1))
 605        progress "$revcount/$revmax ($createcount)"
 606        debug "Processing commit: $rev"
 607        exists=$(cache_get "$rev")
 608        if test -n "$exists"
 609        then
 610                debug "  prior: $exists"
 611                return
 612        fi
 613        createcount=$(($createcount + 1))
 614        debug "  parents: $parents"
 615        newparents=$(cache_get $parents)
 616        debug "  newparents: $newparents"
 617
 618        tree=$(subtree_for_commit "$rev" "$dir")
 619        debug "  tree is: $tree"
 620
 621        check_parents $parents
 622
 623        # ugly.  is there no better way to tell if this is a subtree
 624        # vs. a mainline commit?  Does it matter?
 625        if test -z "$tree"
 626        then
 627                set_notree "$rev"
 628                if test -n "$newparents"
 629                then
 630                        cache_set "$rev" "$rev"
 631                fi
 632                return
 633        fi
 634
 635        newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
 636        debug "  newrev is: $newrev"
 637        cache_set "$rev" "$newrev"
 638        cache_set latest_new "$newrev"
 639        cache_set latest_old "$rev"
 640}
 641
 642cmd_add () {
 643        if test -e "$dir"
 644        then
 645                die "'$dir' already exists.  Cannot add."
 646        fi
 647
 648        ensure_clean
 649
 650        if test $# -eq 1
 651        then
 652                git rev-parse -q --verify "$1^{commit}" >/dev/null ||
 653                        die "'$1' does not refer to a commit"
 654
 655                cmd_add_commit "$@"
 656
 657        elif test $# -eq 2
 658        then
 659                # Technically we could accept a refspec here but we're
 660                # just going to turn around and add FETCH_HEAD under the
 661                # specified directory.  Allowing a refspec might be
 662                # misleading because we won't do anything with any other
 663                # branches fetched via the refspec.
 664                ensure_valid_ref_format "$2"
 665
 666                cmd_add_repository "$@"
 667        else
 668                say "error: parameters were '$@'"
 669                die "Provide either a commit or a repository and commit."
 670        fi
 671}
 672
 673cmd_add_repository () {
 674        echo "git fetch" "$@"
 675        repository=$1
 676        refspec=$2
 677        git fetch "$@" || exit $?
 678        revs=FETCH_HEAD
 679        set -- $revs
 680        cmd_add_commit "$@"
 681}
 682
 683cmd_add_commit () {
 684        revs=$(git rev-parse $default --revs-only "$@") || exit $?
 685        set -- $revs
 686        rev="$1"
 687
 688        debug "Adding $dir as '$rev'..."
 689        git read-tree --prefix="$dir" $rev || exit $?
 690        git checkout -- "$dir" || exit $?
 691        tree=$(git write-tree) || exit $?
 692
 693        headrev=$(git rev-parse HEAD) || exit $?
 694        if test -n "$headrev" && test "$headrev" != "$rev"
 695        then
 696                headp="-p $headrev"
 697        else
 698                headp=
 699        fi
 700
 701        if test -n "$squash"
 702        then
 703                rev=$(new_squash_commit "" "" "$rev") || exit $?
 704                commit=$(add_squashed_msg "$rev" "$dir" |
 705                        git commit-tree "$tree" $headp -p "$rev") || exit $?
 706        else
 707                revp=$(peel_committish "$rev") &&
 708                commit=$(add_msg "$dir" $headrev "$rev" |
 709                        git commit-tree "$tree" $headp -p "$revp") || exit $?
 710        fi
 711        git reset "$commit" || exit $?
 712
 713        say "Added dir '$dir'"
 714}
 715
 716cmd_split () {
 717        debug "Splitting $dir..."
 718        cache_setup || exit $?
 719
 720        if test -n "$onto"
 721        then
 722                debug "Reading history for --onto=$onto..."
 723                git rev-list $onto |
 724                while read rev
 725                do
 726                        # the 'onto' history is already just the subdir, so
 727                        # any parent we find there can be used verbatim
 728                        debug "  cache: $rev"
 729                        cache_set "$rev" "$rev"
 730                done
 731        fi
 732
 733        if test -n "$ignore_joins"
 734        then
 735                unrevs=
 736        else
 737                unrevs="$(find_existing_splits "$dir" "$revs")"
 738        fi
 739
 740        # We can't restrict rev-list to only $dir here, because some of our
 741        # parents have the $dir contents the root, and those won't match.
 742        # (and rev-list --follow doesn't seem to solve this)
 743        grl='git rev-list --topo-order --reverse --parents $revs $unrevs'
 744        revmax=$(eval "$grl" | wc -l)
 745        revcount=0
 746        createcount=0
 747        eval "$grl" |
 748        while read rev parents
 749        do
 750                process_split_commit "$rev" "$parents"
 751        done || exit $?
 752
 753        latest_new=$(cache_get latest_new)
 754        if test -z "$latest_new"
 755        then
 756                die "No new revisions were found"
 757        fi
 758
 759        if test -n "$rejoin"
 760        then
 761                debug "Merging split branch into HEAD..."
 762                latest_old=$(cache_get latest_old)
 763                git merge -s ours \
 764                        --allow-unrelated-histories \
 765                        -m "$(rejoin_msg "$dir" "$latest_old" "$latest_new")" \
 766                        "$latest_new" >&2 || exit $?
 767        fi
 768        if test -n "$branch"
 769        then
 770                if rev_exists "refs/heads/$branch"
 771                then
 772                        if ! rev_is_descendant_of_branch "$latest_new" "$branch"
 773                        then
 774                                die "Branch '$branch' is not an ancestor of commit '$latest_new'."
 775                        fi
 776                        action='Updated'
 777                else
 778                        action='Created'
 779                fi
 780                git update-ref -m 'subtree split' \
 781                        "refs/heads/$branch" "$latest_new" || exit $?
 782                say "$action branch '$branch'"
 783        fi
 784        echo "$latest_new"
 785        exit 0
 786}
 787
 788cmd_merge () {
 789        revs=$(git rev-parse $default --revs-only "$@") || exit $?
 790        ensure_clean
 791
 792        set -- $revs
 793        if test $# -ne 1
 794        then
 795                die "You must provide exactly one revision.  Got: '$revs'"
 796        fi
 797        rev="$1"
 798
 799        if test -n "$squash"
 800        then
 801                first_split="$(find_latest_squash "$dir")"
 802                if test -z "$first_split"
 803                then
 804                        die "Can't squash-merge: '$dir' was never added."
 805                fi
 806                set $first_split
 807                old=$1
 808                sub=$2
 809                if test "$sub" = "$rev"
 810                then
 811                        say "Subtree is already at commit $rev."
 812                        exit 0
 813                fi
 814                new=$(new_squash_commit "$old" "$sub" "$rev") || exit $?
 815                debug "New squash commit: $new"
 816                rev="$new"
 817        fi
 818
 819        version=$(git version)
 820        if test "$version" \< "git version 1.7"
 821        then
 822                if test -n "$message"
 823                then
 824                        git merge -s subtree --message="$message" "$rev"
 825                else
 826                        git merge -s subtree "$rev"
 827                fi
 828        else
 829                if test -n "$message"
 830                then
 831                        git merge -Xsubtree="$prefix" \
 832                                --message="$message" "$rev"
 833                else
 834                        git merge -Xsubtree="$prefix" $rev
 835                fi
 836        fi
 837}
 838
 839cmd_pull () {
 840        if test $# -ne 2
 841        then
 842                die "You must provide <repository> <ref>"
 843        fi
 844        ensure_clean
 845        ensure_valid_ref_format "$2"
 846        git fetch "$@" || exit $?
 847        revs=FETCH_HEAD
 848        set -- $revs
 849        cmd_merge "$@"
 850}
 851
 852cmd_push () {
 853        if test $# -ne 2
 854        then
 855                die "You must provide <repository> <ref>"
 856        fi
 857        ensure_valid_ref_format "$2"
 858        if test -e "$dir"
 859        then
 860                repository=$1
 861                refspec=$2
 862                echo "git push using: " "$repository" "$refspec"
 863                localrev=$(git subtree split --prefix="$prefix") || die
 864                git push "$repository" "$localrev":"refs/heads/$refspec"
 865        else
 866                die "'$dir' must already exist. Try 'git subtree add'."
 867        fi
 868}
 869
 870"cmd_$command" "$@"