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