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