git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* reproducible unexpected behavior for 'git reset'
@ 2011-07-10 22:30 John Nowak
  2011-07-11 21:54 ` Junio C Hamano
  0 siblings, 1 reply; 9+ messages in thread
From: John Nowak @ 2011-07-10 22:30 UTC (permalink / raw)
  To: git

(Apologies if you receive this twice. I sent the first copy before confirming my list subscription and I'm not sure if it went through.)

I am able to reproduce a scenario where, after a 'commit' and a 'stash pop' that results in a merge conflict, I need to 'reset' a file twice in order to get the index back to HEAD. It appears that the first 'reset' sets the index to the merge base version instead of HEAD which was, for me, rather unexpected. I encountered this on 1.7.4 but others have reproduced it on the latest master. If this behavior is documented, I cannot find it.

A transcript to reproduce this oddity is below; note how the file is partially staged after the first 'reset' and unstaged after the second:

---

$ git init
Initialized empty Git repository in /Users/jn/test/.git/

$ echo "a" > foo

$ git add foo

$ git commit -a -m "Initial"
[master (root-commit) 5214837] Initial
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 foo

$ echo "b" >> foo

$ git stash
Saved working directory and index state WIP on master: 5214837 Initial
HEAD is now at 5214837 Initial

$ echo "c" >> foo

$ git add foo

$ git commit -a -m "Added c"
[master 69eef48] Added c
1 files changed, 1 insertions(+), 0 deletions(-)

$ git stash pop
Auto-merging foo
CONFLICT (content): Merge conflict in foo

$ git status
# On branch master
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#	both modified:      foo
#
no changes added to commit (use "git add" and/or "git commit -a")

$ git reset foo
Unstaged changes after reset:
M	foo

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#	modified:   foo
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#	modified:   foo
#

$ git reset foo
Unstaged changes after reset:
M	foo

$ git status
# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#	modified:   foo
#
no changes added to commit (use "git add" and/or "git commit -a")

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: reproducible unexpected behavior for 'git reset'
  2011-07-10 22:30 reproducible unexpected behavior for 'git reset' John Nowak
@ 2011-07-11 21:54 ` Junio C Hamano
  2011-07-11 22:41   ` Junio C Hamano
  0 siblings, 1 reply; 9+ messages in thread
From: Junio C Hamano @ 2011-07-11 21:54 UTC (permalink / raw)
  To: John Nowak; +Cc: git

John Nowak <john@johnnowak.com> writes:

> I am able to reproduce a scenario where, after a 'commit' and a 'stash
> pop' that results in a merge conflict, I need to 'reset' a file twice in
> order to get the index back to HEAD.

Thanks, you found a bug in "git reset [<commit>] -- path" codepath, it
seems, when dealing with an unmerged path.

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: reproducible unexpected behavior for 'git reset'
  2011-07-11 21:54 ` Junio C Hamano
@ 2011-07-11 22:41   ` Junio C Hamano
  2011-07-14  6:01     ` Junio C Hamano
  0 siblings, 1 reply; 9+ messages in thread
From: Junio C Hamano @ 2011-07-11 22:41 UTC (permalink / raw)
  To: git; +Cc: John Nowak

Junio C Hamano <gitster@pobox.com> writes:

>> I am able to reproduce a scenario where, after a 'commit' and a 'stash
>> pop' that results in a merge conflict, I need to 'reset' a file twice in
>> order to get the index back to HEAD.
>
> Thanks, you found a bug in "git reset [<commit>] -- path" codepath, it
> seems, when dealing with an unmerged path.

This patch is probably a wrong way to fix this issue; I have this
suspicion that the original code for "reset [<commit>] -- paths..."
codepath is correctly designed to deal with unmerged index, and it would
probably need a deeper surgery.

-- >8 --
Subject: [PATCH] reset [<commit>] paths...: do not mishandle unmerged paths

By reading the index with unmerged content before running diff-index
with the tree-ish we are reading from, read_from_tree() function ended
up stuffing the object name from a wrong stage to the resulting index.

