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