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