#!/bin/sh setglobal OPTIONS_KEEPDASHDASH = 't' setglobal OPTIONS_SPEC = '"\ git-checkout [options] [] [...] -- b= create a new branch started at l create the new branch's reflog track arrange that the new branch tracks the remote branch f proceed even if the index or working tree is not HEAD m merge local modifications into the new branch q,quiet be quiet '" setglobal SUBDIRECTORY_OK = 'Sometimes' source git-sh-setup require_work_tree setglobal old_name = 'HEAD' setglobal old = $[git rev-parse --verify $old_name !2 >/dev/null] setglobal oldbranch = $[git symbolic-ref $old_name !2 >/dev/null] setglobal new = '' setglobal new_name = '' setglobal force = '' setglobal branch = '' setglobal track = '' setglobal newbranch = '' setglobal newbranch_log = '' setglobal merge = '' setglobal quiet = '' setglobal v = '-v' setglobal LF = '' '' while test $Argc != 0 { match $1 { with -b shift setglobal newbranch = $1 test -z $newbranch && die "git checkout: -b needs a branch name" git show-ref --verify --quiet -- "refs/heads/$newbranch" && die "git checkout: branch $newbranch already exists" git check-ref-format "heads/$newbranch" || die "git checkout: we do not like '$newbranch' as a branch name." with -l setglobal newbranch_log = '-l' with --track|--no-track setglobal track = $1 with -f setglobal force = '1' with -m setglobal merge = '1' with -q|--quiet setglobal quiet = '1' setglobal v = '' with -- shift break with * usage } shift } setglobal arg = $1 setglobal rev = $[git rev-parse --verify $arg !2 >/dev/null] if setglobal rev = $[git rev-parse --verify "$rev^0" !2 >/dev/null] { test -z $rev && die "unknown flag $arg" setglobal new_name = $arg if git show-ref --verify --quiet -- "refs/heads/$arg" { setglobal rev = $[git rev-parse --verify "refs/heads/$arg^0] setglobal branch = $arg } setglobal new = $rev shift } elif setglobal rev = $[git rev-parse --verify "$rev^{tree}" !2 >/dev/null] { # checking out selected paths from a tree-ish. setglobal new = $rev setglobal new_name = ""$rev^{tree}"" shift } test $1 = "--" && shift match "$newbranch,$track" { with ,--* die "git checkout: --track and --no-track require -b" } match "$force$merge" { with 11 die "git checkout: -f and -m are incompatible" } # The behaviour of the command with and without explicit path # parameters is quite different. # # Without paths, we are checking out everything in the work tree, # possibly switching branches. This is the traditional behaviour. # # With paths, we are _never_ switching branch, but checking out # the named paths from either index (when no rev is given), # or the named tree-ish (when rev is given). if test "$Argc" -ge 1 { setglobal hint = '' if test "$Argc" -eq 1 { setglobal hint = "" Did you intend to checkout '$ifsjoin(Argv)' which can not be resolved as commit?"" } if test '' != "$newbranch$force$merge" { die "git checkout: updating paths is incompatible with switching branches/forcing$hint" } if test '' != $new { # from a specific tree-ish; note that this is for # rescuing paths and is never meant to remove what # is not in the named tree-ish. git ls-tree --full-name -r $new @Argv | git update-index --index-info || exit $? } # Make sure the request is about existing paths. git ls-files --full-name --error-unmatch -- @Argv >/dev/null || exit git ls-files --full-name -- @Argv | shell {cd_to_toplevel && git checkout-index -f -u --stdin} # Run a post-checkout hook -- the HEAD does not change so the # current HEAD is passed in for both args if test -x "$GIT_DIR"/hooks/post-checkout { "$GIT_DIR"/hooks/post-checkout $old $old 0 } exit $? } else { # Make sure we did not fall back on $arg^{tree} codepath # since we are not checking out from an arbitrary tree-ish, # but switching branches. if test '' != $new { git rev-parse --verify "$new^{commit}" >/dev/null !2 > !1 || die "Cannot switch branch to a non-commit." } } # We are switching branches and checking out trees, so # we *NEED* to be at the toplevel. cd_to_toplevel test -z $new && setglobal new = $old && setglobal new_name = $old_name # If we don't have an existing branch that we're switching to, # and we don't have a new branch name for the target we # are switching to, then we are detaching our HEAD from any # branch. However, if "git checkout HEAD" detaches the HEAD # from the current branch, even though that may be logically # correct, it feels somewhat funny. More importantly, we do not # want "git checkout" or "git checkout -f" to detach HEAD. setglobal detached = '' setglobal detach_warn = '' proc describe_detached_head { test -n $quiet || do { printf >&2 "$1 > !2 "$1 " env GIT_PAGER= git log >&2 -1 --pretty=oneline --abbrev-commit $2 --> !2 -1 --pretty=oneline --abbrev-commit "$2" -- } } if test -z "$branch$newbranch" && test $new_name != $old_name { setglobal detached = $new if test -n $oldbranch && test -z $quiet { setglobal detach_warn = ""Note: moving to \"$new_name\" which isn't a local branch If you want to create a new branch from this checkout, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b "" } } elif test -z $oldbranch && test $new != $old { describe_detached_head 'Previous HEAD position was' $old } if test "X$old" = X { if test -z $quiet { echo >&2 "warning: You appear to be on a branch yet to be born.> !2 "warning: You appear to be on a branch yet to be born." echo >&2 "warning: Forcing checkout of $new_name.> !2 "warning: Forcing checkout of $new_name." } setglobal force = '1' } if test $force { git read-tree $v --reset -u $new } else { git update-index --refresh >/dev/null git read-tree $v -m -u --exclude-per-directory=.gitignore $old $new || shell { match "$merge,$v" { with ,* exit 1 with 1, # quiet with * echo >&2 "Falling back to 3-way merge...> !2 "Falling back to 3-way merge..." } # Match the index to the working tree, and do a three-way. git diff-files --name-only | git update-index --remove --stdin && setglobal work = $[git write-tree] && git read-tree $v --reset -u $new || exit eval GITHEAD_$new='${new_name:-${branch:-$new}}' && eval GITHEAD_$work=local && export GITHEAD_$new GITHEAD_$work && git merge-recursive $old -- $new $work # Do not register the cleanly merged paths in the index yet. # this is not a real merge before committing, but just carrying # the working tree changes along. setglobal unmerged = $[git ls-files -u] git read-tree $v --reset $new match $unmerged { with '' with * shell { setglobal z40 = '0000000000000000000000000000000000000000' echo $unmerged | sed -e 's/^[0-7]* [0-9a-f]* /'"0 $z40 /" echo $unmerged } | git update-index --index-info } exit 0 } setglobal saved_err = $Status if test $saved_err = 0 && test -z $quiet { git diff-index --name-status $new } shell {exit $saved_err} } # # Switch the HEAD pointer to the new branch if we # checked out a branch head, and remove any potential # old MERGE_HEAD's (subsequent commits will clearly not # be based on them, since we re-set the index) # if test "$Status" -eq 0 { if test $newbranch { git branch $track $newbranch_log $newbranch $new_name || exit setglobal branch = $newbranch } if test -n $branch { setglobal old_branch_name = $[expr "z$oldbranch" : 'zrefs/heads/\(.*\)] env GIT_DIR=$GIT_DIR git symbolic-ref -m "checkout: moving from $(old_branch_name:-$old) to $branch" HEAD "refs/heads/$branch" if test -n $quiet { true # nothing } elif test "refs/heads/$branch" = $oldbranch { echo >&2 "Already on branch \"$branch> !2 "Already on branch \"$branch\"" } else { echo >&2 "Switched to$(newbranch:+ a new) branch \"$branch> !2 "Switched to${newbranch:+ a new} branch \"$branch\"" } } elif test -n $detached { setglobal old_branch_name = $[expr "z$oldbranch" : 'zrefs/heads/\(.*\)] git update-ref --no-deref -m "checkout: moving from $(old_branch_name:-$old) to $arg" HEAD $detached || die "Cannot detach HEAD" if test -n $detach_warn { echo >&2 $detach_warn> !2 "$detach_warn" } describe_detached_head 'HEAD is now at' HEAD } rm -f "$GIT_DIR/MERGE_HEAD" } else { exit 1 } # Run a post-checkout hook if test -x "$GIT_DIR"/hooks/post-checkout { "$GIT_DIR"/hooks/post-checkout $old $new 1 }