a309bf0a1acf07eb7a7286dccb6d9cf68b813318
   1#!/bin/sh
   2
   3USAGE='[-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]'
   4SUBDIRECTORY_OK=Sometimes
   5. git-sh-setup
   6
   7old_name=HEAD
   8old=$(git-rev-parse --verify $old_name 2>/dev/null)
   9oldbranch=$(git-symbolic-ref $old_name 2>/dev/null)
  10new=
  11new_name=
  12force=
  13branch=
  14newbranch=
  15newbranch_log=
  16merge=
  17LF='
  18'
  19while [ "$#" != "0" ]; do
  20    arg="$1"
  21    shift
  22    case "$arg" in
  23        "-b")
  24                newbranch="$1"
  25                shift
  26                [ -z "$newbranch" ] &&
  27                        die "git checkout: -b needs a branch name"
  28                git-show-ref --verify --quiet -- "refs/heads/$newbranch" &&
  29                        die "git checkout: branch $newbranch already exists"
  30                git-check-ref-format "heads/$newbranch" ||
  31                        die "git checkout: we do not like '$newbranch' as a branch name."
  32                ;;
  33        "-l")
  34                newbranch_log=1
  35                ;;
  36        "-f")
  37                force=1
  38                ;;
  39        -m)
  40                merge=1
  41                ;;
  42        --)
  43                break
  44                ;;
  45        -*)
  46                usage
  47                ;;
  48        *)
  49                if rev=$(git-rev-parse --verify "$arg^0" 2>/dev/null)
  50                then
  51                        if [ -z "$rev" ]; then
  52                                echo "unknown flag $arg"
  53                                exit 1
  54                        fi
  55                        new="$rev"
  56                        new_name="$arg"
  57                        if git-show-ref --verify --quiet -- "refs/heads/$arg"
  58                        then
  59                                branch="$arg"
  60                        fi
  61                elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null)
  62                then
  63                        # checking out selected paths from a tree-ish.
  64                        new="$rev"
  65                        new_name="$arg^{tree}"
  66                        branch=
  67                else
  68                        new=
  69                        new_name=
  70                        branch=
  71                        set x "$arg" "$@"
  72                        shift
  73                fi
  74                case "$1" in
  75                --)
  76                        shift ;;
  77                esac
  78                break
  79                ;;
  80    esac
  81done
  82
  83case "$force$merge" in
  8411)
  85        die "git checkout: -f and -m are incompatible"
  86esac
  87
  88# The behaviour of the command with and without explicit path
  89# parameters is quite different.
  90#
  91# Without paths, we are checking out everything in the work tree,
  92# possibly switching branches.  This is the traditional behaviour.
  93#
  94# With paths, we are _never_ switching branch, but checking out
  95# the named paths from either index (when no rev is given),
  96# or the named tree-ish (when rev is given).
  97
  98if test "$#" -ge 1
  99then
 100        hint=
 101        if test "$#" -eq 1
 102        then
 103                hint="
 104Did you intend to checkout '$@' which can not be resolved as commit?"
 105        fi
 106        if test '' != "$newbranch$force$merge"
 107        then
 108                die "git checkout: updating paths is incompatible with switching branches/forcing$hint"
 109        fi
 110        if test '' != "$new"
 111        then
 112                # from a specific tree-ish; note that this is for
 113                # rescuing paths and is never meant to remove what
 114                # is not in the named tree-ish.
 115                git-ls-tree --full-name -r "$new" "$@" |
 116                git-update-index --index-info || exit $?
 117        fi
 118
 119        # Make sure the request is about existing paths.
 120        git-ls-files --error-unmatch -- "$@" >/dev/null || exit
 121        git-ls-files -- "$@" |
 122        git-checkout-index -f -u --stdin
 123        exit $?
 124else
 125        # Make sure we did not fall back on $arg^{tree} codepath
 126        # since we are not checking out from an arbitrary tree-ish,
 127        # but switching branches.
 128        if test '' != "$new"
 129        then
 130                git-rev-parse --verify "$new^{commit}" >/dev/null 2>&1 ||
 131                die "Cannot switch branch to a non-commit."
 132        fi
 133fi
 134
 135# We are switching branches and checking out trees, so
 136# we *NEED* to be at the toplevel.
 137cdup=$(git-rev-parse --show-cdup)
 138if test ! -z "$cdup"
 139then
 140        cd "$cdup"
 141fi
 142
 143[ -z "$new" ] && new=$old && new_name="$old_name"
 144
 145# If we don't have an existing branch that we're switching to,
 146# and we don't have a new branch name for the target we
 147# are switching to, then we are detaching our HEAD from any
 148# branch.  However, if "git checkout HEAD" detaches the HEAD
 149# from the current branch, even though that may be logically
 150# correct, it feels somewhat funny.  More importantly, we do not
 151# want "git checkout" nor "git checkout -f" to detach HEAD.
 152
 153if test -z "$branch$newbranch" && test "$new" != "$old"
 154then
 155        # NEEDSWORK: we would want to have a command here
 156        # that allows us to detach the HEAD atomically.  Perhaps
 157        # something like "git update-ref --detach HEAD $new"
 158        echo "$new" >"$GIT_DIR/HEAD.new" &&
 159        mv "$GIT_DIR/HEAD.new" "$GIT_DIR/HEAD" || die "Cannot detach HEAD"
 160
 161        if test -n "$oldbranch"
 162        then
 163                echo >&2 "warning: you are not on ANY branch anymore.
 164If you meant to create a new branch from the commit, you need -b to
 165associate a new branch with the wanted checkout.  Example:
 166  git checkout -b <new_branch_name> $arg
 167"
 168        fi
 169elif test -z "$oldbranch" && test -n "$branch"
 170then
 171        # Coming back...
 172        if test -z "$force"
 173        then
 174                mb=$(git merge-base --all $old $new) &&
 175                case "$LF$mb$LF" in
 176                *"$LF$old$LF"*) : ;;
 177                *)      false ;;
 178                esac || {
 179                        echo >&2 \
 180"You are not on a branch and switching to $new_name branch may lose
 181your changes.  Use 'git checkout -f $new_name' if you want to."
 182                        exit 1;
 183                }
 184        fi
 185fi
 186
 187if [ "X$old" = X ]
 188then
 189        echo >&2 "warning: You appear to be on a branch yet to be born."
 190        echo >&2 "warning: Forcing checkout of $new_name."
 191        force=1
 192fi
 193
 194if [ "$force" ]
 195then
 196    git-read-tree --reset -u $new
 197else
 198    git-update-index --refresh >/dev/null
 199    merge_error=$(git-read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || (
 200        case "$merge" in
 201        '')
 202                echo >&2 "$merge_error"
 203                exit 1 ;;
 204        esac
 205
 206        # Match the index to the working tree, and do a three-way.
 207        git diff-files --name-only | git update-index --remove --stdin &&
 208        work=`git write-tree` &&
 209        git read-tree --reset -u $new &&
 210        git read-tree -m -u --aggressive --exclude-per-directory=.gitignore $old $new $work ||
 211        exit
 212
 213        if result=`git write-tree 2>/dev/null`
 214        then
 215            echo >&2 "Trivially automerged."
 216        else
 217            git merge-index -o git-merge-one-file -a
 218        fi
 219
 220        # Do not register the cleanly merged paths in the index yet.
 221        # this is not a real merge before committing, but just carrying
 222        # the working tree changes along.
 223        unmerged=`git ls-files -u`
 224        git read-tree --reset $new
 225        case "$unmerged" in
 226        '')     ;;
 227        *)
 228                (
 229                        z40=0000000000000000000000000000000000000000
 230                        echo "$unmerged" |
 231                        sed -e 's/^[0-7]* [0-9a-f]* /'"0 $z40 /"
 232                        echo "$unmerged"
 233                ) | git update-index --index-info
 234                ;;
 235        esac
 236        exit 0
 237    )
 238    saved_err=$?
 239    if test "$saved_err" = 0
 240    then
 241        test "$new" = "$old" || git diff-index --name-status "$new"
 242    fi
 243    (exit $saved_err)
 244fi
 245
 246# 
 247# Switch the HEAD pointer to the new branch if we
 248# checked out a branch head, and remove any potential
 249# old MERGE_HEAD's (subsequent commits will clearly not
 250# be based on them, since we re-set the index)
 251#
 252if [ "$?" -eq 0 ]; then
 253        if [ "$newbranch" ]; then
 254                if [ "$newbranch_log" ]; then
 255                        mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$newbranch")
 256                        touch "$GIT_DIR/logs/refs/heads/$newbranch"
 257                fi
 258                git-update-ref -m "checkout: Created from $new_name" "refs/heads/$newbranch" $new || exit
 259                branch="$newbranch"
 260        fi
 261        [ "$branch" ] &&
 262        GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch"
 263        rm -f "$GIT_DIR/MERGE_HEAD"
 264else
 265        exit 1
 266fi