From 4aaa702794447d9b281dd22fe532fd61e02434e1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 18 Oct 2005 01:29:27 -0700 Subject: [PATCH] git-checkout: revert specific paths to either index or a given tree-ish. When extra paths arguments are given, git-checkout reverts only those paths to either the version recorded in the index or the version recorded in the given tree-ish. This has been on the TODO list for quite a while. Signed-off-by: Junio C Hamano --- Documentation/git-checkout.txt | 42 ++++++++++++++++-- git-checkout.sh | 79 +++++++++++++++++++++++++++------- 2 files changed, 103 insertions(+), 18 deletions(-) diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index f753c149a..b7bb1b4c7 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -7,12 +7,24 @@ git-checkout - Checkout and switch to a branch. SYNOPSIS -------- -'git-checkout' [-f] [-b ] [] +'git-checkout' [-f] [-b ] [] [...] DESCRIPTION ----------- -Updates the index and working tree to reflect the specified branch, -. Updates HEAD to be or, if specified, . + +When are not given, this command switches branches, by +updating the index and working tree to reflect the specified +branch, , and updating HEAD to be or, if +specified, . + +When are given, this command does *not* switch +branches. It updates the named paths in the working tree from +the index file (i.e. it runs `git-checkout-index -f -u`). In +this case, `-f` and `-b` options are meaningless and giving +either of them results in an error. argument can be +used to specify a specific tree-ish to update the index for the +given paths before updating the working tree. + OPTIONS ------- @@ -29,6 +41,30 @@ OPTIONS Branch to checkout; may be any object ID that resolves to a commit. Defaults to HEAD. + +EXAMPLE +------- + +The following sequence checks out the `master` branch, reverts +the `Makefile` to two revisions back, deletes hello.c by +mistake, and gets it back from the index. + +------------ +$ git checkout master +$ git checkout master~2 Makefile +$ rm -f hello.c +$ git checkout hello.c +------------ + +If you have an unfortunate branch that is named `hello.c`, the +last step above would be confused as an instruction to switch to +that branch. You should instead write: + +------------ +$ git checkout -- hello.c +------------ + + Author ------ Written by Linus Torvalds diff --git a/git-checkout.sh b/git-checkout.sh index 2c053a33c..73652fa5f 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -23,32 +23,81 @@ while [ "$#" != "0" ]; do "-f") force=1 ;; + --) + break + ;; *) - rev=$(git-rev-parse --verify "$arg^0" 2>/dev/null) || - die "I don't know any '$arg'." - if [ -z "$rev" ]; then - echo "unknown flag $arg" - exit 1 - fi - if [ "$new" ]; then - echo "Multiple revisions?" - exit 1 - fi - new="$rev" - if [ -f "$GIT_DIR/refs/heads/$arg" ]; then - branch="$arg" + if rev=$(git-rev-parse --verify "$arg^0" 2>/dev/null) + then + if [ -z "$rev" ]; then + echo "unknown flag $arg" + exit 1 + fi + new="$rev" + if [ -f "$GIT_DIR/refs/heads/$arg" ]; then + branch="$arg" + fi + elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null) + then + # checking out selected paths from a tree-ish. + new="$rev" + branch= + else + new= + branch= + set x "$arg" "$@" + shift fi + break ;; esac done -[ -z "$new" ] && new=$old +# The behaviour of the command with and without explicit path +# parameters is quite different. +# +# Without paths, we are checking out everything in the work tree, +# possibly switching branches. This is the traditional behaviour. # +# With paths, we are _never_ switching branch, but checking out +# the named paths from either index (when no rev is given), +# or the named tree-ish (when rev is given). + +if test "$#" -ge 1 +then + if test '' != "$newbranch$force" + then + die "updating paths and switching branches or forcing are incompatible." + fi + if test '' != "$new" + then + # from a specific tree-ish; note that this is for + # rescuing paths and is never meant to remove what + # is not in the named tree-ish. + git-ls-tree -r "$new" "$@" | + sed -ne 's/^\([0-7]*\) blob \(.*\)$/\1 \2/p' | + git-update-index --index-info || exit $? + fi + git-checkout-index -f -u -- "$@" + exit $? +else + # Make sure we did not fall back on $arg^{tree} codepath + # since we are not checking out from an arbitrary tree-ish, + # but switching branches. + if test '' != "$new" + then + git-rev-parse --verify "$new^{commit}" >/dev/null 2>&1 || + die "Cannot switch branch to a non-commit." + fi +fi + +[ -z "$new" ] && new=$old + # If we don't have an old branch that we're switching to, # and we don't have a new branch name for the target we # are switching to, then we'd better just be checking out # what we already had -# + [ -z "$branch$newbranch" ] && [ "$new" != "$old" ] && die "git checkout: you need to specify a new branch name" -- 2.30.2