Noticed by John Nowak.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---

 builtin/reset.c  |    2 +-
 t/t7102-reset.sh |   15 +++++++++++++++
 2 files changed, 16 insertions(+), 1 deletions(-)

diff --git a/builtin/reset.c b/builtin/reset.c
index 98bca04..09c8d2a 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -202,7 +202,7 @@ static int read_from_tree(const char *prefix, const char **argv,
 
 	index_fd = hold_locked_index(lock, 1);
 	index_was_discarded = 0;
-	read_cache();
+	read_cache_unmerged();
 	if (do_diff_cache(tree_sha1, &opt))
 		return 1;
 	diffcore_std(&opt);
diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh
index f1cfc9a..1784412 100755
--- a/t/t7102-reset.sh
+++ b/t/t7102-reset.sh
@@ -429,6 +429,21 @@ test_expect_success '--mixed refreshes the index' '
 	test_i18ncmp expect output
 '
 
+test_expect_success 'resetting specific path that is unmerged' '
+	F1=$(git rev-parse HEAD:file1) &&
+	F2=$(git rev-parse HEAD:file2) &&
+	F3=$(git rev-parse HEAD:secondfile) &&
+	{
+		echo "000000 $_z40 0	file2" &&
+		echo "100644 $F1 1	file2" &&
+		echo "100644 $F2 2	file2" &&
+		echo "100644 $F3 3	file2"
+	} | git update-index --index-info &&
+	git ls-files -u &&
+	test_must_fail git reset HEAD file2 &&
+	git diff-index --exit-code --cached HEAD
+'
+
 test_expect_success 'disambiguation (1)' '
 
 	git reset --hard &&

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: reproducible unexpected behavior for 'git reset'
  2011-07-11 22:41   ` Junio C Hamano
@ 2011-07-14  6:01     ` Junio C Hamano
  2011-07-19 18:13       ` [PATCH 0/2] refactoring diff-index Junio C Hamano
  0 siblings, 1 reply; 9+ messages in thread
From: Junio C Hamano @ 2011-07-14  6:01 UTC (permalink / raw)
  To: git; +Cc: John Nowak

Junio C Hamano <gitster@pobox.com> writes:

> This patch is probably a wrong way to fix this issue...

Yes, it was a wrong way. Here is the hopefully right one ;-)

Funny thing is that this is a rather ancient bug, dating back to d1f2d7e
(Make run_diff_index() use unpack_trees(), not read_tree(), 2008-01-19)
and survived a fix for that commit at 29796c6 (diff-index: report unmerged
new entries, 2009-08-04). The reason it survived is probably because
people care about the fact that the path is unmerged, but not the exact
object name on the LHS of the diff (which comes from the tree).

-- >8 --
Subject: [PATCH] reset [<commit>] paths...: do not mishandle unmerged paths

Because "diff --cached HEAD" showed an incorrect blob object name on the
LHS of the diff, we ended up updating the index entry with bogus value,
not what we read from the tree.

Noticed by John Nowak.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/reset.c  |    2 +-
 diff-lib.c       |    3 ++-
 t/t7102-reset.sh |   15 +++++++++++++++
 3 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/builtin/reset.c b/builtin/reset.c
index 98bca04..777e7c6 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -162,7 +162,7 @@ static void update_index_from_diff(struct diff_queue_struct *q,
 
 	for (i = 0; i < q->nr; i++) {
 		struct diff_filespec *one = q->queue[i]->one;
-		if (one->mode) {
+		if (one->mode && !is_null_sha1(one->sha1)) {
 			struct cache_entry *ce;
 			ce = make_cache_entry(one->mode, one->sha1, one->path,
 				0, 0);
diff --git a/diff-lib.c b/diff-lib.c
index 9c29293..fd61acb 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -379,7 +379,8 @@ static void do_oneway_diff(struct unpack_trees_options *o,
 	if (cached && idx && ce_stage(idx)) {
 		struct diff_filepair *pair;
 		pair = diff_unmerge(&revs->diffopt, idx->name);
-		fill_filespec(pair->one, idx->sha1, idx->ce_mode);
+		if (tree)
+			fill_filespec(pair->one, tree->sha1, tree->ce_mode);
 		return;
 	}
 
diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh
index f1cfc9a..b096dc8 100755
--- a/t/t7102-reset.sh
+++ b/t/t7102-reset.sh
@@ -429,6 +429,21 @@ test_expect_success '--mixed refreshes the index' '
 	test_i18ncmp expect output
 '
 
+test_expect_success 'resetting specific path that is unmerged' '
+	git rm --cached file2 &&
+	F1=$(git rev-parse HEAD:file1) &&
+	F2=$(git rev-parse HEAD:file2) &&
+	F3=$(git rev-parse HEAD:secondfile) &&
+	{
+		echo "100644 $F1 1	file2" &&
+		echo "100644 $F2 2	file2" &&
+		echo "100644 $F3 3	file2"
+	} | git update-index --index-info &&
+	git ls-files -u &&
+	test_must_fail git reset HEAD file2 &&
+	git diff-index --exit-code --cached HEAD
+'
+
 test_expect_success 'disambiguation (1)' '
 
 	git reset --hard &&
-- 
1.7.6.178.g55272

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 0/2] refactoring diff-index
  2011-07-14  6:01     ` Junio C Hamano
@ 2011-07-19 18:13       ` Junio C Hamano
  2011-07-19 18:13         ` [PATCH 1/2] diff-lib: simplify do_diff_cache() Junio C Hamano
  2011-07-19 18:13         ` [PATCH 2/2] diff-lib: refactor run_diff_index() and do_diff_cache() Junio C Hamano
  0 siblings, 2 replies; 9+ messages in thread
From: Junio C Hamano @ 2011-07-19 18:13 UTC (permalink / raw)
  To: git

While I was looking at a rather ancient bug in "git reset" reported by
John Nowak recently, I noticed that we can reduce duplicated code in the
diff_index() codepath.

Junio C Hamano (2):
  diff-lib: simplify do_diff_cache()
  diff-lib: refactor run_diff_index() and do_diff_cache()

 diff-lib.c |   71 ++++++++++++++++-------------------------------------------
 1 files changed, 19 insertions(+), 52 deletions(-)

-- 
1.7.6.178.g55272

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/2] diff-lib: simplify do_diff_cache()
  2011-07-19 18:13       ` [PATCH 0/2] refactoring diff-index Junio C Hamano
@ 2011-07-19 18:13         ` Junio C Hamano
  2011-07-19 20:14           ` Jeff King
  2011-07-19 18:13         ` [PATCH 2/2] diff-lib: refactor run_diff_index() and do_diff_cache() Junio C Hamano
  1 sibling, 1 reply; 9+ messages in thread
From: Junio C Hamano @ 2011-07-19 18:13 UTC (permalink / raw)
  To: git

Since 34110cd (Make 'unpack_trees()' have a separate source and
destination index, 2008-03-06), we can run unpack_trees() without munging
the index at all, but do_diff_cache() tried ever so carefully to work
around the old behaviour of the function.

We can just tell unpack_trees() not to touch the original index and there
is no need to clean-up whatever the previous round has done.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 diff-lib.c |   26 +-------------------------
 1 files changed, 1 insertions(+), 25 deletions(-)

diff --git a/diff-lib.c b/diff-lib.c
index fd61acb..b5bb58d 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -480,33 +480,9 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
 {
 	struct tree *tree;
 	struct rev_info revs;
-	int i;
-	struct cache_entry **dst;
-	struct cache_entry *last = NULL;
 	struct unpack_trees_options opts;
 	struct tree_desc t;
 
-	/*
-	 * This is used by git-blame to run diff-cache internally;
-	 * it potentially needs to repeatedly run this, so we will
-	 * start by removing the higher order entries the last round
-	 * left behind.
-	 */
-	dst = active_cache;
-	for (i = 0; i < active_nr; i++) {
-		struct cache_entry *ce = active_cache[i];
-		if (ce_stage(ce)) {
-			if (last && !strcmp(ce->name, last->name))
-				continue;
-			cache_tree_invalidate_path(active_cache_tree,
-						   ce->name);
-			last = ce;
-			ce->ce_flags |= CE_REMOVE;
-		}
-		*dst++ = ce;
-	}
-	active_nr = dst - active_cache;
-
 	init_revisions(&revs, NULL);
 	init_pathspec(&revs.prune_data, opt->pathspec.raw);
 	tree = parse_tree_indirect(tree_sha1);
@@ -521,7 +497,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
 	opts.fn = oneway_diff;
 	opts.unpack_data = &revs;
 	opts.src_index = &the_index;
-	opts.dst_index = &the_index;
+	opts.dst_index = NULL;
 
 	init_tree_desc(&t, tree->buffer, tree->size);
 	if (unpack_trees(1, &t, &opts))
-- 
1.7.6.178.g55272

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 2/2] diff-lib: refactor run_diff_index() and do_diff_cache()
  2011-07-19 18:13       ` [PATCH 0/2] refactoring diff-index Junio C Hamano
  2011-07-19 18:13         ` [PATCH 1/2] diff-lib: simplify do_diff_cache() Junio C Hamano
@ 2011-07-19 18:13         ` Junio C Hamano
  1 sibling, 0 replies; 9+ messages in thread
From: Junio C Hamano @ 2011-07-19 18:13 UTC (permalink / raw)
  To: git

The latter is meant to be an API for internal callers that want to inspect
the resulting diff-queue, while the former is an implementation of "git
diff-index" command. Extract the common logic into a single helper
function and make them thin wrappers around it.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 diff-lib.c |   47 +++++++++++++++++++----------------------------
 1 files changed, 19 insertions(+), 28 deletions(-)

diff --git a/diff-lib.c b/diff-lib.c
index b5bb58d..c762655 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -440,20 +440,19 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
 	return 0;
 }
 
