contrib / subtree / git-subtree.shon commit Merge branch 'cb/ttk-style' of git-gui into cb/git-gui-ttk-style (4891961)
   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                --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                --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 --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 --pretty=tformat:'%h %s' "$oldsub..$newsub"
 466                git log --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 log -1 --pretty=format:'%T' "$commit" -- || 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
 601cmd_add () {
 602        if test -e "$dir"
 603        then
 604                die "'$dir' already exists.  Cannot add."
 605        fi
 606
 607        ensure_clean
 608
 609        if test $# -eq 1
 610        then
 611                git rev-parse -q --verify "$1^{commit}" >/dev/null ||
 612                        die "'$1' does not refer to a commit"
 613
 614                cmd_add_commit "$@"
 615
 616        elif test $# -eq 2
 617        then
 618                # Technically we could accept a refspec here but we're
 619                # just going to turn around and add FETCH_HEAD under the
 620                # specified directory.  Allowing a refspec might be
 621                # misleading because we won't do anything with any other
 622                # branches fetched via the refspec.
 623                ensure_valid_ref_format "$2"
 624
 625                cmd_add_repository "$@"
 626        else
 627                say "error: parameters were '$@'"
 628                die "Provide either a commit or a repository and commit."
 629        fi
 630}
 631
 632cmd_add_repository () {
 633        echo "git fetch" "$@"
 634        repository=$1
 635        refspec=$2
 636        git fetch "$@" || exit $?
 637        revs=FETCH_HEAD
 638        set -- $revs
 639        cmd_add_commit "$@"
 640}
 641
 642cmd_add_commit () {
 643        revs=$(git rev-parse $default --revs-only "$@") || exit $?
 644        set -- $revs
 645        rev="$1"
 646
 647        debug "Adding $dir as '$rev'..."
 648        git read-tree --prefix="$dir" $rev || exit $?
 649        git checkout -- "$dir" || exit $?
 650        tree=$(git write-tree) || exit $?
 651
 652        headrev=$(git rev-parse HEAD) || exit $?
 653        if test -n "$headrev" && test "$headrev" != "$rev"
 654        then
 655                headp="-p $headrev"
 656        else
 657                headp=
 658        fi
 659
 660        if test -n "$squash"
 661        then
 662                rev=$(new_squash_commit "" "" "$rev") || exit $?
 663                commit=$(add_squashed_msg "$rev" "$dir" |
 664                        git commit-tree "$tree" $headp -p "$rev") || exit $?
 665        else
 666                revp=$(peel_committish "$rev") &&
 667                commit=$(add_msg "$dir" $headrev "$rev" |
 668                        git commit-tree "$tree" $headp -p "$revp") || exit $?
 669        fi
 670        git reset "$commit" || exit $?
 671
 672        say "Added dir '$dir'"
 673}
 674
 675cmd_split () {
 676        debug "Splitting $dir..."
 677        cache_setup || exit $?
 678
 679        if test -n "$onto"
 680        then
 681                debug "Reading history for --onto=$onto..."
 682                git rev-list $onto |
 683                while read rev
 684                do
 685                        # the 'onto' history is already just the subdir, so
 686                        # any parent we find there can be used verbatim
 687                        debug "  cache: $rev"
 688                        cache_set "$rev" "$rev"
 689                done
 690        fi
 691
 692        if test -n "$ignore_joins"
 693        then
 694                unrevs=
 695        else
 696                unrevs="$(find_existing_splits "$dir" "$revs")"
 697        fi
 698
 699        # We can't restrict rev-list to only $dir here, because some of our
 700        # parents have the $dir contents the root, and those won't match.
 701        # (and rev-list --follow doesn't seem to solve this)
 702        grl='git rev-list --topo-order --reverse --parents $revs $unrevs'
 703        revmax=$(eval "$grl" | wc -l)
 704        revcount=0
 705        createcount=0
 706        eval "$grl" |
 707        while read rev parents
 708        do
 709                revcount=$(($revcount + 1))
 710                progress "$revcount/$revmax ($createcount)"
 711                debug "Processing commit: $rev"
 712                exists=$(cache_get "$rev")
 713                if test -n "$exists"
 714                then
 715                        debug "  prior: $exists"
 716                        continue
 717                fi
 718                createcount=$(($createcount + 1))
 719                debug "  parents: $parents"
 720                newparents=$(cache_get $parents)
 721                debug "  newparents: $newparents"
 722
 723                tree=$(subtree_for_commit "$rev" "$dir")
 724                debug "  tree is: $tree"
 725
 726                check_parents $parents
 727
 728                # ugly.  is there no better way to tell if this is a subtree
 729                # vs. a mainline commit?  Does it matter?
 730                if test -z "$tree"
 731                then
 732                        set_notree "$rev"
 733                        if test -n "$newparents"
 734                        then
 735                                cache_set "$rev" "$rev"
 736                        fi
 737                        continue
 738                fi
 739
 740                newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
 741                debug "  newrev is: $newrev"
 742                cache_set "$rev" "$newrev"
 743                cache_set latest_new "$newrev"
 744                cache_set latest_old "$rev"
 745        done || exit $?
 746
 747        latest_new=$(cache_get latest_new)
 748        if test -z "$latest_new"
 749        then
 750                die "No new revisions were found"
 751        fi
 752
 753        if test -n "$rejoin"
 754        then
 755                debug "Merging split branch into HEAD..."
 756                latest_old=$(cache_get latest_old)
 757                git merge -s ours \
 758                        --allow-unrelated-histories \
 759                        -m "$(rejoin_msg "$dir" "$latest_old" "$latest_new")" \
 760                        "$latest_new" >&2 || exit $?
 761        fi
 762        if test -n "$branch"
 763        then
 764                if rev_exists "refs/heads/$branch"
 765                then
 766                        if ! rev_is_descendant_of_branch "$latest_new" "$branch"
 767                        then
 768                                die "Branch '$branch' is not an ancestor of commit '$latest_new'."
 769                        fi
 770                        action='Updated'
 771                else
 772                        action='Created'
 773                fi
 774                git update-ref -m 'subtree split' \
 775                        "refs/heads/$branch" "$latest_new" || exit $?
 776                say "$action branch '$branch'"
 777        fi
 778        echo "$latest_new"
 779        exit 0
 780}
 781
 782cmd_merge () {
 783        revs=$(git rev-parse $default --revs-only "$@") || exit $?
 784        ensure_clean
 785
 786        set -- $revs
 787        if test $# -ne 1
 788        then
 789                die "You must provide exactly one revision.  Got: '$revs'"
 790        fi
 791        rev="$1"
 792
 793        if test -n "$squash"
 794        then
 795                first_split="$(find_latest_squash "$dir")"
 796                if test -z "$first_split"
 797                then
 798                        die "Can't squash-merge: '$dir' was never added."
 799                fi
 800                set $first_split
 801                old=$1
 802                sub=$2
 803                if test "$sub" = "$rev"
 804                then
 805                        say "Subtree is already at commit $rev."
 806                        exit 0
 807                fi
 808                new=$(new_squash_commit "$old" "$sub" "$rev") || exit $?
 809                debug "New squash commit: $new"
 810                rev="$new"
 811        fi
 812
 813        version=$(git version)
 814        if test "$version" \< "git version 1.7"
 815        then
 816                if test -n "$message"
 817                then
 818                        git merge -s subtree --message="$message" "$rev"
 819                else
 820                        git merge -s subtree "$rev"
 821                fi
 822        else
 823                if test -n "$message"
 824                then
 825                        git merge -Xsubtree="$prefix" \
 826                                --message="$message" "$rev"
 827                else
 828                        git merge -Xsubtree="$prefix" $rev
 829                fi
 830        fi
 831}
 832
 833cmd_pull () {
 834        if test $# -ne 2
 835        then
 836                die "You must provide <repository> <ref>"
 837        fi
 838        ensure_clean
 839        ensure_valid_ref_format "$2"
 840        git fetch "$@" || exit $?
 841        revs=FETCH_HEAD
 842        set -- $revs
 843        cmd_merge "$@"
 844}
 845
 846cmd_push () {
 847        if test $# -ne 2
 848        then
 849                die "You must provide <repository> <ref>"
 850        fi
 851        ensure_valid_ref_format "$2"
 852        if test -e "$dir"
 853        then
 854                repository=$1
 855                refspec=$2
 856                echo "git push using: " "$repository" "$refspec"
 857                localrev=$(git subtree split --prefix="$prefix") || die
 858                git push "$repository" "$localrev":"refs/heads/$refspec"
 859        else
 860                die "'$dir' must already exist. Try 'git subtree add'."
 861        fi
 862}
 863
 864"cmd_$command" "$@"