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