-int run_diff_index(struct rev_info *revs, int cached)
+static int diff_cache(struct rev_info *revs,
+		      const unsigned char *tree_sha1,
+		      const char *tree_name,
+		      int cached)
 {
-	struct object *ent;
 	struct tree *tree;
-	const char *tree_name;
-	struct unpack_trees_options opts;
 	struct tree_desc t;
+	struct unpack_trees_options opts;
 
-	ent = revs->pending.objects[0].item;
-	tree_name = revs->pending.objects[0].name;
-	tree = parse_tree_indirect(ent->sha1);
+	tree = parse_tree_indirect(tree_sha1);
 	if (!tree)
-		return error("bad tree object %s", tree_name);
-
+		return error("bad tree object %s",
+			     tree_name ? tree_name : sha1_to_hex(tree_sha1));
 	memset(&opts, 0, sizeof(opts));
 	opts.head_idx = 1;
 	opts.index_only = cached;
@@ -466,7 +465,15 @@ int run_diff_index(struct rev_info *revs, int cached)
 	opts.dst_index = NULL;
 
 	init_tree_desc(&t, tree->buffer, tree->size);
-	if (unpack_trees(1, &t, &opts))
+	return unpack_trees(1, &t, &opts);
+}
+
+int run_diff_index(struct rev_info *revs, int cached)
+{
+	struct object_array_entry *ent;
+
+	ent = revs->pending.objects;
+	if (diff_cache(revs, ent->item->sha1, ent->name, cached))
 		exit(128);
 
 	diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");
@@ -478,29 +485,13 @@ int run_diff_index(struct rev_info *revs, int cached)
 
 int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
 {
-	struct tree *tree;
 	struct rev_info revs;
-	struct unpack_trees_options opts;
-	struct tree_desc t;
 
 	init_revisions(&revs, NULL);
 	init_pathspec(&revs.prune_data, opt->pathspec.raw);
-	tree = parse_tree_indirect(tree_sha1);
-	if (!tree)
-		die("bad tree object %s", sha1_to_hex(tree_sha1));
+	revs.diffopt = *opt;
 
-	memset(&opts, 0, sizeof(opts));
-	opts.head_idx = 1;
-	opts.index_only = 1;
-	opts.diff_index_cached = !DIFF_OPT_TST(opt, FIND_COPIES_HARDER);
-	opts.merge = 1;
-	opts.fn = oneway_diff;
-	opts.unpack_data = &revs;
-	opts.src_index = &the_index;
-	opts.dst_index = NULL;
-
-	init_tree_desc(&t, tree->buffer, tree->size);
-	if (unpack_trees(1, &t, &opts))
+	if (diff_cache(&revs, tree_sha1, NULL, 1))
 		exit(128);
 	return 0;
 }
-- 
1.7.6.178.g55272

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH 1/2] diff-lib: simplify do_diff_cache()
  2011-07-19 18:13         ` [PATCH 1/2] diff-lib: simplify do_diff_cache() Junio C Hamano
@ 2011-07-19 20:14           ` Jeff King
  2011-07-19 21:28             ` Junio C Hamano
  0 siblings, 1 reply; 9+ messages in thread
From: Jeff King @ 2011-07-19 20:14 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Tue, Jul 19, 2011 at 11:13:05AM -0700, Junio C Hamano wrote:

> Since 34110cd (Make 'unpack_trees()' have a separate source and
> destination index, 2008-03-06), we can run unpack_trees() without munging
> the index at all, but do_diff_cache() tried ever so carefully to work
> around the old behaviour of the function.
> 
> We can just tell unpack_trees() not to touch the original index and there
> is no need to clean-up whatever the previous round has done.

Can we drop the calls to discard_cache in reset.c now?

$ git grep -B1 discard_cache builtin/reset.c
builtin/reset.c-        /* do_diff_cache() mangled the index */
builtin/reset.c:        discard_cache();
--
builtin/reset.c-                /* The index is still clobbered from do_diff_cache() */
builtin/reset.c:                discard_cache();

-Peff

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 1/2] diff-lib: simplify do_diff_cache()
  2011-07-19 20:14           ` Jeff King
