contrib / examples / git-checkout.shon commit Sync with v2.9.1 (b1ec08f)
   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" or "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    git read-tree $v -m -u --exclude-per-directory=.gitignore $old $new || (
 214        case "$merge,$v" in
 215        ,*)
 216                exit 1 ;;
 217        1,)
 218                ;; # quiet
 219        *)
 220                echo >&2 "Falling back to 3-way merge..." ;;
 221        esac
 222
 223        # Match the index to the working tree, and do a three-way.
 224        git diff-files --name-only | git update-index --remove --stdin &&
 225        work=$(git write-tree) &&
 226        git read-tree $v --reset -u $new || exit
 227
 228        eval GITHEAD_$new='${new_name:-${branch:-$new}}' &&
 229        eval GITHEAD_$work=local &&
 230        export GITHEAD_$new GITHEAD_$work &&
 231        git merge-recursive $old -- $new $work
 232
 233        # Do not register the cleanly merged paths in the index yet.
 234        # this is not a real merge before committing, but just carrying
 235        # the working tree changes along.
 236        unmerged=$(git ls-files -u)
 237        git read-tree $v --reset $new
 238        case "$unmerged" in
 239        '')     ;;
 240        *)
 241                (
 242                        z40=0000000000000000000000000000000000000000
 243                        echo "$unmerged" |
 244                        sed -e 's/^[0-7]* [0-9a-f]* /'"0 $z40 /"
 245                        echo "$unmerged"
 246                ) | git update-index --index-info
 247                ;;
 248        esac
 249        exit 0
 250    )
 251    saved_err=$?
 252    if test "$saved_err" = 0 && test -z "$quiet"
 253    then
 254        git diff-index --name-status "$new"
 255    fi
 256    (exit $saved_err)
 257fi
 258
 259#
 260# Switch the HEAD pointer to the new branch if we
 261# checked out a branch head, and remove any potential
 262# old MERGE_HEAD's (subsequent commits will clearly not
 263# be based on them, since we re-set the index)
 264#
 265if [ "$?" -eq 0 ]; then
 266        if [ "$newbranch" ]; then
 267                git branch $track $newbranch_log "$newbranch" "$new_name" || exit
 268                branch="$newbranch"
 269        fi
 270        if test -n "$branch"
 271        then
 272                old_branch_name=$(expr "z$oldbranch" : 'zrefs/heads/\(.*\)')
 273                GIT_DIR="$GIT_DIR" git symbolic-ref -m "checkout: moving from ${old_branch_name:-$old} to $branch" HEAD "refs/heads/$branch"
 274                if test -n "$quiet"
 275                then
 276                        true    # nothing
 277                elif test "refs/heads/$branch" = "$oldbranch"
 278                then
 279                        echo >&2 "Already on branch \"$branch\""
 280                else
 281                        echo >&2 "Switched to${newbranch:+ a new} branch \"$branch\""
 282                fi
 283        elif test -n "$detached"
 284        then
 285                old_branch_name=$(expr "z$oldbranch" : 'zrefs/heads/\(.*\)')
 286                git update-ref --no-deref -m "checkout: moving from ${old_branch_name:-$old} to $arg" HEAD "$detached" ||
 287                        die "Cannot detach HEAD"
 288                if test -n "$detach_warn"
 289                then
 290                        echo >&2 "$detach_warn"
 291                fi
 292                describe_detached_head 'HEAD is now at' HEAD
 293        fi
 294        rm -f "$GIT_DIR/MERGE_HEAD"
 295else
 296        exit 1
 297fi
 298
 299# Run a post-checkout hook
 300if test -x "$GIT_DIR"/hooks/post-checkout; then
 301        "$GIT_DIR"/hooks/post-checkout $old $new 1
 302fi