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