@ 2011-07-19 21:28             ` Junio C Hamano
  0 siblings, 0 replies; 9+ messages in thread
From: Junio C Hamano @ 2011-07-19 21:28 UTC (permalink / raw)
  To: Jeff King; +Cc: Junio C Hamano, git

Jeff King <peff@peff.net> writes:

> On Tue, Jul 19, 2011 at 11:13:05AM -0700, Junio C Hamano wrote:
>
>> Since 34110cd (Make 'unpack_trees()' have a separate source and
>> destination index, 2008-03-06), we can run unpack_trees() without munging
>> the index at all, but do_diff_cache() tried ever so carefully to work
>> around the old behaviour of the function.
>> 
>> We can just tell unpack_trees() not to touch the original index and there
>> is no need to clean-up whatever the previous round has done.
>
> Can we drop the calls to discard_cache in reset.c now?

Possibly, along with "index-was-discarded" flag bit.

> $ git grep -B1 discard_cache builtin/reset.c
> builtin/reset.c-        /* do_diff_cache() mangled the index */
> builtin/reset.c:        discard_cache();
> --
> builtin/reset.c-                /* The index is still clobbered from do_diff_cache() */
> builtin/reset.c:                discard_cache();
>
> -Peff

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2011-07-19 21:28 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-07-10 22:30 reproducible unexpected behavior for 'git reset' John Nowak
2011-07-11 21:54 ` Junio C Hamano
2011-07-11 22:41   ` Junio C Hamano
2011-07-14  6:01     ` Junio C Hamano
2011-07-19 18:13       ` [PATCH 0/2] refactoring diff-index Junio C Hamano
2011-07-19 18:13         ` [PATCH 1/2] diff-lib: simplify do_diff_cache() Junio C Hamano
2011-07-19 20:14           ` Jeff King
2011-07-19 21:28             ` Junio C Hamano
2011-07-19 18:13         ` [PATCH 2/2] diff-lib: refactor run_diff_index() and do_diff_cache() Junio C Hamano

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).