In cases where HEAD is not supposed to be updated, there is no reason
that "git reset" should require a commit, a tree should be enough. So
make "git reset $rev^{tree}" work just like "git reset $rev", except
that the former will not update HEAD (since there is no commit to
point it to).
Disallow --soft with trees, since that is about updating only HEAD.
By not updating HEAD, "git reset $rev^{tree}" behaves quite like "git
reset $rev .". One might therefore consider requiring a path when
using reset with a tree to make that similarity more obvious. However,
a future commit will make "git reset" work on an unborn branch by
interpreting it as "git reset $empty_tree" and it would seem
unintuitive to the user to say "git reset ." on an unborn
branch. Requiring a path would also make "git reset --hard $tree"
disallowed.
This commit effectively undoes some of commit 13243c2 (reset: the
command takes committish, 2012-07-03). The command line argument is
now required to be an unambiguous treeish.
---
My implementation of lookup_commit_or_tree looks a little clunky. I'm
not very familiar with the API. Suggestions welcome.
Why is the "HEAD is now at ..." message printed only for --hard reset?
After all, HEAD is updated for all types of reset not involving paths.
builtin/reset.c | 67 +++++++++++++++++++++----------------
t/t1512-rev-parse-disambiguation.sh | 4 ---
t/t7102-reset.sh | 26 ++++++++++++++
3 files changed, 65 insertions(+), 32 deletions(-)
diff --git a/builtin/reset.c b/builtin/reset.c
index 915cc9f..cec9874 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -225,6 +225,21 @@ static void die_if_unmerged_cache(int reset_type)
}
+static struct object *lookup_commit_or_tree(const char *rev) {
+ unsigned char sha1[20];
+ struct commit *commit;
+ struct tree *tree;
+ if (get_sha1_treeish(rev, sha1))
+ die(_("Failed to resolve '%s' as a valid ref."), rev);
+ commit = lookup_commit_reference_gently(sha1, 1);
+ if (commit)
+ return (struct object *) commit;
+ tree = parse_tree_indirect(sha1);
+ if (tree)
+ return (struct object *) tree;
+ die(_("Could not parse object '%s'."), rev);
+}
+
int cmd_reset(int argc, const char **argv, const char *prefix)
{
int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
@@ -232,7 +247,8 @@ int cmd_reset(int argc, const char **argv, const char
*prefix)
const char *rev = "HEAD";
unsigned char sha1[20], *orig = NULL, sha1_orig[20],
*old_orig = NULL, sha1_old_orig[20];
- struct commit *commit;
+ struct object *object;
+ struct commit *commit = NULL;
struct strbuf msg = STRBUF_INIT;
const struct option options[] = {
OPT__QUIET(&quiet, N_("be quiet, only report errors")),
@@ -276,7 +292,7 @@ int cmd_reset(int argc, const char **argv, const char
*prefix)
* Otherwise, argv[i] could be either <rev> or <paths> and
* has to be unambiguous.
*/
- else if (!get_sha1_committish(argv[i], sha1)) {
+ else if (!get_sha1_treeish(argv[i], sha1)) {
/*
* Ok, argv[i] looks like a rev; it should not
* be a filename.
@@ -289,19 +305,12 @@ int cmd_reset(int argc, const char **argv, const char
*prefix)
}
}
- if (get_sha1_committish(rev, sha1))
- die(_("Failed to resolve '%s' as a valid ref."), rev);
-
- /*
- * NOTE: As "git reset $treeish -- $path" should be usable on
- * any tree-ish, this is not strictly correct. We are not
- * moving the HEAD to any commit; we are merely resetting the
- * entries in the index to that of a treeish.
- */
- commit = lookup_commit_reference(sha1);
- if (!commit)
- die(_("Could not parse object '%s'."), rev);
- hashcpy(sha1, commit->object.sha1);
+ object = lookup_commit_or_tree(rev);
+ if (object->type == OBJ_COMMIT)
+ commit = (struct commit*) object;
+ else if (reset_type == SOFT)
+ die(_("--soft requires a commit, which '%s' is not"), rev);
+ hashcpy(sha1, object->sha1);
if (patch_mode) {
if (reset_type != NONE)
@@ -347,23 +356,25 @@ int cmd_reset(int argc, const char **argv, const char
*prefix)
die(_("Could not reset index file to revision '%s'."),
rev);
}
- /* Any resets update HEAD to the head being switched to,
- * saving the previous head in ORIG_HEAD before. */
- if (!get_sha1("ORIG_HEAD", sha1_old_orig))
- old_orig = sha1_old_orig;
- if (!get_sha1("HEAD", sha1_orig)) {
- orig = sha1_orig;
- set_reflog_message(&msg, "updating ORIG_HEAD", NULL);
- update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
+ if (commit) {
+ /* Any resets update HEAD to the head being switched to,
+ * saving the previous head in ORIG_HEAD before. */
+ if (!get_sha1("ORIG_HEAD", sha1_old_orig))
+ old_orig = sha1_old_orig;
+ if (!get_sha1("HEAD", sha1_orig)) {
+ orig = sha1_orig;
+ set_reflog_message(&msg, "updating ORIG_HEAD", NULL);
+ update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0,
MSG_ON_ERR);
+ }
+ else if (old_orig)
+ delete_ref("ORIG_HEAD", old_orig, 0);
+ set_reflog_message(&msg, "updating HEAD", rev);
+ update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0,
MSG_ON_ERR);
}
- else if (old_orig)
- delete_ref("ORIG_HEAD", old_orig, 0);
- set_reflog_message(&msg, "updating HEAD", rev);
- update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0,
MSG_ON_ERR);
switch (reset_type) {
case HARD:
- if (!update_ref_status && !quiet)
+ if (commit && !update_ref_status && !quiet)
print_new_head_line(commit);
break;
case SOFT: /* Nothing else to do. */
diff --git a/t/t1512-rev-parse-disambiguation.sh
b/t/t1512-rev-parse-disambiguation.sh
index 6b3d797..bc1e40c 100755
--- a/t/t1512-rev-parse-disambiguation.sh
+++ b/t/t1512-rev-parse-disambiguation.sh
@@ -121,10 +121,6 @@ test_expect_success 'git log takes only commit-ish' '
git log 000000000
'
-test_expect_success 'git reset takes only commit-ish' '
- git reset 000000000
-'
-
test_expect_success 'first tag' '
# create one tag 0000000000f8f
git tag -a -m j7cp83um v1.0.0
diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh
index b096dc8..d723ef5 100755
--- a/t/t7102-reset.sh
+++ b/t/t7102-reset.sh
@@ -491,4 +491,30 @@ test_expect_success 'disambiguation (4)' '
test ! -f secondfile
'
+test_expect_success 'reset to tree does not update HEAD' '
+ git reset --hard HEAD &&
+ rev_before=$(git rev-parse HEAD) &&
+ git reset HEAD^^{tree} &&
+ test $(git rev-parse HEAD) == $rev_before
+'
+
+test_expect_success 'reset to tree' '
+ # for simpler tests, drop last commit containing added files
+ git reset --hard HEAD^ &&
+ git reset HEAD^^{tree} &&
+ git diff --cached HEAD^ --exit-code &&
+ git diff HEAD --exit-code
+'
+
+test_expect_success 'reset --hard to tree' '
+ git reset --hard &&
+ git reset --hard HEAD^^{tree} &&
+ git diff --cached HEAD^ --exit-code &&
+ git diff HEAD^ --exit-code
+'
+
+test_expect_success 'reset to tree not allowed with --soft}' '
+ test_must_fail git reset --soft HEAD^^{tree}
+'
+
test_done
--
1.8.0.1.240.ge8a1f5a
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html