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