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