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 old 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'd better just be checking out 146# what we already had 147 148iftest -z"$branch$newbranch"&&test"$new"!="$old" 149then 150# NEEDSWORK: we would want to have this command here 151# that allows us to detach the HEAD atomically. 152# git update-ref --detach HEAD "$new" 153echo"$new">"$GIT_DIR/HEAD.new"&& 154mv"$GIT_DIR/HEAD.new""$GIT_DIR/HEAD"|| die "Cannot detach HEAD" 155 156iftest -n"$oldbranch" 157then 158echo>&2"WARNING: you are not on ANY branch anymore. 159If you meant to create a new branch from the commit, you need -b to 160associate a new branch with the wanted checkout. Example: 161 git checkout -b <new_branch_name>$arg 162" 163fi 164fi 165 166if["X$old"= X ] 167then 168echo"warning: You do not appear to currently be on a branch.">&2 169echo"warning: Forcing checkout of$new_name.">&2 170 force=1 171fi 172 173if["$force"] 174then 175 git-read-tree --reset -u$new 176else 177 git-update-index --refresh>/dev/null 178 merge_error=$(git-read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1)|| ( 179case"$merge"in 180'') 181echo>&2"$merge_error" 182exit1;; 183esac 184 185# Match the index to the working tree, and do a three-way. 186 git diff-files --name-only| git update-index --remove --stdin&& 187 work=`git write-tree`&& 188 git read-tree --reset -u$new&& 189 git read-tree -m -u --aggressive --exclude-per-directory=.gitignore $old $new $work|| 190exit 191 192if result=`git write-tree 2>/dev/null` 193then 194echo>&2"Trivially automerged." 195else 196 git merge-index -o git-merge-one-file -a 197fi 198 199# Do not register the cleanly merged paths in the index yet. 200# this is not a real merge before committing, but just carrying 201# the working tree changes along. 202 unmerged=`git ls-files -u` 203 git read-tree --reset$new 204case"$unmerged"in 205'') ;; 206*) 207( 208 z40=0000000000000000000000000000000000000000 209echo"$unmerged"| 210sed-e's/^[0-7]* [0-9a-f]* /'"0$z40/" 211echo"$unmerged" 212) | git update-index --index-info 213;; 214esac 215exit0 216) 217 saved_err=$? 218iftest"$saved_err"=0 219then 220test"$new"="$old"|| git diff-index --name-status"$new" 221fi 222(exit$saved_err) 223fi 224 225# 226# Switch the HEAD pointer to the new branch if we 227# checked out a branch head, and remove any potential 228# old MERGE_HEAD's (subsequent commits will clearly not 229# be based on them, since we re-set the index) 230# 231if["$?"-eq0];then 232if["$newbranch"];then 233if["$newbranch_log"];then 234mkdir-p$(dirname "$GIT_DIR/logs/refs/heads/$newbranch") 235touch"$GIT_DIR/logs/refs/heads/$newbranch" 236fi 237 git-update-ref -m"checkout: Created from$new_name""refs/heads/$newbranch"$new||exit 238 branch="$newbranch" 239fi 240["$branch"] && 241 GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch" 242rm-f"$GIT_DIR/MERGE_HEAD" 243else 244exit1 245fi