git-checkout.shon commit expose a helper function peel_to_type(). (8177631)
   1#!/bin/sh
   2
   3OPTIONS_KEEPDASHDASH=t
   4OPTIONS_SPEC="\
   5git-checkout [options] [<branch>] [<paths>...]
   6--
   7b=          create a new branch started at <branch>
   8l           create the new branch's reflog
   9track       arrange that the new branch tracks the remote branch
  10f           proceed even if the index or working tree is not HEAD
  11m           merge local modifications into the new branch
  12q,quiet     be quiet
  13"
  14SUBDIRECTORY_OK=Sometimes
  15. git-sh-setup
  16require_work_tree
  17
  18old_name=HEAD
  19old=$(git rev-parse --verify $old_name 2>/dev/null)
  20oldbranch=$(git symbolic-ref $old_name 2>/dev/null)
  21new=
  22new_name=
  23force=
  24branch=
  25track=
  26newbranch=
  27newbranch_log=
  28merge=
  29quiet=
  30v=-v
  31LF='
  32'
  33
  34while test $# != 0; do
  35        case "$1" in
  36        -b)
  37                shift
  38                newbranch="$1"
  39                [ -z "$newbranch" ] &&
  40                        die "git checkout: -b needs a branch name"
  41                git show-ref --verify --quiet -- "refs/heads/$newbranch" &&
  42                        die "git checkout: branch $newbranch already exists"
  43                git check-ref-format "heads/$newbranch" ||
  44                        die "git checkout: we do not like '$newbranch' as a branch name."
  45                ;;
  46        -l)
  47                newbranch_log=-l
  48                ;;
  49        --track|--no-track)
  50                track="$1"
  51                ;;
  52        -f)
  53                force=1
  54                ;;
  55        -m)
  56                merge=1
  57                ;;
  58        -q|--quiet)
  59                quiet=1
  60                v=
  61                ;;
  62        --)
  63                shift
  64                break
  65                ;;
  66        *)
  67                usage
  68                ;;
  69        esac
  70        shift
  71done
  72
  73arg="$1"
  74rev=$(git rev-parse --verify "$arg" 2>/dev/null)
  75if rev=$(git rev-parse --verify "$rev^0" 2>/dev/null)
  76then
  77        [ -z "$rev" ] && die "unknown flag $arg"
  78        new_name="$arg"
  79        if git show-ref --verify --quiet -- "refs/heads/$arg"
  80        then
  81                rev=$(git rev-parse --verify "refs/heads/$arg^0")
  82                branch="$arg"
  83        fi
  84        new="$rev"
  85        shift
  86elif rev=$(git rev-parse --verify "$rev^{tree}" 2>/dev/null)
  87then
  88        # checking out selected paths from a tree-ish.
  89        new="$rev"
  90        new_name="$rev^{tree}"
  91        shift
  92fi
  93[ "$1" = "--" ] && shift
  94
  95case "$newbranch,$track" in
  96,--*)
  97        die "git checkout: --track and --no-track require -b"
  98esac
  99
 100case "$force$merge" in
 10111)
 102        die "git checkout: -f and -m are incompatible"
 103esac
 104
 105# The behaviour of the command with and without explicit path
 106# parameters is quite different.
 107#
 108# Without paths, we are checking out everything in the work tree,
 109# possibly switching branches.  This is the traditional behaviour.
 110#
 111# With paths, we are _never_ switching branch, but checking out
 112# the named paths from either index (when no rev is given),
 113# or the named tree-ish (when rev is given).
 114
 115if test "$#" -ge 1
 116then
 117        hint=
 118        if test "$#" -eq 1
 119        then
 120                hint="
 121Did you intend to checkout '$@' which can not be resolved as commit?"
 122        fi
 123        if test '' != "$newbranch$force$merge"
 124        then
 125                die "git checkout: updating paths is incompatible with switching branches/forcing$hint"
 126        fi
 127        if test '' != "$new"
 128        then
 129                # from a specific tree-ish; note that this is for
 130                # rescuing paths and is never meant to remove what
 131                # is not in the named tree-ish.
 132                git ls-tree --full-name -r "$new" "$@" |
 133                git update-index --index-info || exit $?
 134        fi
 135
 136        # Make sure the request is about existing paths.
 137        git ls-files --full-name --error-unmatch -- "$@" >/dev/null || exit
 138        git ls-files --full-name -- "$@" |
 139                (cd_to_toplevel && git checkout-index -f -u --stdin)
 140
 141        # Run a post-checkout hook -- the HEAD does not change so the
 142        # current HEAD is passed in for both args
 143        if test -x "$GIT_DIR"/hooks/post-checkout; then
 144            "$GIT_DIR"/hooks/post-checkout $old $old 0
 145        fi
 146
 147        exit $?
 148else
 149        # Make sure we did not fall back on $arg^{tree} codepath
 150        # since we are not checking out from an arbitrary tree-ish,
 151        # but switching branches.
 152        if test '' != "$new"
 153        then
 154                git rev-parse --verify "$new^{commit}" >/dev/null 2>&1 ||
 155                die "Cannot switch branch to a non-commit."
 156        fi
 157fi
 158
 159# We are switching branches and checking out trees, so
 160# we *NEED* to be at the toplevel.
 161cd_to_toplevel
 162
 163[ -z "$new" ] && new=$old && new_name="$old_name"
 164
 165# If we don't have an existing branch that we're switching to,
 166# and we don't have a new branch name for the target we
 167# are switching to, then we are detaching our HEAD from any
 168# branch.  However, if "git checkout HEAD" detaches the HEAD
 169# from the current branch, even though that may be logically
 170# correct, it feels somewhat funny.  More importantly, we do not
 171# want "git checkout" nor "git checkout -f" to detach HEAD.
 172
 173detached=
 174detach_warn=
 175
 176describe_detached_head () {
 177        test -n "$quiet" || {
 178                printf >&2 "$1 "
 179                GIT_PAGER= git log >&2 -1 --pretty=oneline --abbrev-commit "$2" --
 180        }
 181}
 182
 183if test -z "$branch$newbranch" && test "$new_name" != "$old_name"
 184then
 185        detached="$new"
 186        if test -n "$oldbranch" && test -z "$quiet"
 187        then
 188                detach_warn="Note: moving to \"$new_name\" which isn't a local branch
 189If you want to create a new branch from this checkout, you may do so
 190(now or later) by using -b with the checkout command again. Example:
 191  git checkout -b <new_branch_name>"
 192        fi
 193elif test -z "$oldbranch" && test "$new" != "$old"
 194then
 195        describe_detached_head 'Previous HEAD position was' "$old"
 196fi
 197
 198if [ "X$old" = X ]
 199then
 200        if test -z "$quiet"
 201        then
 202                echo >&2 "warning: You appear to be on a branch yet to be born."
 203                echo >&2 "warning: Forcing checkout of $new_name."
 204        fi
 205        force=1
 206fi
 207
 208if [ "$force" ]
 209then
 210    git read-tree $v --reset -u $new
 211else
 212    git update-index --refresh >/dev/null
 213    merge_error=$(git read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || (
 214        case "$merge" in
 215        '')
 216                echo >&2 "$merge_error"
 217                exit 1 ;;
 218        esac
 219
 220        # Match the index to the working tree, and do a three-way.
 221        git diff-files --name-only | git update-index --remove --stdin &&
 222        work=`git write-tree` &&
 223        git read-tree $v --reset -u $new || exit
 224
 225        eval GITHEAD_$new='${new_name:-${branch:-$new}}' &&
 226        eval GITHEAD_$work=local &&
 227        export GITHEAD_$new GITHEAD_$work &&
 228        git merge-recursive $old -- $new $work
 229
 230        # Do not register the cleanly merged paths in the index yet.
 231        # this is not a real merge before committing, but just carrying
 232        # the working tree changes along.
 233        unmerged=`git ls-files -u`
 234        git read-tree $v --reset $new
 235        case "$unmerged" in
 236        '')     ;;
 237        *)
 238                (
 239                        z40=0000000000000000000000000000000000000000
 240                        echo "$unmerged" |
 241                        sed -e 's/^[0-7]* [0-9a-f]* /'"0 $z40 /"
 242                        echo "$unmerged"
 243                ) | git update-index --index-info
 244                ;;
 245        esac
 246        exit 0
 247    )
 248    saved_err=$?
 249    if test "$saved_err" = 0 && test -z "$quiet"
 250    then
 251        git diff-index --name-status "$new"
 252    fi
 253    (exit $saved_err)
 254fi
 255
 256#
 257# Switch the HEAD pointer to the new branch if we
 258# checked out a branch head, and remove any potential
 259# old MERGE_HEAD's (subsequent commits will clearly not
 260# be based on them, since we re-set the index)
 261#
 262if [ "$?" -eq 0 ]; then
 263        if [ "$newbranch" ]; then
 264                git branch $track $newbranch_log "$newbranch" "$new_name" || exit
 265                branch="$newbranch"
 266        fi
 267        if test -n "$branch"
 268        then
 269                old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
 270                GIT_DIR="$GIT_DIR" git symbolic-ref -m "checkout: moving from ${old_branch_name:-$old} to $branch" HEAD "refs/heads/$branch"
 271                if test -n "$quiet"
 272                then
 273                        true    # nothing
 274                elif test "refs/heads/$branch" = "$oldbranch"
 275                then
 276                        echo >&2 "Already on branch \"$branch\""
 277                else
 278                        echo >&2 "Switched to${newbranch:+ a new} branch \"$branch\""
 279                fi
 280        elif test -n "$detached"
 281        then
 282                old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
 283                git update-ref --no-deref -m "checkout: moving from ${old_branch_name:-$old} to $arg" HEAD "$detached" ||
 284                        die "Cannot detach HEAD"
 285                if test -n "$detach_warn"
 286                then
 287                        echo >&2 "$detach_warn"
 288                fi
 289                describe_detached_head 'HEAD is now at' HEAD
 290        fi
 291        rm -f "$GIT_DIR/MERGE_HEAD"
 292else
 293        exit 1
 294fi
 295
 296# Run a post-checkout hook
 297if test -x "$GIT_DIR"/hooks/post-checkout; then
 298        "$GIT_DIR"/hooks/post-checkout $old $new 1
 299fi