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