git-checkout: revert specific paths to either index or a given tree-ish.
authorJunio C Hamano <junkio@cox.net>
Tue, 18 Oct 2005 08:29:27 +0000 (01:29 -0700)
committerJunio C Hamano <junkio@cox.net>
Tue, 18 Oct 2005 08:29:27 +0000 (01:29 -0700)
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 <junkio@cox.net>
Documentation/git-checkout.txt
git-checkout.sh

index f753c14..b7bb1b4 100644 (file)
@@ -7,12 +7,24 @@ git-checkout - Checkout and switch to a branch.
 
 SYNOPSIS
 --------
-'git-checkout' [-f] [-b <new_branch>] [<branch>]
+'git-checkout' [-f] [-b <new_branch>] [<branch>] [<paths>...]
 
 DESCRIPTION
 -----------
-Updates the index and working tree to reflect the specified branch,
-<branch>. Updates HEAD to be <branch> or, if specified, <new_branch>.
+
+When <paths> are not given, this command switches branches, by
+updating the index and working tree to reflect the specified
+branch, <branch>, and updating HEAD to be <branch> or, if
+specified, <new_branch>.
+
+When <paths> 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.  <branch> 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 <torvalds@osdl.org>
index 2c053a3..73652fa 100755 (executable)
@@ -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"