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