threads / patch / 32568

patch, 19 partsreset $pathspec: exit with code 0 if successful

Subject: [PATCH 02/19] reset $pathspec: exit with code 0 if successful

## tl;dr

68 messages between Jan 9, 2013 and Jan 16, 2013. Diffs are folded; open one to read it.

replies: 67people: 5as markdown or json

Martin von Zweigbergk· Jan 9, 2013, 08:15 UTC · lore

[PATCH 00/19] reset improvements

This is kind of a re-roll of [1] (wow, apparently it took me almost two months to get done). The goal was, then and now, to teach "git reset" to work on an unborn branch and to not require a commit when a tree would do. This time, I also made some tangential improvements along the way, mostly related to readability and performance.

As usual, the risker patches are towards the end. In particular, I find it hard to evaluate how risky the last patch is. That last patch is responsible for much of the improvements in the timing table below, so it would be nice if it doesn't break things too badly (test pass, of course). The timings are best-of-five, wall time.

Command Before After reset (warm) 0.23 0.07 reset -q (warm) 0.23 0.03 reset . (warm) 0.09 0.07 reset -q . (warm) 0.09 0.03 reset --keep (warm) 0.31 0.29 reset --keep -q (warm) 0.31 0.29 reset (cold) 9.74 2.60 reset -q (cold) 9.85 0.37 reset . (cold) 2.66 2.51 reset -q . (cold) 2.59 0.33 reset --keep (cold) 7.58 7.52 reset --keep -q (cold) 7.37 7.21

  [1] http://thread.gmane.org/gmane.comp.version-control.git/210568/focus=210855
Martin von Zweigbergk (19):
  reset $pathspec: no need to discard index
  reset $pathspec: exit with code 0 if successful
  reset.c: pass pathspec around instead of (prefix, argv) pair
  reset: don't allow "git reset -- $pathspec" in bare repo
  reset.c: extract function for parsing arguments
  reset.c: remove unnecessary variable 'i'
  reset.c: extract function for updating {ORIG,}HEAD
  reset.c: share call to die_if_unmerged_cache()
  reset.c: replace switch by if-else
  reset --keep: only write index file once
  reset: avoid redundant error message
  reset.c: move update_index_refresh() call out of read_from_tree()
  reset.c: move lock, write and commit out of update_index_refresh()
  reset [--mixed]: don't write index file twice
  reset.c: finish entire cmd_reset() whether or not pathspec is given
  reset [--mixed] --quiet: don't refresh index
  reset $sha1 $pathspec: require $sha1 only to be treeish
  reset: allow reset on unborn branch
  reset [--mixed]: use diff-based reset whether or not pathspec was
    given
 builtin/reset.c                | 281 +++++++++++++++++++----------------------
 t/t2013-checkout-submodule.sh  |   2 +-
 t/t7102-reset.sh               |  26 +++-
 t/t7106-reset-unborn-branch.sh |  52 ++++++++
 4 files changed, 200 insertions(+), 161 deletions(-)
 create mode 100755 t/t7106-reset-unborn-branch.sh
-- 
1.8.1.rc3.331.g1ef2165
Martin von Zweigbergk· Jan 9, 2013, 08:15 UTC · re: Martin von Zweigbergk · lore

[PATCH 01/19] reset $pathspec: no need to discard index

Since 34110cd (Make 'unpack_trees()' have a separate source and destination index, 2008-03-06), the index no longer gets clobbered by do_diff_cache() and we can remove the code for discarding and re-reading it.

There are two paths to update_index_refresh() from cmd_reset(), but on both paths, either read_cache() or read_cache_unmerged() will have been called, so the call to read_cache() in this method is redundant (although practically free).

This speeds up "git reset -- ." a little on the linux-2.6 repo (best of five, warm cache):

        Before      After
real    0m0.093s    0m0.080s
user    0m0.040s    0m0.020s
sys     0m0.050s    0m0.050s
---
 builtin/reset.c | 16 +---------------
 1 file changed, 1 insertion(+), 15 deletions(-)
Show changes to builtin/reset.c +1 −15
diff --git a/builtin/reset.c b/builtin/reset.c
index 915cc9f..8cc7c72 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -126,9 +126,6 @@ static int update_index_refresh(int fd, struct lock_file *index_lock, int flags)
 		fd = hold_locked_index(index_lock, 1);
 	}
 
-	if (read_cache() < 0)
-		return error(_("Could not read index"));
-
 	result = refresh_index(&the_index, (flags), NULL, NULL,
 			       _("Unstaged changes after reset:")) ? 1 : 0;
 	if (write_cache(fd, active_cache, active_nr) ||
@@ -141,12 +138,6 @@ static void update_index_from_diff(struct diff_queue_struct *q,
 		struct diff_options *opt, void *data)
 {
 	int i;
-	int *discard_flag = data;
-
-	/* do_diff_cache() mangled the index */
-	discard_cache();
-	*discard_flag = 1;
-	read_cache();
 
 	for (i = 0; i < q->nr; i++) {
 		struct diff_filespec *one = q->queue[i]->one;
@@ -179,17 +170,15 @@ static int read_from_tree(const char *prefix, const char **argv,
 		unsigned char *tree_sha1, int refresh_flags)
 {
 	struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-	int index_fd, index_was_discarded = 0;
+	int index_fd;
 	struct diff_options opt;
 
 	memset(&opt, 0, sizeof(opt));
 	diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
 	opt.output_format = DIFF_FORMAT_CALLBACK;
 	opt.format_callback = update_index_from_diff;
-	opt.format_callback_data = &index_was_discarded;
 
 	index_fd = hold_locked_index(lock, 1);
-	index_was_discarded = 0;
 	read_cache();
 	if (do_diff_cache(tree_sha1, &opt))
 		return 1;
@@ -197,9 +186,6 @@ static int read_from_tree(const char *prefix, const char **argv,
 	diff_flush(&opt);
 	diff_tree_release_paths(&opt);
 
-	if (!index_was_discarded)
-		/* The index is still clobbered from do_diff_cache() */
-		discard_cache();
 	return update_index_refresh(index_fd, lock, refresh_flags);
 }
 
-- 
1.8.1.rc3.331.g1ef2165
Martin von Zweigbergk· Jan 9, 2013, 08:15 UTC · re: Martin von Zweigbergk · lore

"git reset $pathspec" currently exits with a non-zero exit code if the worktree is dirty after resetting, which is inconsistent with reset without pathspec, and it makes it harder to know whether the command really failed. Change it to exit with code 0 regardless of whether the worktree is dirty so that non-zero indicates an error.

This makes the 4 "disambiguation" test cases in t7102 clearer since they all used to "fail", 3 of which "failed" due to changes in the work tree. Now only the ambiguous one fails. --- I suppose this makes documenting the exit code unnecessary, since "return zero iff successful" is probably understood to be the default.

The variable in refresh_index() containing the value to be returned is called has_errors. I'm guessing I shouldn't take the name too seriously.

 builtin/reset.c               |  8 +++-----
 t/t2013-checkout-submodule.sh |  2 +-
 t/t7102-reset.sh              | 18 ++++++++++++------
 3 files changed, 16 insertions(+), 12 deletions(-)
Show changes to 3 files +16 −12

builtin/reset.c, t/t2013-checkout-submodule.sh, t/t7102-reset.sh

diff --git a/builtin/reset.c b/builtin/reset.c
index 8cc7c72..65413d0 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -119,19 +119,17 @@ static void print_new_head_line(struct commit *commit)
 
 static int update_index_refresh(int fd, struct lock_file *index_lock, int flags)
 {
-	int result;
-
 	if (!index_lock) {
 		index_lock = xcalloc(1, sizeof(struct lock_file));
 		fd = hold_locked_index(index_lock, 1);
 	}
 
-	result = refresh_index(&the_index, (flags), NULL, NULL,
-			       _("Unstaged changes after reset:")) ? 1 : 0;
+	refresh_index(&the_index, (flags), NULL, NULL,
+		      _("Unstaged changes after reset:"));
 	if (write_cache(fd, active_cache, active_nr) ||
 			commit_locked_index(index_lock))
 		return error ("Could not refresh index");
-	return result;
+	return 0;
 }
 
 static void update_index_from_diff(struct diff_queue_struct *q,
diff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh
index 70edbb3..06b18f8 100755
--- a/t/t2013-checkout-submodule.sh
+++ b/t/t2013-checkout-submodule.sh
@@ -23,7 +23,7 @@ test_expect_success '"reset <submodule>" updates the index' '
 	git update-index --refresh &&
 	git diff-files --quiet &&
 	git diff-index --quiet --cached HEAD &&
-	test_must_fail git reset HEAD^ submodule &&
+	git reset HEAD^ submodule &&
 	test_must_fail git diff-files --quiet &&
 	git reset submodule &&
 	git diff-files --quiet
diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh
index b096dc8..81b2570 100755
--- a/t/t7102-reset.sh
+++ b/t/t7102-reset.sh
@@ -388,7 +388,8 @@ test_expect_success 'test --mixed <paths>' '
 	echo 4 > file4 &&
 	echo 5 > file1 &&
 	git add file1 file3 file4 &&
-	test_must_fail git reset HEAD -- file1 file2 file3 &&
+	git reset HEAD -- file1 file2 file3 &&
+	test_must_fail git diff --quiet &&
 	git diff > output &&
 	test_cmp output expect &&
 	git diff --cached > output &&
@@ -402,7 +403,8 @@ test_expect_success 'test resetting the index at give paths' '
 	>sub/file2 &&
 	git update-index --add sub/file1 sub/file2 &&
 	T=$(git write-tree) &&
-	test_must_fail git reset HEAD sub/file2 &&
+	git reset HEAD sub/file2 &&
+	test_must_fail git diff --quiet &&
 	U=$(git write-tree) &&
 	echo "$T" &&
 	echo "$U" &&
@@ -440,7 +442,8 @@ test_expect_success 'resetting specific path that is unmerged' '
 		echo "100644 $F3 3	file2"
 	} | git update-index --index-info &&
 	git ls-files -u &&
-	test_must_fail git reset HEAD file2 &&
+	git reset HEAD file2 &&
+	test_must_fail git diff --quiet &&
 	git diff-index --exit-code --cached HEAD
 '
 
@@ -449,7 +452,8 @@ test_expect_success 'disambiguation (1)' '
 	git reset --hard &&
 	>secondfile &&
 	git add secondfile &&
-	test_must_fail git reset secondfile &&
+	git reset secondfile &&
+	test_must_fail git diff --quiet -- secondfile &&
 	test -z "$(git diff --cached --name-only)" &&
 	test -f secondfile &&
 	test ! -s secondfile
@@ -474,7 +478,8 @@ test_expect_success 'disambiguation (3)' '
 	>secondfile &&
 	git add secondfile &&
 	rm -f secondfile &&
-	test_must_fail git reset HEAD secondfile &&
+	git reset HEAD secondfile &&
+	test_must_fail git diff --quiet &&
 	test -z "$(git diff --cached --name-only)" &&
 	test ! -f secondfile
 
@@ -486,7 +491,8 @@ test_expect_success 'disambiguation (4)' '
 	>secondfile &&
 	git add secondfile &&
 	rm -f secondfile &&
-	test_must_fail git reset -- secondfile &&
+	git reset -- secondfile &&
+	test_must_fail git diff --quiet &&
 	test -z "$(git diff --cached --name-only)" &&
 	test ! -f secondfile
 '
-- 
1.8.1.rc3.331.g1ef2165
Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 03/19] reset.c: pass pathspec around instead of (prefix, argv) pair

We use the path arguments in two places in reset.c: in interactive_reset() and read_from_tree(). Both of these call get_pathspec(), so we pass the (prefix, arv) pair to both functions. Move the call to get_pathspec() out of these methods, for two reasons: 1) One argument is simpler than two. 2) It lets us use the (arguably clearer) "if (pathspec)" in place of "if (i < argc)". --- If I understand correctly, this should be rebased on top of nd/parse-pathspec. Please let me know.

 builtin/reset.c | 27 ++++++++++-----------------
 1 file changed, 10 insertions(+), 17 deletions(-)
Show changes to builtin/reset.c +10 −17
diff --git a/builtin/reset.c b/builtin/reset.c
index 65413d0..045c960 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -153,26 +153,15 @@ static void update_index_from_diff(struct diff_queue_struct *q,
 	}
 }
 
-static int interactive_reset(const char *revision, const char **argv,
-			     const char *prefix)
-{
-	const char **pathspec = NULL;
-
-	if (*argv)
-		pathspec = get_pathspec(prefix, argv);
-
-	return run_add_interactive(revision, "--patch=reset", pathspec);
-}
-
-static int read_from_tree(const char *prefix, const char **argv,
-		unsigned char *tree_sha1, int refresh_flags)
+static int read_from_tree(const char **pathspec, unsigned char *tree_sha1,
+			  int refresh_flags)
 {
 	struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 	int index_fd;
 	struct diff_options opt;
 
 	memset(&opt, 0, sizeof(opt));
-	diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
+	diff_tree_setup_paths(pathspec, &opt);
 	opt.output_format = DIFF_FORMAT_CALLBACK;
 	opt.format_callback = update_index_from_diff;
 
@@ -216,6 +205,7 @@ 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];
+	const char **pathspec = NULL;
 	struct commit *commit;
 	struct strbuf msg = STRBUF_INIT;
 	const struct option options[] = {
@@ -287,22 +277,25 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		die(_("Could not parse object '%s'."), rev);
 	hashcpy(sha1, commit->object.sha1);
 
+	if (i < argc)
+		pathspec = get_pathspec(prefix, argv + i);
+
 	if (patch_mode) {
 		if (reset_type != NONE)
 			die(_("--patch is incompatible with --{hard,mixed,soft}"));
-		return interactive_reset(rev, argv + i, prefix);
+		return run_add_interactive(rev, "--patch=reset", pathspec);
 	}
 
 	/* git reset tree [--] paths... can be used to
 	 * load chosen paths from the tree into the index without
 	 * affecting the working tree nor HEAD. */
-	if (i < argc) {
+	if (pathspec) {
 		if (reset_type == MIXED)
 			warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."));
 		else if (reset_type != NONE)
 			die(_("Cannot do %s reset with paths."),
 					_(reset_type_names[reset_type]));
-		return read_from_tree(prefix, argv + i, sha1,
+		return read_from_tree(pathspec, sha1,
 				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
 	}
 	if (reset_type == NONE)
-- 
1.8.1.rc3.331.g1ef2165
Matt Kraai· Jan 9, 2013, 11:42 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH 03/19] reset.c: pass pathspec around instead of (prefix, argv) pair

On Wed, Jan 09, 2013 at 12:16:00AM -0800, Martin von Zweigbergk wrote:
> We use the path arguments in two places in reset.c: in
> interactive_reset() and read_from_tree(). Both of these call
> get_pathspec(), so we pass the (prefix, arv) pair to both
                                          ^^^
argv is misspelled.
-- 
Matt
Junio C Hamano· Jan 9, 2013, 19:26 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH 03/19] reset.c: pass pathspec around instead of (prefix, argv) pair

Martin von Zweigbergk <martinvonz@gmail.com> writes:
Show 9 quoted lines
> We use the path arguments in two places in reset.c: in
> interactive_reset() and read_from_tree(). Both of these call
> get_pathspec(), so we pass the (prefix, arv) pair to both
> functions. Move the call to get_pathspec() out of these methods, for
> two reasons: 1) One argument is simpler than two. 2) It lets us use
> the (arguably clearer) "if (pathspec)" in place of "if (i < argc)".
> ---
> If I understand correctly, this should be rebased on top of
> nd/parse-pathspec. Please let me know.

Yeah, this will conflict with the get_pathspec-to-parse_pathspec conversion Duy has been working on.

Without the interactions with that topic, the conversion seems straightforward to me, though.

Show 75 quoted lines
>
>  builtin/reset.c | 27 ++++++++++-----------------
>  1 file changed, 10 insertions(+), 17 deletions(-)
>
> diff --git a/builtin/reset.c b/builtin/reset.c
> index 65413d0..045c960 100644
> --- a/builtin/reset.c
> +++ b/builtin/reset.c
> @@ -153,26 +153,15 @@ static void update_index_from_diff(struct diff_queue_struct *q,
>  	}
>  }
>  
> -static int interactive_reset(const char *revision, const char **argv,
> -			     const char *prefix)
> -{
> -	const char **pathspec = NULL;
> -
> -	if (*argv)
> -		pathspec = get_pathspec(prefix, argv);
> -
> -	return run_add_interactive(revision, "--patch=reset", pathspec);
> -}
> -
> -static int read_from_tree(const char *prefix, const char **argv,
> -		unsigned char *tree_sha1, int refresh_flags)
> +static int read_from_tree(const char **pathspec, unsigned char *tree_sha1,
> +			  int refresh_flags)
>  {
>  	struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
>  	int index_fd;
>  	struct diff_options opt;
>  
>  	memset(&opt, 0, sizeof(opt));
> -	diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
> +	diff_tree_setup_paths(pathspec, &opt);
>  	opt.output_format = DIFF_FORMAT_CALLBACK;
>  	opt.format_callback = update_index_from_diff;
>  
> @@ -216,6 +205,7 @@ 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];
> +	const char **pathspec = NULL;
>  	struct commit *commit;
>  	struct strbuf msg = STRBUF_INIT;
>  	const struct option options[] = {
> @@ -287,22 +277,25 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
>  		die(_("Could not parse object '%s'."), rev);
>  	hashcpy(sha1, commit->object.sha1);
>  
> +	if (i < argc)
> +		pathspec = get_pathspec(prefix, argv + i);
> +
>  	if (patch_mode) {
>  		if (reset_type != NONE)
>  			die(_("--patch is incompatible with --{hard,mixed,soft}"));
> -		return interactive_reset(rev, argv + i, prefix);
> +		return run_add_interactive(rev, "--patch=reset", pathspec);
>  	}
>  
>  	/* git reset tree [--] paths... can be used to
>  	 * load chosen paths from the tree into the index without
>  	 * affecting the working tree nor HEAD. */
> -	if (i < argc) {
> +	if (pathspec) {
>  		if (reset_type == MIXED)
>  			warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."));
>  		else if (reset_type != NONE)
>  			die(_("Cannot do %s reset with paths."),
>  					_(reset_type_names[reset_type]));
> -		return read_from_tree(prefix, argv + i, sha1,
> +		return read_from_tree(pathspec, sha1,
>  				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
>  	}
>  	if (reset_type == NONE)
Duy Nguyen· Jan 10, 2013, 11:05 UTC · re: Junio C Hamano · lore

Re: [PATCH 03/19] reset.c: pass pathspec around instead of (prefix, argv) pair

On Thu, Jan 10, 2013 at 2:26 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 14 quoted lines
> Martin von Zweigbergk <martinvonz@gmail.com> writes:
>
>> We use the path arguments in two places in reset.c: in
>> interactive_reset() and read_from_tree(). Both of these call
>> get_pathspec(), so we pass the (prefix, arv) pair to both
>> functions. Move the call to get_pathspec() out of these methods, for
>> two reasons: 1) One argument is simpler than two. 2) It lets us use
>> the (arguably clearer) "if (pathspec)" in place of "if (i < argc)".
>> ---
>> If I understand correctly, this should be rebased on top of
>> nd/parse-pathspec. Please let me know.
>
> Yeah, this will conflict with the get_pathspec-to-parse_pathspec
> conversion Duy has been working on.

Or I could hold off nd/parse-pathspec if this series has a better chance of graduation first. Decision?

-- 
Duy
Junio C Hamano· Jan 10, 2013, 23:09 UTC · re: Duy Nguyen · lore

Re: [PATCH 03/19] reset.c: pass pathspec around instead of (prefix, argv) pair

Duy Nguyen <pclouds@gmail.com> writes:
Show 18 quoted lines
> On Thu, Jan 10, 2013 at 2:26 AM, Junio C Hamano <gitster@pobox.com> wrote:
>> Martin von Zweigbergk <martinvonz@gmail.com> writes:
>>
>>> We use the path arguments in two places in reset.c: in
>>> interactive_reset() and read_from_tree(). Both of these call
>>> get_pathspec(), so we pass the (prefix, arv) pair to both
>>> functions. Move the call to get_pathspec() out of these methods, for
>>> two reasons: 1) One argument is simpler than two. 2) It lets us use
>>> the (arguably clearer) "if (pathspec)" in place of "if (i < argc)".
>>> ---
>>> If I understand correctly, this should be rebased on top of
>>> nd/parse-pathspec. Please let me know.
>>
>> Yeah, this will conflict with the get_pathspec-to-parse_pathspec
>> conversion Duy has been working on.
>
> Or I could hold off nd/parse-pathspec if this series has a better
> chance of graduation first. Decision?
I am greedy and want to have both ;-)

Before deciding that, I'd appreciate a second set of eyes giving Martin's series an independent review, to see if it is going in the right direction. I think I didn't spot anything questionable in it myself, but second opinion always helps.

There is no textual conflict between the two topics at the moment, but because the ultimate goal of your series is to remove all uses of the pathspec.raw[] field outside the implementation of pathspec matching, it might help to rename the field to _private_raw (or remove it), and either make get_pathspec() private or disappear, to ensure that the compiler will help us catching semantic conflicts with new users of it at a late stage of your series.

Duy Nguyen· Jan 11, 2013, 11:10 UTC · re: Junio C Hamano · lore

Re: [PATCH 03/19] reset.c: pass pathspec around instead of (prefix, argv) pair

On Fri, Jan 11, 2013 at 6:09 AM, Junio C Hamano <gitster@pobox.com> wrote:
>> Or I could hold off nd/parse-pathspec if this series has a better
>> chance of graduation first. Decision?
>
> I am greedy and want to have both ;-)
Apparently I have no problems with your being greedy.
Show 7 quoted lines
> There is no textual conflict between the two topics at the moment,
> but because the ultimate goal of your series is to remove all uses
> of the pathspec.raw[] field outside the implementation of pathspec
> matching, it might help to rename the field to _private_raw (or
> remove it), and either make get_pathspec() private or disappear, to
> ensure that the compiler will help us catching semantic conflicts
> with new users of it at a late stage of your series.

There are still some uses for get_pathspec() and new call sites won't cause big problems because they would need init_pathspec() to convert get_pathspec() results to struct pathspec. I will rename raw[] though.

-- 
Duy
Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 04/19] reset: don't allow "git reset -- $pathspec" in bare repo

---
 builtin/reset.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
Show changes to builtin/reset.c +4 −2
diff --git a/builtin/reset.c b/builtin/reset.c
index 045c960..664fad9 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -295,8 +295,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		else if (reset_type != NONE)
 			die(_("Cannot do %s reset with paths."),
 					_(reset_type_names[reset_type]));
-		return read_from_tree(pathspec, sha1,
-				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
 	}
 	if (reset_type == NONE)
 		reset_type = MIXED; /* by default */
@@ -308,6 +306,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		die(_("%s reset is not allowed in a bare repository"),
 		    _(reset_type_names[reset_type]));
 
+	if (pathspec)
+		return read_from_tree(pathspec, sha1,
+				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+
 	/* Soft reset does not touch the index file nor the working tree
 	 * at all, but requires them in a good order.  Other resets reset
 	 * the index file to the tree object we are switching to. */
-- 
1.8.1.rc3.331.g1ef2165
Junio C Hamano· Jan 9, 2013, 19:32 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH 04/19] reset: don't allow "git reset -- $pathspec" in bare repo

Martin von Zweigbergk <martinvonz@gmail.com> writes:
> ---
>  builtin/reset.c | 6 ++++--
>  1 file changed, 4 insertions(+), 2 deletions(-)

With the patch that does not have any explicit check for bareness nor new error message to scold user with, it is rather hard to tell what is going on, without any description on what (if anything) is broken at the end user level and what remedy is done about that breakage...

Show 25 quoted lines
>
> diff --git a/builtin/reset.c b/builtin/reset.c
> index 045c960..664fad9 100644
> --- a/builtin/reset.c
> +++ b/builtin/reset.c
> @@ -295,8 +295,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
>  		else if (reset_type != NONE)
>  			die(_("Cannot do %s reset with paths."),
>  					_(reset_type_names[reset_type]));
> -		return read_from_tree(pathspec, sha1,
> -				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
>  	}
>  	if (reset_type == NONE)
>  		reset_type = MIXED; /* by default */
> @@ -308,6 +306,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
>  		die(_("%s reset is not allowed in a bare repository"),
>  		    _(reset_type_names[reset_type]));
>  
> +	if (pathspec)
> +		return read_from_tree(pathspec, sha1,
> +				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
> +
>  	/* Soft reset does not touch the index file nor the working tree
>  	 * at all, but requires them in a good order.  Other resets reset
>  	 * the index file to the tree object we are switching to. */
Martin von Zweigbergk· Jan 10, 2013, 08:24 UTC · re: Junio C Hamano · lore

Re: [PATCH 04/19] reset: don't allow "git reset -- $pathspec" in bare repo

On Wed, Jan 9, 2013 at 11:32 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 11 quoted lines
> Martin von Zweigbergk <martinvonz@gmail.com> writes:
>
>> ---
>>  builtin/reset.c | 6 ++++--
>>  1 file changed, 4 insertions(+), 2 deletions(-)
>
> With the patch that does not have any explicit check for bareness
> nor new error message to scold user with, it is rather hard to tell
> what is going on, without any description on what (if anything) is
> broken at the end user level and what remedy is done about that
> breakage...
Will include the following in a re-roll.
    reset: don't allow "git reset -- $pathspec" in bare repo
    Running e.g. "git reset ." in a bare repo results in an index file
    being created from the HEAD commit. The differences compared to the
    index are then printed as usual, but since there is no worktree, it
    will appear as if all files are deleted. For example, in a bare clone
    of git.git:
      Unstaged changes after reset:
      D       .gitattributes
      D       .gitignore
      D       .mailmap
      ...
    This happens because the check for is_bare_repository() happens after
    we branch off into read_from_tree() to reset with paths. Fix by moving
    the branching point after the check.
Junio C Hamano· Jan 10, 2013, 18:04 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH 04/19] reset: don't allow "git reset -- $pathspec" in bare repo

Martin von Zweigbergk <martinvonz@gmail.com> writes:
>     ... Fix by moving
>     the branching point after the check.

OK, that is what I missed. We have an existing check for mixed reset, which was originally meant to handle case without any pathspec but can use the same error condition (i.e. type is mixed and repository is bare) and error message (i.e. no mixed reset in a bare repository). "reset with pathspec" was done before that check kicked in.

Thanks for clarification (and sorry for the noise).
Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 05/19] reset.c: extract function for parsing arguments

Declutter cmd_reset() a bit by moving out the argument parsing to its
own function.
---
 builtin/reset.c | 71 ++++++++++++++++++++++++++++++---------------------------
 1 file changed, 38 insertions(+), 33 deletions(-)
Show changes to builtin/reset.c +38 −33
diff --git a/builtin/reset.c b/builtin/reset.c
index 664fad9..9473725 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -198,36 +198,10 @@ static void die_if_unmerged_cache(int reset_type)
 
 }
 
-int cmd_reset(int argc, const char **argv, const char *prefix)
-{
-	int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
-	int patch_mode = 0;
+const char **parse_args(int argc, const char **argv, const char *prefix, const char **rev_ret) {
+	int i = 0;
 	const char *rev = "HEAD";
-	unsigned char sha1[20], *orig = NULL, sha1_orig[20],
-				*old_orig = NULL, sha1_old_orig[20];
-	const char **pathspec = NULL;
-	struct commit *commit;
-	struct strbuf msg = STRBUF_INIT;
-	const struct option options[] = {
-		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
-		OPT_SET_INT(0, "mixed", &reset_type,
-						N_("reset HEAD and index"), MIXED),
-		OPT_SET_INT(0, "soft", &reset_type, N_("reset only HEAD"), SOFT),
-		OPT_SET_INT(0, "hard", &reset_type,
-				N_("reset HEAD, index and working tree"), HARD),
-		OPT_SET_INT(0, "merge", &reset_type,
-				N_("reset HEAD, index and working tree"), MERGE),
-		OPT_SET_INT(0, "keep", &reset_type,
-				N_("reset HEAD but keep local changes"), KEEP),
-		OPT_BOOLEAN('p', "patch", &patch_mode, N_("select hunks interactively")),
-		OPT_END()
-	};
-
-	git_config(git_default_config, NULL);
-
-	argc = parse_options(argc, argv, prefix, options, git_reset_usage,
-						PARSE_OPT_KEEP_DASHDASH);
-
+	unsigned char unused[20];
 	/*
 	 * Possible arguments are:
 	 *
@@ -250,7 +224,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_committish(argv[i], unused)) {
 			/*
 			 * Ok, argv[i] looks like a rev; it should not
 			 * be a filename.
@@ -262,6 +236,40 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 			verify_filename(prefix, argv[i], 1);
 		}
 	}
+	*rev_ret = rev;
+	return i < argc ? get_pathspec(prefix, argv + i) : NULL;
+}
+
+int cmd_reset(int argc, const char **argv, const char *prefix)
+{
+	int reset_type = NONE, update_ref_status = 0, quiet = 0;
+	int patch_mode = 0;
+	const char *rev;
+	unsigned char sha1[20], *orig = NULL, sha1_orig[20],
+				*old_orig = NULL, sha1_old_orig[20];
+	const char **pathspec = NULL;
+	struct commit *commit;
+	struct strbuf msg = STRBUF_INIT;
+	const struct option options[] = {
+		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
+		OPT_SET_INT(0, "mixed", &reset_type,
+						N_("reset HEAD and index"), MIXED),
+		OPT_SET_INT(0, "soft", &reset_type, N_("reset only HEAD"), SOFT),
+		OPT_SET_INT(0, "hard", &reset_type,
+				N_("reset HEAD, index and working tree"), HARD),
+		OPT_SET_INT(0, "merge", &reset_type,
+				N_("reset HEAD, index and working tree"), MERGE),
+		OPT_SET_INT(0, "keep", &reset_type,
+				N_("reset HEAD but keep local changes"), KEEP),
+		OPT_BOOLEAN('p', "patch", &patch_mode, N_("select hunks interactively")),
+		OPT_END()
+	};
+
+	git_config(git_default_config, NULL);
+
+	argc = parse_options(argc, argv, prefix, options, git_reset_usage,
+						PARSE_OPT_KEEP_DASHDASH);
+	pathspec = parse_args(argc, argv, prefix, &rev);
 
 	if (get_sha1_committish(rev, sha1))
 		die(_("Failed to resolve '%s' as a valid ref."), rev);
@@ -277,9 +285,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		die(_("Could not parse object '%s'."), rev);
 	hashcpy(sha1, commit->object.sha1);
 
-	if (i < argc)
-		pathspec = get_pathspec(prefix, argv + i);
-
 	if (patch_mode) {
 		if (reset_type != NONE)
 			die(_("--patch is incompatible with --{hard,mixed,soft}"));
-- 
1.8.1.rc3.331.g1ef2165
Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 06/19] reset.c: remove unnecessary variable 'i'

Throughout most of parse_args(), the variable 'i' remains at 0. In the remaining few cases, we can do pointer arithmentic on argv itself instead. --- This is clearly mostly a matter of taste. The remainder of the series does not depend on it in any way.

 builtin/reset.c | 29 ++++++++++++++---------------
 1 file changed, 14 insertions(+), 15 deletions(-)
Show changes to builtin/reset.c +14 −15
diff --git a/builtin/reset.c b/builtin/reset.c
index 9473725..68be05c 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -199,7 +199,6 @@ static void die_if_unmerged_cache(int reset_type)
 }
 
 const char **parse_args(int argc, const char **argv, const char *prefix, const char **rev_ret) {
-	int i = 0;
 	const char *rev = "HEAD";
 	unsigned char unused[20];
 	/*
@@ -210,34 +209,34 @@ const char **parse_args(int argc, const char **argv, const char *prefix, const c
 	 * git reset [-opts] -- <paths>...
 	 * git reset [-opts] <paths>...
 	 *
-	 * At this point, argv[i] points immediately after [-opts].
+	 * At this point, argv points immediately after [-opts].
 	 */
 
-	if (i < argc) {
-		if (!strcmp(argv[i], "--")) {
-			i++; /* reset to HEAD, possibly with paths */
-		} else if (i + 1 < argc && !strcmp(argv[i+1], "--")) {
-			rev = argv[i];
-			i += 2;
+	if (argc) {
+		if (!strcmp(argv[0], "--")) {
+			argv++; /* reset to HEAD, possibly with paths */
+		} else if (argc > 1 && !strcmp(argv[1], "--")) {
+			rev = argv[0];
+			argv += 2;
 		}
 		/*
-		 * Otherwise, argv[i] could be either <rev> or <paths> and
+		 * Otherwise, argv[0] could be either <rev> or <paths> and
 		 * has to be unambiguous.
 		 */
-		else if (!get_sha1_committish(argv[i], unused)) {
+		else if (!get_sha1_committish(argv[0], unused)) {
 			/*
-			 * Ok, argv[i] looks like a rev; it should not
+			 * Ok, argv[0] looks like a rev; it should not
 			 * be a filename.
 			 */
-			verify_non_filename(prefix, argv[i]);
-			rev = argv[i++];
+			verify_non_filename(prefix, argv[0]);
+			rev = *argv++;
 		} else {
 			/* Otherwise we treat this as a filename */
-			verify_filename(prefix, argv[i], 1);
+			verify_filename(prefix, argv[0], 1);
 		}
 	}
 	*rev_ret = rev;
-	return i < argc ? get_pathspec(prefix, argv + i) : NULL;
+	return *argv ? get_pathspec(prefix, argv) : NULL;
 }
 
 int cmd_reset(int argc, const char **argv, const char *prefix)
-- 
1.8.1.rc3.331.g1ef2165
Junio C Hamano· Jan 9, 2013, 19:39 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH 06/19] reset.c: remove unnecessary variable 'i'

Martin von Zweigbergk <martinvonz@gmail.com> writes:
Show 6 quoted lines
> Throughout most of parse_args(), the variable 'i' remains at 0. In the
> remaining few cases, we can do pointer arithmentic on argv itself
> instead.
> ---
> This is clearly mostly a matter of taste. The remainder of the series
> does not depend on it in any way.
I agree that it indeed is a matter of taste between
 (1) look at av[i], check with (i < ac) for the end, and increment i to
     iterate over the arguments; and
 (2) look at av[0], check with (0 < ac) for the end, and increment
     av and decrement ac at the same time to iterate over the
     arguments.

When (ac, av) appear as a pair, however, adjusting only av without adjusting ac is asking for future trouble. It violates a common expectation that av[ac] points at the NULL at the end of the list.

If a code chooses to use !av[0] as the terminating condition and never looks at ac, then incrementing only av is fine, but in such a case, the function probably should lose ac altogether.

Show 64 quoted lines
>  builtin/reset.c | 29 ++++++++++++++---------------
>  1 file changed, 14 insertions(+), 15 deletions(-)
>
> diff --git a/builtin/reset.c b/builtin/reset.c
> index 9473725..68be05c 100644
> --- a/builtin/reset.c
> +++ b/builtin/reset.c
> @@ -199,7 +199,6 @@ static void die_if_unmerged_cache(int reset_type)
>  }
>  
>  const char **parse_args(int argc, const char **argv, const char *prefix, const char **rev_ret) {
> -	int i = 0;
>  	const char *rev = "HEAD";
>  	unsigned char unused[20];
>  	/*
> @@ -210,34 +209,34 @@ const char **parse_args(int argc, const char **argv, const char *prefix, const c
>  	 * git reset [-opts] -- <paths>...
>  	 * git reset [-opts] <paths>...
>  	 *
> -	 * At this point, argv[i] points immediately after [-opts].
> +	 * At this point, argv points immediately after [-opts].
>  	 */
>  
> -	if (i < argc) {
> -		if (!strcmp(argv[i], "--")) {
> -			i++; /* reset to HEAD, possibly with paths */
> -		} else if (i + 1 < argc && !strcmp(argv[i+1], "--")) {
> -			rev = argv[i];
> -			i += 2;
> +	if (argc) {
> +		if (!strcmp(argv[0], "--")) {
> +			argv++; /* reset to HEAD, possibly with paths */
> +		} else if (argc > 1 && !strcmp(argv[1], "--")) {
> +			rev = argv[0];
> +			argv += 2;
>  		}
>  		/*
> -		 * Otherwise, argv[i] could be either <rev> or <paths> and
> +		 * Otherwise, argv[0] could be either <rev> or <paths> and
>  		 * has to be unambiguous.
>  		 */
> -		else if (!get_sha1_committish(argv[i], unused)) {
> +		else if (!get_sha1_committish(argv[0], unused)) {
>  			/*
> -			 * Ok, argv[i] looks like a rev; it should not
> +			 * Ok, argv[0] looks like a rev; it should not
>  			 * be a filename.
>  			 */
> -			verify_non_filename(prefix, argv[i]);
> -			rev = argv[i++];
> +			verify_non_filename(prefix, argv[0]);
> +			rev = *argv++;
>  		} else {
>  			/* Otherwise we treat this as a filename */
> -			verify_filename(prefix, argv[i], 1);
> +			verify_filename(prefix, argv[0], 1);
>  		}
>  	}
>  	*rev_ret = rev;
> -	return i < argc ? get_pathspec(prefix, argv + i) : NULL;
> +	return *argv ? get_pathspec(prefix, argv) : NULL;
>  }
>  
>  int cmd_reset(int argc, const char **argv, const char *prefix)
Martin von Zweigbergk· Jan 10, 2013, 08:41 UTC · re: Junio C Hamano · lore

Re: [PATCH 06/19] reset.c: remove unnecessary variable 'i'

On Wed, Jan 9, 2013 at 11:39 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 21 quoted lines
> Martin von Zweigbergk <martinvonz@gmail.com> writes:
>
>> Throughout most of parse_args(), the variable 'i' remains at 0. In the
>> remaining few cases, we can do pointer arithmentic on argv itself
>> instead.
>> ---
>> This is clearly mostly a matter of taste. The remainder of the series
>> does not depend on it in any way.
>
> I agree that it indeed is a matter of taste between
>
>  (1) look at av[i], check with (i < ac) for the end, and increment i to
>      iterate over the arguments; and
>
>  (2) look at av[0], check with (0 < ac) for the end, and increment
>      av and decrement ac at the same time to iterate over the
>      arguments.
>
> When (ac, av) appear as a pair, however, adjusting only av without
> adjusting ac is asking for future trouble.  It violates a common
> expectation that av[ac] points at the NULL at the end of the list.
Good points.
> If a code chooses to use !av[0] as the terminating condition and
> never looks at ac, then incrementing only av is fine, but in such a
> case, the function probably should lose ac altogether.

Makes sense. I've picked this style for now (i.e. dropped both 'i' and 'argc'). I was surprised by the style that referred to the variable in many places where it was know to be 0, but I'm no experienced C programmer, so if that's a common practice when it comes to argument parsing, I'm also happy to drop the patch. Let me know what you prefer.

Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 07/19] reset.c: extract function for updating {ORIG,}HEAD

By extracting the code for updating the HEAD and ORIG_HEAD symbolic
references to a separate function, we declutter cmd_reset() a bit and
we make it clear that e.g. the four variables {,sha1_}{,old_}orig are
only used by this code.
---
 builtin/reset.c | 39 +++++++++++++++++++++++----------------
 1 file changed, 23 insertions(+), 16 deletions(-)
Show changes to builtin/reset.c +23 −16
diff --git a/builtin/reset.c b/builtin/reset.c
index 68be05c..4d556e7 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -239,16 +239,35 @@ const char **parse_args(int argc, const char **argv, const char *prefix, const c
 	return *argv ? get_pathspec(prefix, argv) : NULL;
 }
 
+static int update_refs(const char *rev, const unsigned char *sha1) {
+	int update_ref_status;
+	struct strbuf msg = STRBUF_INIT;
+	unsigned char *orig = NULL, sha1_orig[20],
+		*old_orig = NULL, sha1_old_orig[20];
+
+	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);
+	strbuf_release(&msg);
+	return update_ref_status;
+}
+
 int cmd_reset(int argc, const char **argv, const char *prefix)
 {
 	int reset_type = NONE, update_ref_status = 0, quiet = 0;
 	int patch_mode = 0;
 	const char *rev;
-	unsigned char sha1[20], *orig = NULL, sha1_orig[20],
-				*old_orig = NULL, sha1_old_orig[20];
+	unsigned char sha1[20];
 	const char **pathspec = NULL;
 	struct commit *commit;
-	struct strbuf msg = STRBUF_INIT;
 	const struct option options[] = {
 		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
 		OPT_SET_INT(0, "mixed", &reset_type,
@@ -332,17 +351,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 
 	/* 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);
+	update_ref_status = update_refs(rev, sha1);
 
 	switch (reset_type) {
 	case HARD:
@@ -359,7 +368,5 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 
 	remove_branch_state();
 
-	strbuf_release(&msg);
-
 	return update_ref_status;
 }
-- 
1.8.1.rc3.331.g1ef2165
Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 08/19] reset.c: share call to die_if_unmerged_cache()

Use a single condition to guard the call to die_if_unmerged_cache for both --soft and --keep. This avoids the small distraction of the precondition check from the logic following it.

Also change an instance of
  if (e)
    err = err || f();
to the almost as short, but clearer
  if (e && !err)
    err = f();
(which is equivalent since we only care whether exit code is 0)
---
 builtin/reset.c | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)
Show changes to builtin/reset.c +6 −8
diff --git a/builtin/reset.c b/builtin/reset.c
index 4d556e7..42d1563 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -336,15 +336,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	/* Soft reset does not touch the index file nor the working tree
 	 * at all, but requires them in a good order.  Other resets reset
 	 * the index file to the tree object we are switching to. */
-	if (reset_type == SOFT)
+	if (reset_type == SOFT || reset_type == KEEP)
 		die_if_unmerged_cache(reset_type);
-	else {
-		int err;
-		if (reset_type == KEEP)
-			die_if_unmerged_cache(reset_type);
-		err = reset_index_file(sha1, reset_type, quiet);
-		if (reset_type == KEEP)
-			err = err || reset_index_file(sha1, MIXED, quiet);
+
+	if (reset_type != SOFT) {
+		int err = reset_index_file(sha1, reset_type, quiet);
+		if (reset_type == KEEP && !err)
+			err = reset_index_file(sha1, MIXED, quiet);
 		if (err)
 			die(_("Could not reset index file to revision '%s'."), rev);
 	}
-- 
1.8.1.rc3.331.g1ef2165
Junio C Hamano· Jan 9, 2013, 19:48 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH 08/19] reset.c: share call to die_if_unmerged_cache()

Martin von Zweigbergk <martinvonz@gmail.com> writes:
Show 15 quoted lines
> Use a single condition to guard the call to die_if_unmerged_cache for
> both --soft and --keep. This avoids the small distraction of the
> precondition check from the logic following it.
>
> Also change an instance of
>
>   if (e)
>     err = err || f();
>
> to the almost as short, but clearer
>
>   if (e && !err)
>     err = f();
>
> (which is equivalent since we only care whether exit code is 0)

It is not just equivalent, but should give us identical result, even if we cared the actual value.

And I tend to agree that the latter is more readable, especially when f() can be longer, which is often the case in real life.

Happy to see this change.
Show 30 quoted lines
> ---
>  builtin/reset.c | 14 ++++++--------
>  1 file changed, 6 insertions(+), 8 deletions(-)
>
> diff --git a/builtin/reset.c b/builtin/reset.c
> index 4d556e7..42d1563 100644
> --- a/builtin/reset.c
> +++ b/builtin/reset.c
> @@ -336,15 +336,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
>  	/* Soft reset does not touch the index file nor the working tree
>  	 * at all, but requires them in a good order.  Other resets reset
>  	 * the index file to the tree object we are switching to. */
> -	if (reset_type == SOFT)
> +	if (reset_type == SOFT || reset_type == KEEP)
>  		die_if_unmerged_cache(reset_type);
> -	else {
> -		int err;
> -		if (reset_type == KEEP)
> -			die_if_unmerged_cache(reset_type);
> -		err = reset_index_file(sha1, reset_type, quiet);
> -		if (reset_type == KEEP)
> -			err = err || reset_index_file(sha1, MIXED, quiet);
> +
> +	if (reset_type != SOFT) {
> +		int err = reset_index_file(sha1, reset_type, quiet);
> +		if (reset_type == KEEP && !err)
> +			err = reset_index_file(sha1, MIXED, quiet);
>  		if (err)
>  			die(_("Could not reset index file to revision '%s'."), rev);
>  	}
Martin von Zweigbergk· Jan 10, 2013, 08:51 UTC · re: Junio C Hamano · lore

Re: [PATCH 08/19] reset.c: share call to die_if_unmerged_cache()

On Wed, Jan 9, 2013 at 11:48 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 20 quoted lines
> Martin von Zweigbergk <martinvonz@gmail.com> writes:
>
>> Use a single condition to guard the call to die_if_unmerged_cache for
>> both --soft and --keep. This avoids the small distraction of the
>> precondition check from the logic following it.
>>
>> Also change an instance of
>>
>>   if (e)
>>     err = err || f();
>>
>> to the almost as short, but clearer
>>
>>   if (e && !err)
>>     err = f();
>>
>> (which is equivalent since we only care whether exit code is 0)
>
> It is not just equivalent, but should give us identical result, even
> if we cared the actual value.

If err is initially 0, and f() evaluates to 2, err would be 1 in the first case, but 2 in the second case, right?

I think the two might be identical in e.g. JavaScript and Python, but I don't use either much.

Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 09/19] reset.c: replace switch by if-else

---
 builtin/reset.c | 13 +++----------
 1 file changed, 3 insertions(+), 10 deletions(-)
Show changes to builtin/reset.c +3 −10
diff --git a/builtin/reset.c b/builtin/reset.c
index 42d1563..05ccfd4 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -351,18 +351,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	 * saving the previous head in ORIG_HEAD before. */
 	update_ref_status = update_refs(rev, sha1);
 
-	switch (reset_type) {
-	case HARD:
-		if (!update_ref_status && !quiet)
-			print_new_head_line(commit);
-		break;
-	case SOFT: /* Nothing else to do. */
-		break;
-	case MIXED: /* Report what has not been updated. */
+	if (reset_type == HARD && !update_ref_status && !quiet)
+		print_new_head_line(commit);
+	else if (reset_type == MIXED) /* Report what has not been updated. */
 		update_index_refresh(0, NULL,
 				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
-		break;
-	}
 
 	remove_branch_state();
 
-- 
1.8.1.rc3.331.g1ef2165
Junio C Hamano· Jan 9, 2013, 19:53 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH 09/19] reset.c: replace switch by if-else

Martin von Zweigbergk <martinvonz@gmail.com> writes:
Show 27 quoted lines
> ---
>  builtin/reset.c | 13 +++----------
>  1 file changed, 3 insertions(+), 10 deletions(-)
>
> diff --git a/builtin/reset.c b/builtin/reset.c
> index 42d1563..05ccfd4 100644
> --- a/builtin/reset.c
> +++ b/builtin/reset.c
> @@ -351,18 +351,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
>  	 * saving the previous head in ORIG_HEAD before. */
>  	update_ref_status = update_refs(rev, sha1);
>  
> -	switch (reset_type) {
> -	case HARD:
> -		if (!update_ref_status && !quiet)
> -			print_new_head_line(commit);
> -		break;
> -	case SOFT: /* Nothing else to do. */
> -		break;
> -	case MIXED: /* Report what has not been updated. */
> +	if (reset_type == HARD && !update_ref_status && !quiet)
> +		print_new_head_line(commit);
> +	else if (reset_type == MIXED) /* Report what has not been updated. */
>  		update_index_refresh(0, NULL,
>  				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
> -		break;
> -	}
Justification?

It might be shorter, but I somehow find the original _much_ easier to follow, and to possibly extend. The case arms delineate the major modes of operation, and when somebody is interested in what happens in "reset --hard", the case labels allow eyes to immediately spot and skip uninteresting case arms. On the other hand, the updated one forces you to read the if/else cascade through.

Martin von Zweigbergk· Jan 11, 2013, 06:35 UTC · re: Junio C Hamano · lore

Re: [PATCH 09/19] reset.c: replace switch by if-else

On Wed, Jan 9, 2013 at 11:53 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 31 quoted lines
> Martin von Zweigbergk <martinvonz@gmail.com> writes:
>
>> ---
>>  builtin/reset.c | 13 +++----------
>>  1 file changed, 3 insertions(+), 10 deletions(-)
>>
>> diff --git a/builtin/reset.c b/builtin/reset.c
>> index 42d1563..05ccfd4 100644
>> --- a/builtin/reset.c
>> +++ b/builtin/reset.c
>> @@ -351,18 +351,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
>>        * saving the previous head in ORIG_HEAD before. */
>>       update_ref_status = update_refs(rev, sha1);
>>
>> -     switch (reset_type) {
>> -     case HARD:
>> -             if (!update_ref_status && !quiet)
>> -                     print_new_head_line(commit);
>> -             break;
>> -     case SOFT: /* Nothing else to do. */
>> -             break;
>> -     case MIXED: /* Report what has not been updated. */
>> +     if (reset_type == HARD && !update_ref_status && !quiet)
>> +             print_new_head_line(commit);
>> +     else if (reset_type == MIXED) /* Report what has not been updated. */
>>               update_index_refresh(0, NULL,
>>                               quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
>> -             break;
>> -     }
>
> Justification?

Clairvoyance -- the HARD case will soon be the only non-empty case. It's also missing KEEP and MERGE (but the empty SOFT block is there).

I'll update the message. I will also move the patch a little later in the series, closer to where it will be useful.

Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 10/19] reset --keep: only write index file once

"git reset --keep" calls reset_index_file() twice, first doing a two-way merge to the target revision, updating the index and worktree, and then resetting the index. After each call, we write the index file.

In the unlikely event that the second call to reset_index_file() fails, the index will have been merged to the target revision, but HEAD will not be updated, leaving the user with a dirty index.

By moving the locking, writing and committing out of reset_index_file() and into the caller, we can avoid writing the index twice, thereby making the sure we don't end up in the half-way reset state. As a bonus, we speed up "git reset --keep" a little on the linux-2.6 repo (best of five, warm cache):

        Before      After
real    0m0.315s    0m0.296s
user    0m0.290s    0m0.280s
sys     0m0.020s    0m0.010s
---
 builtin/reset.c | 21 ++++++++++-----------
 1 file changed, 10 insertions(+), 11 deletions(-)
Show changes to builtin/reset.c +10 −11
diff --git a/builtin/reset.c b/builtin/reset.c
index 05ccfd4..8e5d097 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -38,14 +38,12 @@ static inline int is_merge(void)
 	return !access(git_path("MERGE_HEAD"), F_OK);
 }
 
-static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
+static int reset_index(const unsigned char *sha1, int reset_type, int quiet)
 {
 	int nr = 1;
-	int newfd;
 	struct tree_desc desc[2];
 	struct tree *tree;
 	struct unpack_trees_options opts;
-	struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 
 	memset(&opts, 0, sizeof(opts));
 	opts.head_idx = 1;
@@ -67,8 +65,6 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
 		opts.reset = 1;
 	}
 
-	newfd = hold_locked_index(lock, 1);
-
 	read_cache_unmerged();
 
 	if (reset_type == KEEP) {
@@ -91,10 +87,6 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
 		prime_cache_tree(&active_cache_tree, tree);
 	}
 
-	if (write_cache(newfd, active_cache, active_nr) ||
-	    commit_locked_index(lock))
-		return error(_("Could not write new index file."));
-
 	return 0;
 }
 
@@ -340,9 +332,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		die_if_unmerged_cache(reset_type);
 
 	if (reset_type != SOFT) {
-		int err = reset_index_file(sha1, reset_type, quiet);
+		struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+		int newfd = hold_locked_index(lock, 1);
+		int err = reset_index(sha1, reset_type, quiet);
 		if (reset_type == KEEP && !err)
-			err = reset_index_file(sha1, MIXED, quiet);
+			err = reset_index(sha1, MIXED, quiet);
+		if (!err &&
+		    (write_cache(newfd, active_cache, active_nr) ||
+		     commit_locked_index(lock))) {
+			err = error(_("Could not write new index file."));
+		}
 		if (err)
 			die(_("Could not reset index file to revision '%s'."), rev);
 	}
-- 
1.8.1.rc3.331.g1ef2165
Junio C Hamano· Jan 9, 2013, 19:55 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH 10/19] reset --keep: only write index file once

Martin von Zweigbergk <martinvonz@gmail.com> writes:
Show 13 quoted lines
> "git reset --keep" calls reset_index_file() twice, first doing a
> two-way merge to the target revision, updating the index and worktree,
> and then resetting the index. After each call, we write the index
> file.
>
> In the unlikely event that the second call to reset_index_file()
> fails, the index will have been merged to the target revision, but
> HEAD will not be updated, leaving the user with a dirty index.
>
> By moving the locking, writing and committing out of
> reset_index_file() and into the caller, we can avoid writing the index
> twice, thereby making the sure we don't end up in the half-way reset
> state.
Nice.
Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 11/19] reset: avoid redundant error message

If writing or committing the new index file fails, we print "Could not
write new index file." followed by "Could not reset index file to
revision $rev.". The first message seems to imply the second, so print
only the first message.
---
 builtin/reset.c | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)
Show changes to builtin/reset.c +3 −5
diff --git a/builtin/reset.c b/builtin/reset.c
index 8e5d097..54e3c5b 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -337,13 +337,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		int err = reset_index(sha1, reset_type, quiet);
 		if (reset_type == KEEP && !err)
 			err = reset_index(sha1, MIXED, quiet);
-		if (!err &&
-		    (write_cache(newfd, active_cache, active_nr) ||
-		     commit_locked_index(lock))) {
-			err = error(_("Could not write new index file."));
-		}
 		if (err)
 			die(_("Could not reset index file to revision '%s'."), rev);
+		if (write_cache(newfd, active_cache, active_nr) ||
+		    commit_locked_index(lock))
+			die(_("Could not write new index file."));
 	}
 
 	/* Any resets update HEAD to the head being switched to,
-- 
1.8.1.rc3.331.g1ef2165
Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 12/19] reset.c: move update_index_refresh() call out of read_from_tree()

The final part of cmd_reset() essentially looks like:
  if (pathspec) {
    ...
    read_from_tree(...);
  } else {
    ...
    reset_index(...);
    update_index_refresh(...);
    ...
  }
where read_from_tree() internally also calls
update_index_refresh(). Move the call to update_index_refresh() out of
read_from_tree for symmetry with the 'else' block, making
read_from_tree() and reset_index() closer in functionality.
---
 builtin/reset.c | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)
Show changes to builtin/reset.c +9 −9
diff --git a/builtin/reset.c b/builtin/reset.c
index 54e3c5b..a21ba31 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -145,11 +145,8 @@ static void update_index_from_diff(struct diff_queue_struct *q,
 	}
 }
 
-static int read_from_tree(const char **pathspec, unsigned char *tree_sha1,
-			  int refresh_flags)
+static int read_from_tree(const char **pathspec, unsigned char *tree_sha1)
 {
-	struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-	int index_fd;
 	struct diff_options opt;
 
 	memset(&opt, 0, sizeof(opt));
@@ -157,7 +154,6 @@ static int read_from_tree(const char **pathspec, unsigned char *tree_sha1,
 	opt.output_format = DIFF_FORMAT_CALLBACK;
 	opt.format_callback = update_index_from_diff;
 
-	index_fd = hold_locked_index(lock, 1);
 	read_cache();
 	if (do_diff_cache(tree_sha1, &opt))
 		return 1;
@@ -165,7 +161,7 @@ static int read_from_tree(const char **pathspec, unsigned char *tree_sha1,
 	diff_flush(&opt);
 	diff_tree_release_paths(&opt);
 
-	return update_index_refresh(index_fd, lock, refresh_flags);
+	return 0;
 }
 
 static void set_reflog_message(struct strbuf *sb, const char *action,
@@ -321,9 +317,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		die(_("%s reset is not allowed in a bare repository"),
 		    _(reset_type_names[reset_type]));
 
-	if (pathspec)
-		return read_from_tree(pathspec, sha1,
-				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+	if (pathspec) {
+		struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+		int index_fd = hold_locked_index(lock, 1);
+		return read_from_tree(pathspec, sha1) ||
+			update_index_refresh(index_fd, lock,
+					quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+	}
 
 	/* Soft reset does not touch the index file nor the working tree
 	 * at all, but requires them in a good order.  Other resets reset
-- 
1.8.1.rc3.331.g1ef2165
Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 13/19] reset.c: move lock, write and commit out of update_index_refresh()

In preparation for the/a following patch, move the locking, writing and committing of the index file out of update_index_refresh(). The code duplication caused will soon be taken care of. What remains of update_index_refresh() is just one line, but it is still called from two places, so let's leave it for now.

In the process, we expose and fix the minor UI bug that makes us print
"Could not refresh index" when we fail to write the index file when
invoked with a pathspec. Copy the error message from the pathspec-less
codepath ("Could not write new index file.").
---
 builtin/reset.c | 34 ++++++++++++++++++----------------
 1 file changed, 18 insertions(+), 16 deletions(-)
Show changes to builtin/reset.c +18 −16
diff --git a/builtin/reset.c b/builtin/reset.c
index a21ba31..2243b95 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -109,19 +109,10 @@ static void print_new_head_line(struct commit *commit)
 		printf("\n");
 }
 
-static int update_index_refresh(int fd, struct lock_file *index_lock, int flags)
+static void update_index_refresh(int flags)
 {
-	if (!index_lock) {
-		index_lock = xcalloc(1, sizeof(struct lock_file));
-		fd = hold_locked_index(index_lock, 1);
-	}
-
 	refresh_index(&the_index, (flags), NULL, NULL,
 		      _("Unstaged changes after reset:"));
-	if (write_cache(fd, active_cache, active_nr) ||
-			commit_locked_index(index_lock))
-		return error ("Could not refresh index");
-	return 0;
 }
 
 static void update_index_from_diff(struct diff_queue_struct *q,
@@ -320,9 +311,14 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	if (pathspec) {
 		struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 		int index_fd = hold_locked_index(lock, 1);
-		return read_from_tree(pathspec, sha1) ||
-			update_index_refresh(index_fd, lock,
-					quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+		if (read_from_tree(pathspec, sha1))
+			return 1;
+		update_index_refresh(
+			quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+		if (write_cache(index_fd, active_cache, active_nr) ||
+		    commit_locked_index(lock))
+			return error("Could not write new index file.");
+		return 0;
 	}
 
 	/* Soft reset does not touch the index file nor the working tree
@@ -350,9 +346,15 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 
 	if (reset_type == HARD && !update_ref_status && !quiet)
 		print_new_head_line(commit);
-	else if (reset_type == MIXED) /* Report what has not been updated. */
-		update_index_refresh(0, NULL,
-				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+	else if (reset_type == MIXED) { /* Report what has not been updated. */
+		struct lock_file *index_lock = xcalloc(1, sizeof(struct lock_file));
+		int fd = hold_locked_index(index_lock, 1);
+		update_index_refresh(
+			quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+		if (write_cache(fd, active_cache, active_nr) ||
+		    commit_locked_index(index_lock))
+			error("Could not refresh index");
+	}
 
 	remove_branch_state();
 
-- 
1.8.1.rc3.331.g1ef2165
Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 14/19] reset [--mixed]: don't write index file twice

When doing a mixed reset without paths, the index is locked, read, reset, and written back as part of the actual reset operation (in reset_index()). Then, when showing the list of worktree modifications, we lock the index again, refresh it, and write it.

Change this so we only write the index once, making "git reset" a little faster. It does mean that the index lock will be held a little longer, but the difference is small compared to the time spent refreshing the index.

There is one minor functional difference: We used to say "Could not write new index file." if the first write failed, and "Could not refresh index" if the second write failed. Now, we will only use the first message.

This speeds up "git reset" a little on the linux-2.6 repo (best of five, warm cache):

        Before      After
real    0m0.239s    0m0.214s
user    0m0.160s    0m0.130s
sys     0m0.070s    0m0.080s
---
 builtin/reset.c | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)
Show changes to builtin/reset.c +5 −9
diff --git a/builtin/reset.c b/builtin/reset.c
index 2243b95..254afa9 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -335,6 +335,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 			err = reset_index(sha1, MIXED, quiet);
 		if (err)
 			die(_("Could not reset index file to revision '%s'."), rev);
+
+		if (reset_type == MIXED) /* Report what has not been updated. */
+			update_index_refresh(
+				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+
 		if (write_cache(newfd, active_cache, active_nr) ||
 		    commit_locked_index(lock))
 			die(_("Could not write new index file."));
@@ -346,15 +351,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 
 	if (reset_type == HARD && !update_ref_status && !quiet)
 		print_new_head_line(commit);
-	else if (reset_type == MIXED) { /* Report what has not been updated. */
-		struct lock_file *index_lock = xcalloc(1, sizeof(struct lock_file));
-		int fd = hold_locked_index(index_lock, 1);
-		update_index_refresh(
-			quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
-		if (write_cache(fd, active_cache, active_nr) ||
-		    commit_locked_index(index_lock))
-			error("Could not refresh index");
-	}
 
 	remove_branch_state();
 
-- 
1.8.1.rc3.331.g1ef2165
Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 15/19] reset.c: finish entire cmd_reset() whether or not pathspec is given

By not returning from inside the "if (pathspec)" block, we can let the pathspec-aware and pathspec-less code share a bit more, making it easier to make future changes that should affect both cases. This also highlights the similarity between read_from_tree() and reset_index(). --- Should error reporting be aligned too? Speaking of which, do_diff_cache() never returns anything by 0. Is the return value for future-proofing?

 builtin/reset.c | 42 ++++++++++++++++++------------------------
 1 file changed, 18 insertions(+), 24 deletions(-)
Show changes to builtin/reset.c +18 −24
diff --git a/builtin/reset.c b/builtin/reset.c
index 254afa9..9bcad29 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -308,19 +308,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		die(_("%s reset is not allowed in a bare repository"),
 		    _(reset_type_names[reset_type]));
 
-	if (pathspec) {
-		struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-		int index_fd = hold_locked_index(lock, 1);
-		if (read_from_tree(pathspec, sha1))
-			return 1;
-		update_index_refresh(
-			quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
-		if (write_cache(index_fd, active_cache, active_nr) ||
-		    commit_locked_index(lock))
-			return error("Could not write new index file.");
-		return 0;
-	}
-
 	/* Soft reset does not touch the index file nor the working tree
 	 * at all, but requires them in a good order.  Other resets reset
 	 * the index file to the tree object we are switching to. */
@@ -330,11 +317,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	if (reset_type != SOFT) {
 		struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 		int newfd = hold_locked_index(lock, 1);
-		int err = reset_index(sha1, reset_type, quiet);
-		if (reset_type == KEEP && !err)
-			err = reset_index(sha1, MIXED, quiet);
-		if (err)
-			die(_("Could not reset index file to revision '%s'."), rev);
+		if (pathspec) {
+			if (read_from_tree(pathspec, sha1))
+				return 1;
+		} else {
+			int err = reset_index(sha1, reset_type, quiet);
+			if (reset_type == KEEP && !err)
+				err = reset_index(sha1, MIXED, quiet);
+			if (err)
+				die(_("Could not reset index file to revision '%s'."), rev);
+		}
 
 		if (reset_type == MIXED) /* Report what has not been updated. */
 			update_index_refresh(
@@ -345,14 +337,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 			die(_("Could not write new index file."));
 	}
 
-	/* Any resets update HEAD to the head being switched to,
-	 * saving the previous head in ORIG_HEAD before. */
-	update_ref_status = update_refs(rev, sha1);
+	if (!pathspec) {
+		/* Any resets without paths update HEAD to the head being
+		 * switched to, saving the previous head in ORIG_HEAD before. */
+		update_ref_status = update_refs(rev, sha1);
 
-	if (reset_type == HARD && !update_ref_status && !quiet)
-		print_new_head_line(commit);
+		if (reset_type == HARD && !update_ref_status && !quiet)
+			print_new_head_line(commit);
 
-	remove_branch_state();
+		remove_branch_state();
+	}
 
 	return update_ref_status;
 }
-- 
1.8.1.rc3.331.g1ef2165
Junio C Hamano· Jan 9, 2013, 19:59 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH 15/19] reset.c: finish entire cmd_reset() whether or not pathspec is given

Martin von Zweigbergk <martinvonz@gmail.com> writes:
Show 8 quoted lines
> By not returning from inside the "if (pathspec)" block, we can let the
> pathspec-aware and pathspec-less code share a bit more, making it
> easier to make future changes that should affect both cases. This also
> highlights the similarity between read_from_tree() and reset_index().
> ---
> Should error reporting be aligned too? Speaking of which,
> do_diff_cache() never returns anything by 0. Is the return value for
> future-proofing?
Perhaps, and yes.
Show 73 quoted lines
>
>  builtin/reset.c | 42 ++++++++++++++++++------------------------
>  1 file changed, 18 insertions(+), 24 deletions(-)
>
> diff --git a/builtin/reset.c b/builtin/reset.c
> index 254afa9..9bcad29 100644
> --- a/builtin/reset.c
> +++ b/builtin/reset.c
> @@ -308,19 +308,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
>  		die(_("%s reset is not allowed in a bare repository"),
>  		    _(reset_type_names[reset_type]));
>  
> -	if (pathspec) {
> -		struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
> -		int index_fd = hold_locked_index(lock, 1);
> -		if (read_from_tree(pathspec, sha1))
> -			return 1;
> -		update_index_refresh(
> -			quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
> -		if (write_cache(index_fd, active_cache, active_nr) ||
> -		    commit_locked_index(lock))
> -			return error("Could not write new index file.");
> -		return 0;
> -	}
> -
>  	/* Soft reset does not touch the index file nor the working tree
>  	 * at all, but requires them in a good order.  Other resets reset
>  	 * the index file to the tree object we are switching to. */
> @@ -330,11 +317,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
>  	if (reset_type != SOFT) {
>  		struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
>  		int newfd = hold_locked_index(lock, 1);
> -		int err = reset_index(sha1, reset_type, quiet);
> -		if (reset_type == KEEP && !err)
> -			err = reset_index(sha1, MIXED, quiet);
> -		if (err)
> -			die(_("Could not reset index file to revision '%s'."), rev);
> +		if (pathspec) {
> +			if (read_from_tree(pathspec, sha1))
> +				return 1;
> +		} else {
> +			int err = reset_index(sha1, reset_type, quiet);
> +			if (reset_type == KEEP && !err)
> +				err = reset_index(sha1, MIXED, quiet);
> +			if (err)
> +				die(_("Could not reset index file to revision '%s'."), rev);
> +		}
>  
>  		if (reset_type == MIXED) /* Report what has not been updated. */
>  			update_index_refresh(
> @@ -345,14 +337,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
>  			die(_("Could not write new index file."));
>  	}
>  
> -	/* Any resets update HEAD to the head being switched to,
> -	 * saving the previous head in ORIG_HEAD before. */
> -	update_ref_status = update_refs(rev, sha1);
> +	if (!pathspec) {
> +		/* Any resets without paths update HEAD to the head being
> +		 * switched to, saving the previous head in ORIG_HEAD before. */
> +		update_ref_status = update_refs(rev, sha1);
>  
> -	if (reset_type == HARD && !update_ref_status && !quiet)
> -		print_new_head_line(commit);
> +		if (reset_type == HARD && !update_ref_status && !quiet)
> +			print_new_head_line(commit);
>  
> -	remove_branch_state();
> +		remove_branch_state();
> +	}
>  
>  	return update_ref_status;
>  }
Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 16/19] reset [--mixed] --quiet: don't refresh index

"git reset [--mixed]" without --quiet refreshes the index in order to display the "Unstaged changes after reset". When --quiet is given, that output is suppressed, removing the need to refresh the index. Other porcelain commands that care about a refreshed index should already be refreshing it, so running e.g. "git reset -q && git diff" is still safe.

This commit together with 686b2de (oneway_merge(): only lstat() when told to update worktree, 2012-12-20) removes all calls to lstat() the worktree from the command.

This speeds up "git reset -q" a little on the linux-2.6 repo (best of five, warm cache):

        Before      After
real    0m0.215s    0m0.176s
user    0m0.150s    0m0.130s
sys     0m0.060s    0m0.040s
And with cold cache (best of five):
        Before      After
real    0m11.351s   0m8.420s
user    0m0.230s    0m0.220s
sys     0m0.270s    0m0.060s
---
There is a test case in t7102 called '--mixed refreshes the index',
but it only checks that right output it printed. Is the test case not
testing right or not named right? As you can see, I suspect it's the
name/description that should change.
 builtin/reset.c | 12 +++---------
 1 file changed, 3 insertions(+), 9 deletions(-)
Show changes to builtin/reset.c +3 −9
diff --git a/builtin/reset.c b/builtin/reset.c
index 9bcad29..a2e69eb 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -109,12 +109,6 @@ static void print_new_head_line(struct commit *commit)
 		printf("\n");
 }
 
-static void update_index_refresh(int flags)
-{
-	refresh_index(&the_index, (flags), NULL, NULL,
-		      _("Unstaged changes after reset:"));
-}
-
 static void update_index_from_diff(struct diff_queue_struct *q,
 		struct diff_options *opt, void *data)
 {
@@ -328,9 +322,9 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 				die(_("Could not reset index file to revision '%s'."), rev);
 		}
 
-		if (reset_type == MIXED) /* Report what has not been updated. */
-			update_index_refresh(
-				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+		if (reset_type == MIXED && !quiet) /* Report what has not been updated. */
+			refresh_index(&the_index, REFRESH_IN_PORCELAIN, NULL, NULL,
+				      _("Unstaged changes after reset:"));
 
 		if (write_cache(newfd, active_cache, active_nr) ||
 		    commit_locked_index(lock))
-- 
1.8.1.rc3.331.g1ef2165
Jeff King· Jan 9, 2013, 17:01 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH 16/19] reset [--mixed] --quiet: don't refresh index

On Wed, Jan 09, 2013 at 12:16:13AM -0800, Martin von Zweigbergk wrote:
Show 6 quoted lines
> "git reset [--mixed]" without --quiet refreshes the index in order to
> display the "Unstaged changes after reset". When --quiet is given,
> that output is suppressed, removing the need to refresh the index.
> Other porcelain commands that care about a refreshed index should
> already be refreshing it, so running e.g. "git reset -q && git diff"
> is still safe.
Hmm. But "git reset -q && git diff-files" would not be?

We have never been very clear about which commands refresh the index. Since "reset" is about manipulating the index, I'd expect it to be refreshed afterwards. On the other hand, since we have never guaranteed anything, perhaps a careful script should always use "git update-index --refresh". I would not be too surprised if some of our own scripts are not that careful, though.

-Peff
Martin von Zweigbergk· Jan 9, 2013, 18:43 UTC · re: Jeff King · lore

Re: [PATCH 16/19] reset [--mixed] --quiet: don't refresh index

On Wed, Jan 9, 2013 at 9:01 AM, Jeff King <peff@peff.net> wrote:
Show 10 quoted lines
> On Wed, Jan 09, 2013 at 12:16:13AM -0800, Martin von Zweigbergk wrote:
>
>> "git reset [--mixed]" without --quiet refreshes the index in order to
>> display the "Unstaged changes after reset". When --quiet is given,
>> that output is suppressed, removing the need to refresh the index.
>> Other porcelain commands that care about a refreshed index should
>> already be refreshing it, so running e.g. "git reset -q && git diff"
>> is still safe.
>
> Hmm. But "git reset -q && git diff-files" would not be?

Right. Actually, "git reset -q && git diff" was perhaps not a good example, because its analogous plumbing command would be "git reset -q && git diff-files -p", which is also safe. But, as you say, "git reset -q && git diff-files" (without -p) might list files for which only the stat information has changed.

> We have never been very clear about which commands refresh the index.
Yes, git-reset's documentation doesn't mention it.
> Since "reset" is about manipulating the index, I'd expect it to be
> refreshed afterwards. On the other hand, since we have never guaranteed
> anything, perhaps a careful script should always use "git update-index
> --refresh".

Since "git diff-files" is a plumbing command, users of it to a hopefully a bit more careful than regular users, but you never know.

> I would not be too surprised if some of our own scripts are
> not that careful, though.
I didn't find any, but I might have missed something.

Regardless, this patch was tangential. The goal of this series can be achieved independently of this patch, so if it's too risky, we can drop easily drop it.

Also, even though it does make "git reset -q" faster, I'm not sure how important that is in practice. Most use cases would probably refresh the index afterwards anyway. In such cases, the improvement on warm cache would still be there, but the relative improvement in the cold cache case would be pretty much gone (since the entire tree would be stat'ed by the following refresh anyway).

Martin
Junio C Hamano· Jan 9, 2013, 19:12 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH 16/19] reset [--mixed] --quiet: don't refresh index

Martin von Zweigbergk <martinvonz@gmail.com> writes:
Show 16 quoted lines
>> We have never been very clear about which commands refresh the index.
>
> Yes, git-reset's documentation doesn't mention it.
>
>> Since "reset" is about manipulating the index, I'd expect it to be
>> refreshed afterwards. On the other hand, since we have never guaranteed
>> anything, perhaps a careful script should always use "git update-index
>> --refresh".
>
> Since "git diff-files" is a plumbing command, users of it to a
> hopefully a bit more careful than regular users, but you never know.
>
>> I would not be too surprised if some of our own scripts are
>> not that careful, though.
>
> I didn't find any, but I might have missed something.

contrib/examples/ have some, but looking at it makes me realize that we have been fairly careful to avoid using "git reset" which is a Porcelain.

And as a Porcelain, I would rather expect it to leave the resulting index refreshed.

Martin von Zweigbergk· Jan 9, 2013, 19:38 UTC · re: Junio C Hamano · lore

Re: [PATCH 16/19] reset [--mixed] --quiet: don't refresh index

On Wed, Jan 9, 2013 at 11:12 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Martin von Zweigbergk <martinvonz@gmail.com> writes:
>
> And as a Porcelain, I would rather expect it to leave the resulting
> index refreshed.

Yeah, I guess you're right. Regular users (those using only porcelain) shouldn't notice, but it does make sense to think that the index would be refreshed after running a porcelain. And the risk of breaking people's scripts seems real too. I'll drop patch this from the re-roll (which I'll also make sure I'll sign off)

(FYI, the reason I wrote this patch was because I was surprised that "git reset" did anything with the worktree at all.)

Junio C Hamano· Jan 9, 2013, 20:05 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH 16/19] reset [--mixed] --quiet: don't refresh index

Martin von Zweigbergk <martinvonz@gmail.com> writes:
> There is a test case in t7102 called '--mixed refreshes the index',
> but it only checks that right output it printed.

I think that comes from 620a6cd (builtin-reset: avoid forking "update-index --refresh", 2007-11-03). Before that commit, we refreshed the index with --mixed, and the test tries to make sure we continue to do so after the change. Even though it is not testing if the index has stat only changes (which is rather cumbersome to write---you need to futz with timestamp or something) and using the output from refresh machinery as a substitute, I think the intent of that commit is fairly clear.

Show 33 quoted lines
>  builtin/reset.c | 12 +++---------
>  1 file changed, 3 insertions(+), 9 deletions(-)
>
> diff --git a/builtin/reset.c b/builtin/reset.c
> index 9bcad29..a2e69eb 100644
> --- a/builtin/reset.c
> +++ b/builtin/reset.c
> @@ -109,12 +109,6 @@ static void print_new_head_line(struct commit *commit)
>  		printf("\n");
>  }
>  
> -static void update_index_refresh(int flags)
> -{
> -	refresh_index(&the_index, (flags), NULL, NULL,
> -		      _("Unstaged changes after reset:"));
> -}
> -
>  static void update_index_from_diff(struct diff_queue_struct *q,
>  		struct diff_options *opt, void *data)
>  {
> @@ -328,9 +322,9 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
>  				die(_("Could not reset index file to revision '%s'."), rev);
>  		}
>  
> -		if (reset_type == MIXED) /* Report what has not been updated. */
> -			update_index_refresh(
> -				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
> +		if (reset_type == MIXED && !quiet) /* Report what has not been updated. */
> +			refresh_index(&the_index, REFRESH_IN_PORCELAIN, NULL, NULL,
> +				      _("Unstaged changes after reset:"));
>  
>  		if (write_cache(newfd, active_cache, active_nr) ||
>  		    commit_locked_index(lock))
Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 17/19] reset $sha1 $pathspec: require $sha1 only to be treeish

Resetting with paths does not update HEAD and there is nothing else that a commit should be needed for. Relax the argument parsing so only a tree is required.

The sha1 is only passed to read_from_tree(), which already only requires a tree.

The "rev" variable we pass to run_add_interactive() will resolve to a tree. This is fine since interactive_reset only needs the parameter to be a treeish and doesn't use it for display purposes. --- Is it correct that interactive_reset does not use the revision specifier for display purposes? Or, worse, that it requires it to be a commit in some cases? I tried it and didn't see any problem.

Can the two blocks of code that look up commit or tree be made to share more? I'm not very familiar with what functions are available. I think I tried keeping a separate "struct object *object" to be able to put the last three lines outside the blocks, but didn't like the result.

 builtin/reset.c  | 46 ++++++++++++++++++++++++++--------------------
 t/t7102-reset.sh |  8 ++++++++
 2 files changed, 34 insertions(+), 20 deletions(-)
Show changes to 2 files +34 −20

builtin/reset.c, t/t7102-reset.sh

diff --git a/builtin/reset.c b/builtin/reset.c
index a2e69eb..4c223bd 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -177,9 +177,10 @@ const char **parse_args(int argc, const char **argv, const char *prefix, const c
 	/*
 	 * Possible arguments are:
 	 *
-	 * git reset [-opts] <rev> <paths>...
-	 * git reset [-opts] <rev> -- <paths>...
-	 * git reset [-opts] -- <paths>...
+	 * git reset [-opts] [<rev>]
+	 * git reset [-opts] <tree> [<paths>...]
+	 * git reset [-opts] <tree> -- [<paths>...]
+	 * git reset [-opts] -- [<paths>...]
 	 * git reset [-opts] <paths>...
 	 *
 	 * At this point, argv points immediately after [-opts].
@@ -194,11 +195,13 @@ const char **parse_args(int argc, const char **argv, const char *prefix, const c
 		}
 		/*
 		 * Otherwise, argv[0] could be either <rev> or <paths> and
-		 * has to be unambiguous.
+		 * has to be unambiguous. If there is a single argument, it
+		 * can not be a tree
 		 */
-		else if (!get_sha1_committish(argv[0], unused)) {
+		else if ((argc == 1 && !get_sha1_committish(argv[0], unused)) ||
+			 (argc > 1 && !get_sha1_treeish(argv[0], unused))) {
 			/*
-			 * Ok, argv[0] looks like a rev; it should not
+			 * Ok, argv[0] looks like a commit/tree; it should not
 			 * be a filename.
 			 */
 			verify_non_filename(prefix, argv[0]);
@@ -240,7 +243,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	const char *rev;
 	unsigned char sha1[20];
 	const char **pathspec = NULL;
-	struct commit *commit;
+	struct commit *commit = NULL;
 	const struct option options[] = {
 		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
 		OPT_SET_INT(0, "mixed", &reset_type,
@@ -262,19 +265,22 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 						PARSE_OPT_KEEP_DASHDASH);
 	pathspec = parse_args(argc, argv, prefix, &rev);
 
-	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);
+	if (!pathspec) {
+		if (get_sha1_committish(rev, sha1))
+			die(_("Failed to resolve '%s' as a valid revision."), rev);
+		commit = lookup_commit_reference(sha1);
+		if (!commit)
+			die(_("Could not parse object '%s'."), rev);
+		hashcpy(sha1, commit->object.sha1);
+	} else {
+		struct tree *tree;
+		if (get_sha1_treeish(rev, sha1))
+			die(_("Failed to resolve '%s' as a valid tree."), rev);
+		tree = parse_tree_indirect(sha1);
+		if (!tree)
+			die(_("Could not parse object '%s'."), rev);
+		hashcpy(sha1, tree->object.sha1);
+	}
 
 	if (patch_mode) {
 		if (reset_type != NONE)
diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh
index 81b2570..1fa2a5f 100755
--- a/t/t7102-reset.sh
+++ b/t/t7102-reset.sh
@@ -497,4 +497,12 @@ test_expect_success 'disambiguation (4)' '
 	test ! -f secondfile
 '
 
+test_expect_success 'reset with paths accepts 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_done
-- 
1.8.1.rc3.331.g1ef2165
Junio C Hamano· Jan 9, 2013, 20:23 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH 17/19] reset $sha1 $pathspec: require $sha1 only to be treeish

Martin von Zweigbergk <martinvonz@gmail.com> writes:
Show 14 quoted lines
> Resetting with paths does not update HEAD and there is nothing else
> that a commit should be needed for. Relax the argument parsing so only
> a tree is required.
>
> The sha1 is only passed to read_from_tree(), which already only
> requires a tree.
>
> The "rev" variable we pass to run_add_interactive() will resolve to a
> tree. This is fine since interactive_reset only needs the parameter to
> be a treeish and doesn't use it for display purposes.
> ---
> Is it correct that interactive_reset does not use the revision
> specifier for display purposes? Or, worse, that it requires it to be a
> commit in some cases? I tried it and didn't see any problem.

As far as I know, it is only given to git-diff-index as the tree-ish, and resulting patch text is used for application via git-apply just like any patch coming from any origin, so I think it should be fine.

Show 5 quoted lines
> Can the two blocks of code that look up commit or tree be made to
> share more? I'm not very familiar with what functions are available. I
> think I tried keeping a separate "struct object *object" to be able to
> put the last three lines outside the blocks, but didn't like the
> result.

I think the patch looks fine from the sharing perspective, but it may be even nicer to have a separate variable to hold a commit object limited to the scope of if (!pathspec) block to make them more symmetric. The commit is only needed later to show "we are now at this commit", but that code can find the commit itself given the object name in sha1[].

Show 101 quoted lines
>  builtin/reset.c  | 46 ++++++++++++++++++++++++++--------------------
>  t/t7102-reset.sh |  8 ++++++++
>  2 files changed, 34 insertions(+), 20 deletions(-)
>
> diff --git a/builtin/reset.c b/builtin/reset.c
> index a2e69eb..4c223bd 100644
> --- a/builtin/reset.c
> +++ b/builtin/reset.c
> @@ -177,9 +177,10 @@ const char **parse_args(int argc, const char **argv, const char *prefix, const c
>  	/*
>  	 * Possible arguments are:
>  	 *
> -	 * git reset [-opts] <rev> <paths>...
> -	 * git reset [-opts] <rev> -- <paths>...
> -	 * git reset [-opts] -- <paths>...
> +	 * git reset [-opts] [<rev>]
> +	 * git reset [-opts] <tree> [<paths>...]
> +	 * git reset [-opts] <tree> -- [<paths>...]
> +	 * git reset [-opts] -- [<paths>...]
>  	 * git reset [-opts] <paths>...
>  	 *
>  	 * At this point, argv points immediately after [-opts].
> @@ -194,11 +195,13 @@ const char **parse_args(int argc, const char **argv, const char *prefix, const c
>  		}
>  		/*
>  		 * Otherwise, argv[0] could be either <rev> or <paths> and
> -		 * has to be unambiguous.
> +		 * has to be unambiguous. If there is a single argument, it
> +		 * can not be a tree
>  		 */
> -		else if (!get_sha1_committish(argv[0], unused)) {
> +		else if ((argc == 1 && !get_sha1_committish(argv[0], unused)) ||
> +			 (argc > 1 && !get_sha1_treeish(argv[0], unused))) {
>  			/*
> -			 * Ok, argv[0] looks like a rev; it should not
> +			 * Ok, argv[0] looks like a commit/tree; it should not
>  			 * be a filename.
>  			 */
>  			verify_non_filename(prefix, argv[0]);
> @@ -240,7 +243,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
>  	const char *rev;
>  	unsigned char sha1[20];
>  	const char **pathspec = NULL;
> -	struct commit *commit;
> +	struct commit *commit = NULL;
>  	const struct option options[] = {
>  		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
>  		OPT_SET_INT(0, "mixed", &reset_type,
> @@ -262,19 +265,22 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
>  						PARSE_OPT_KEEP_DASHDASH);
>  	pathspec = parse_args(argc, argv, prefix, &rev);
>  
> -	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);
> +	if (!pathspec) {
> +		if (get_sha1_committish(rev, sha1))
> +			die(_("Failed to resolve '%s' as a valid revision."), rev);
> +		commit = lookup_commit_reference(sha1);
> +		if (!commit)
> +			die(_("Could not parse object '%s'."), rev);
> +		hashcpy(sha1, commit->object.sha1);
> +	} else {
> +		struct tree *tree;
> +		if (get_sha1_treeish(rev, sha1))
> +			die(_("Failed to resolve '%s' as a valid tree."), rev);
> +		tree = parse_tree_indirect(sha1);
> +		if (!tree)
> +			die(_("Could not parse object '%s'."), rev);
> +		hashcpy(sha1, tree->object.sha1);
> +	}
>  
>  	if (patch_mode) {
>  		if (reset_type != NONE)
> diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh
> index 81b2570..1fa2a5f 100755
> --- a/t/t7102-reset.sh
> +++ b/t/t7102-reset.sh
> @@ -497,4 +497,12 @@ test_expect_success 'disambiguation (4)' '
>  	test ! -f secondfile
>  '
>  
> +test_expect_success 'reset with paths accepts 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_done
Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 18/19] reset: allow reset on unborn branch

Some users seem to think, knowingly or not, that being on an unborn branch is like having a commit with an empty tree checked out, but when run on an unborn branch, "git reset" currently fails with:

  fatal: Failed to resolve 'HEAD' as a valid ref.
Instead of making users figure out that they should run
 git rm --cached -r .
, let's teach "git reset" without a revision argument, when on an
unborn branch, to behave as if the user asked to reset to an empty
tree. Don't take the analogy with an empty commit too far, though, but
still disallow explictly referring to HEAD in "git reset HEAD".
---
 builtin/reset.c                | 17 ++++++++------
 t/t7106-reset-unborn-branch.sh | 52 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 62 insertions(+), 7 deletions(-)
 create mode 100755 t/t7106-reset-unborn-branch.sh
Show changes to 2 files +62 −7

builtin/reset.c, t/t7106-reset-unborn-branch.sh

diff --git a/builtin/reset.c b/builtin/reset.c
index 4c223bd..5cd48ac 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -239,7 +239,7 @@ static int update_refs(const char *rev, const unsigned char *sha1) {
 int cmd_reset(int argc, const char **argv, const char *prefix)
 {
 	int reset_type = NONE, update_ref_status = 0, quiet = 0;
-	int patch_mode = 0;
+	int patch_mode = 0, unborn;
 	const char *rev;
 	unsigned char sha1[20];
 	const char **pathspec = NULL;
@@ -264,8 +264,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, git_reset_usage,
 						PARSE_OPT_KEEP_DASHDASH);
 	pathspec = parse_args(argc, argv, prefix, &rev);
-
-	if (!pathspec) {
+	unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", sha1);
+	if (unborn) {
+		/* reset on unborn branch: treat as reset to empty tree */
+		hashcpy(sha1, EMPTY_TREE_SHA1_BIN);
+	} else if (!pathspec) {
 		if (get_sha1_committish(rev, sha1))
 			die(_("Failed to resolve '%s' as a valid revision."), rev);
 		commit = lookup_commit_reference(sha1);
@@ -285,7 +288,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	if (patch_mode) {
 		if (reset_type != NONE)
 			die(_("--patch is incompatible with --{hard,mixed,soft}"));
-		return run_add_interactive(rev, "--patch=reset", pathspec);
+		return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", pathspec);
 	}
 
 	/* git reset tree [--] paths... can be used to
@@ -337,16 +340,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 			die(_("Could not write new index file."));
 	}
 
-	if (!pathspec) {
+	if (!pathspec && !unborn) {
 		/* Any resets without paths update HEAD to the head being
 		 * switched to, saving the previous head in ORIG_HEAD before. */
 		update_ref_status = update_refs(rev, sha1);
 
 		if (reset_type == HARD && !update_ref_status && !quiet)
 			print_new_head_line(commit);
-
-		remove_branch_state();
 	}
+	if (!pathspec)
+		remove_branch_state();
 
 	return update_ref_status;
 }
diff --git a/t/t7106-reset-unborn-branch.sh b/t/t7106-reset-unborn-branch.sh
new file mode 100755
index 0000000..4ff6af4
--- /dev/null
+++ b/t/t7106-reset-unborn-branch.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='git reset should work on unborn branch'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	echo a >a &&
+	echo b >b
+'
+
+test_expect_success 'reset' '
+	git add a b &&
+	git reset &&
+	test "$(git ls-files)" == ""
+'
+
+test_expect_success 'reset HEAD' '
+	rm .git/index &&
+	git add a b &&
+	test_must_fail git reset HEAD
+'
+
+test_expect_success 'reset $file' '
+	rm .git/index &&
+	git add a b &&
+	git reset a &&
+	test "$(git ls-files)" == "b"
+'
+
+test_expect_success 'reset -p' '
+	rm .git/index &&
+	git add a &&
+	echo y | git reset -p &&
+	test "$(git ls-files)" == ""
+'
+
+test_expect_success 'reset --soft is a no-op' '
+	rm .git/index &&
+	git add a &&
+	git reset --soft
+	test "$(git ls-files)" == "a"
+'
+
+test_expect_success 'reset --hard' '
+	rm .git/index &&
+	git add a &&
+	git reset --hard &&
+	test "$(git ls-files)" == "" &&
+	test_path_is_missing a
+'
+
+test_done
-- 
1.8.1.rc3.331.g1ef2165
Martin von Zweigbergk· Jan 9, 2013, 08:16 UTC · re: Martin von Zweigbergk · lore

[PATCH 19/19] reset [--mixed]: use diff-based reset whether or not pathspec was given

Thanks to b65982b (Optimize "diff-index --cached" using cache-tree, 2009-05-20), resetting with paths is much faster than resetting without paths. Some timings for the linux-2.6 repo to illustrate this (best of five, warm cache):

        reset       reset .
real    0m0.219s    0m0.080s
user    0m0.140s    0m0.040s
sys     0m0.070s    0m0.030s

These two commands should do the same thing, so instead of having the user type the trailing " ." to get the faster do_diff_cache()-based implementation, always use it when doing a mixed reset, with or without paths (so "git reset $rev" would also be faster).

Comparing before and after (best of five):
                       Before     After
reset    (warm cache):   0.21      0.07
reset -q (warm cache)    0.17      0.03
reset    (cold cache):  10.31      2.72
reset -q (cold cache)    7.64      0.38
---
Are unmerged entries handled the same? read_from_tree() calls
read_cache(), while reset_index() calls read_cache_unmerged(). I
haven't figured out if/why they should be different.

Are there other differences, or could unpack_trees() learn the same optimization as do_diff_cache()? Actually, the commit mentioned above does say

  Tweak unpack_trees() logic that is used to read in the tree object
  to catch the case where the tree entry we are looking at matches the
  index as a whole by looking at the cache-tree.

If there are differences, we are clearly missing tests for them. And it seems like any difference between them should be fixed, so "git reset" and "git reset ." (from root of tree) do the same thing even before this patch.

 builtin/reset.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/reset.c +1 −1
diff --git a/builtin/reset.c b/builtin/reset.c
index 5cd48ac..6db0a10 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -320,7 +320,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	if (reset_type != SOFT) {
 		struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 		int newfd = hold_locked_index(lock, 1);
-		if (pathspec) {
+		if (reset_type == MIXED) {
 			if (read_from_tree(pathspec, sha1))
 				return 1;
 		} else {
-- 
1.8.1.rc3.331.g1ef2165
Junio C Hamano· Jan 9, 2013, 20:27 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH 19/19] reset [--mixed]: use diff-based reset whether or not pathspec was given

Martin von Zweigbergk <martinvonz@gmail.com> writes:
Show 9 quoted lines
> Thanks to b65982b (Optimize "diff-index --cached" using cache-tree,
> 2009-05-20), resetting with paths is much faster than resetting
> without paths. Some timings for the linux-2.6 repo to illustrate this
> (best of five, warm cache):
>
>         reset       reset .
> real    0m0.219s    0m0.080s
> user    0m0.140s    0m0.040s
> sys     0m0.070s    0m0.030s
Nice.
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 00/19] reset improvements

Changes since v1:
 - Spelling fixes.
 - Explained how "git reset -- $pathspec" in bare repo is broken.
 - Provided motivation for replacement of switch by if-else
 - Fixed argv/argc handling by removing use of argc.
 - Replaced "don't refresh index on --quiet" patch by one that just
   inlines update_index_refresh()
 - Incorporated fixes from Junio's repo
 - Provided some motivation for "replace switch by if-else" amd moved
   the patch later in the series.
Thanks for reviewing!
Martin von Zweigbergk (19):
  reset $pathspec: no need to discard index
  reset $pathspec: exit with code 0 if successful
  reset.c: pass pathspec around instead of (prefix, argv) pair
  reset: don't allow "git reset -- $pathspec" in bare repo
  reset.c: extract function for parsing arguments
  reset.c: remove unnecessary variable 'i'
  reset.c: extract function for updating {ORIG_,}HEAD
  reset.c: share call to die_if_unmerged_cache()
  reset --keep: only write index file once
  reset: avoid redundant error message
  reset.c: replace switch by if-else
  reset.c: move update_index_refresh() call out of read_from_tree()
  reset.c: move lock, write and commit out of update_index_refresh()
  reset [--mixed]: only write index file once
  reset.c: finish entire cmd_reset() whether or not pathspec is given
  reset.c: inline update_index_refresh()
  reset $sha1 $pathspec: require $sha1 only to be treeish
  reset: allow reset on unborn branch
  reset [--mixed]: use diff-based reset whether or not pathspec was
    given
 builtin/reset.c                | 283 +++++++++++++++++++----------------------
 t/t2013-checkout-submodule.sh  |   2 +-
 t/t7102-reset.sh               |  26 +++-
 t/t7106-reset-unborn-branch.sh |  52 ++++++++
 4 files changed, 203 insertions(+), 160 deletions(-)
 create mode 100755 t/t7106-reset-unborn-branch.sh
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 01/19] reset $pathspec: no need to discard index

Since 34110cd (Make 'unpack_trees()' have a separate source and destination index, 2008-03-06), the index no longer gets clobbered by do_diff_cache() and we can remove the code for discarding and re-reading it.

There are two paths to update_index_refresh() from cmd_reset(), but on both paths, either read_cache() or read_cache_unmerged() will have been called, so the call to read_cache() in this method is redundant (although practically free).

This speeds up "git reset -- ." a little on the linux-2.6 repo (best of five, warm cache):

        Before      After
real    0m0.093s    0m0.080s
user    0m0.040s    0m0.020s
sys     0m0.050s    0m0.050s
Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c | 16 +---------------
 1 file changed, 1 insertion(+), 15 deletions(-)
Show changes to builtin/reset.c +1 −15
diff --git a/builtin/reset.c b/builtin/reset.c
index 915cc9f..8cc7c72 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -126,9 +126,6 @@ static int update_index_refresh(int fd, struct lock_file *index_lock, int flags)
 		fd = hold_locked_index(index_lock, 1);
 	}
 
-	if (read_cache() < 0)
-		return error(_("Could not read index"));
-
 	result = refresh_index(&the_index, (flags), NULL, NULL,
 			       _("Unstaged changes after reset:")) ? 1 : 0;
 	if (write_cache(fd, active_cache, active_nr) ||
@@ -141,12 +138,6 @@ static void update_index_from_diff(struct diff_queue_struct *q,
 		struct diff_options *opt, void *data)
 {
 	int i;
-	int *discard_flag = data;
-
-	/* do_diff_cache() mangled the index */
-	discard_cache();
-	*discard_flag = 1;
-	read_cache();
 
 	for (i = 0; i < q->nr; i++) {
 		struct diff_filespec *one = q->queue[i]->one;
@@ -179,17 +170,15 @@ static int read_from_tree(const char *prefix, const char **argv,
 		unsigned char *tree_sha1, int refresh_flags)
 {
 	struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-	int index_fd, index_was_discarded = 0;
+	int index_fd;
 	struct diff_options opt;
 
 	memset(&opt, 0, sizeof(opt));
 	diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
 	opt.output_format = DIFF_FORMAT_CALLBACK;
 	opt.format_callback = update_index_from_diff;
-	opt.format_callback_data = &index_was_discarded;
 
 	index_fd = hold_locked_index(lock, 1);
-	index_was_discarded = 0;
 	read_cache();
 	if (do_diff_cache(tree_sha1, &opt))
 		return 1;
@@ -197,9 +186,6 @@ static int read_from_tree(const char *prefix, const char **argv,
 	diff_flush(&opt);
 	diff_tree_release_paths(&opt);
 
-	if (!index_was_discarded)
-		/* The index is still clobbered from do_diff_cache() */
-		discard_cache();
 	return update_index_refresh(index_fd, lock, refresh_flags);
 }
 
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 02/19] reset $pathspec: exit with code 0 if successful

"git reset $pathspec" currently exits with a non-zero exit code if the worktree is dirty after resetting, which is inconsistent with reset without pathspec, and it makes it harder to know whether the command really failed. Change it to exit with code 0 regardless of whether the worktree is dirty so that non-zero indicates an error.

This makes the 4 "disambiguation" test cases in t7102 clearer since they all used to "fail", 3 of which "failed" due to changes in the work tree. Now only the ambiguous one fails.

Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c               |  8 +++-----
 t/t2013-checkout-submodule.sh |  2 +-
 t/t7102-reset.sh              | 18 ++++++++++++------
 3 files changed, 16 insertions(+), 12 deletions(-)
Show changes to 3 files +16 −12

builtin/reset.c, t/t2013-checkout-submodule.sh, t/t7102-reset.sh

diff --git a/builtin/reset.c b/builtin/reset.c
index 8cc7c72..65413d0 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -119,19 +119,17 @@ static void print_new_head_line(struct commit *commit)
 
 static int update_index_refresh(int fd, struct lock_file *index_lock, int flags)
 {
-	int result;
-
 	if (!index_lock) {
 		index_lock = xcalloc(1, sizeof(struct lock_file));
 		fd = hold_locked_index(index_lock, 1);
 	}
 
-	result = refresh_index(&the_index, (flags), NULL, NULL,
-			       _("Unstaged changes after reset:")) ? 1 : 0;
+	refresh_index(&the_index, (flags), NULL, NULL,
+		      _("Unstaged changes after reset:"));
 	if (write_cache(fd, active_cache, active_nr) ||
 			commit_locked_index(index_lock))
 		return error ("Could not refresh index");
-	return result;
+	return 0;
 }
 
 static void update_index_from_diff(struct diff_queue_struct *q,
diff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh
index 70edbb3..06b18f8 100755
--- a/t/t2013-checkout-submodule.sh
+++ b/t/t2013-checkout-submodule.sh
@@ -23,7 +23,7 @@ test_expect_success '"reset <submodule>" updates the index' '
 	git update-index --refresh &&
 	git diff-files --quiet &&
 	git diff-index --quiet --cached HEAD &&
-	test_must_fail git reset HEAD^ submodule &&
+	git reset HEAD^ submodule &&
 	test_must_fail git diff-files --quiet &&
 	git reset submodule &&
 	git diff-files --quiet
diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh
index b096dc8..81b2570 100755
--- a/t/t7102-reset.sh
+++ b/t/t7102-reset.sh
@@ -388,7 +388,8 @@ test_expect_success 'test --mixed <paths>' '
 	echo 4 > file4 &&
 	echo 5 > file1 &&
 	git add file1 file3 file4 &&
-	test_must_fail git reset HEAD -- file1 file2 file3 &&
+	git reset HEAD -- file1 file2 file3 &&
+	test_must_fail git diff --quiet &&
 	git diff > output &&
 	test_cmp output expect &&
 	git diff --cached > output &&
@@ -402,7 +403,8 @@ test_expect_success 'test resetting the index at give paths' '
 	>sub/file2 &&
 	git update-index --add sub/file1 sub/file2 &&
 	T=$(git write-tree) &&
-	test_must_fail git reset HEAD sub/file2 &&
+	git reset HEAD sub/file2 &&
+	test_must_fail git diff --quiet &&
 	U=$(git write-tree) &&
 	echo "$T" &&
 	echo "$U" &&
@@ -440,7 +442,8 @@ test_expect_success 'resetting specific path that is unmerged' '
 		echo "100644 $F3 3	file2"
 	} | git update-index --index-info &&
 	git ls-files -u &&
-	test_must_fail git reset HEAD file2 &&
+	git reset HEAD file2 &&
+	test_must_fail git diff --quiet &&
 	git diff-index --exit-code --cached HEAD
 '
 
@@ -449,7 +452,8 @@ test_expect_success 'disambiguation (1)' '
 	git reset --hard &&
 	>secondfile &&
 	git add secondfile &&
-	test_must_fail git reset secondfile &&
+	git reset secondfile &&
+	test_must_fail git diff --quiet -- secondfile &&
 	test -z "$(git diff --cached --name-only)" &&
 	test -f secondfile &&
 	test ! -s secondfile
@@ -474,7 +478,8 @@ test_expect_success 'disambiguation (3)' '
 	>secondfile &&
 	git add secondfile &&
 	rm -f secondfile &&
-	test_must_fail git reset HEAD secondfile &&
+	git reset HEAD secondfile &&
+	test_must_fail git diff --quiet &&
 	test -z "$(git diff --cached --name-only)" &&
 	test ! -f secondfile
 
@@ -486,7 +491,8 @@ test_expect_success 'disambiguation (4)' '
 	>secondfile &&
 	git add secondfile &&
 	rm -f secondfile &&
-	test_must_fail git reset -- secondfile &&
+	git reset -- secondfile &&
+	test_must_fail git diff --quiet &&
 	test -z "$(git diff --cached --name-only)" &&
 	test ! -f secondfile
 '
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 03/19] reset.c: pass pathspec around instead of (prefix, argv) pair

We use the path arguments in two places in reset.c: in interactive_reset() and read_from_tree(). Both of these call get_pathspec(), so we pass the (prefix, argv) pair to both functions. Move the call to get_pathspec() out of these methods, for two reasons: 1) One argument is simpler than two. 2) It lets us use the (arguably clearer) "if (pathspec)" in place of "if (i < argc)".

Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c | 27 ++++++++++-----------------
 1 file changed, 10 insertions(+), 17 deletions(-)
Show changes to builtin/reset.c +10 −17
diff --git a/builtin/reset.c b/builtin/reset.c
index 65413d0..045c960 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -153,26 +153,15 @@ static void update_index_from_diff(struct diff_queue_struct *q,
 	}
 }
 
-static int interactive_reset(const char *revision, const char **argv,
-			     const char *prefix)
-{
-	const char **pathspec = NULL;
-
-	if (*argv)
-		pathspec = get_pathspec(prefix, argv);
-
-	return run_add_interactive(revision, "--patch=reset", pathspec);
-}
-
-static int read_from_tree(const char *prefix, const char **argv,
-		unsigned char *tree_sha1, int refresh_flags)
+static int read_from_tree(const char **pathspec, unsigned char *tree_sha1,
+			  int refresh_flags)
 {
 	struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 	int index_fd;
 	struct diff_options opt;
 
 	memset(&opt, 0, sizeof(opt));
-	diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
+	diff_tree_setup_paths(pathspec, &opt);
 	opt.output_format = DIFF_FORMAT_CALLBACK;
 	opt.format_callback = update_index_from_diff;
 
@@ -216,6 +205,7 @@ 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];
+	const char **pathspec = NULL;
 	struct commit *commit;
 	struct strbuf msg = STRBUF_INIT;
 	const struct option options[] = {
@@ -287,22 +277,25 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		die(_("Could not parse object '%s'."), rev);
 	hashcpy(sha1, commit->object.sha1);
 
+	if (i < argc)
+		pathspec = get_pathspec(prefix, argv + i);
+
 	if (patch_mode) {
 		if (reset_type != NONE)
 			die(_("--patch is incompatible with --{hard,mixed,soft}"));
-		return interactive_reset(rev, argv + i, prefix);
+		return run_add_interactive(rev, "--patch=reset", pathspec);
 	}
 
 	/* git reset tree [--] paths... can be used to
 	 * load chosen paths from the tree into the index without
 	 * affecting the working tree nor HEAD. */
-	if (i < argc) {
+	if (pathspec) {
 		if (reset_type == MIXED)
 			warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."));
 		else if (reset_type != NONE)
 			die(_("Cannot do %s reset with paths."),
 					_(reset_type_names[reset_type]));
-		return read_from_tree(prefix, argv + i, sha1,
+		return read_from_tree(pathspec, sha1,
 				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
 	}
 	if (reset_type == NONE)
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 04/19] reset: don't allow "git reset -- $pathspec" in bare repo

Running e.g. "git reset ." in a bare repo results in an index file being created from the HEAD commit. The differences compared to the index are then printed as usual, but since there is no worktree, it will appear as if all files are deleted. For example, in a bare clone of git.git:

  Unstaged changes after reset:
  D       .gitattributes
  D       .gitignore
  D       .mailmap
  ...

This happens because the check for is_bare_repository() happens after we branch off into read_from_tree() to reset with paths. Fix by moving the branching point after the check.

Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
Show changes to builtin/reset.c +4 −2
diff --git a/builtin/reset.c b/builtin/reset.c
index 045c960..664fad9 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -295,8 +295,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		else if (reset_type != NONE)
 			die(_("Cannot do %s reset with paths."),
 					_(reset_type_names[reset_type]));
-		return read_from_tree(pathspec, sha1,
-				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
 	}
 	if (reset_type == NONE)
 		reset_type = MIXED; /* by default */
@@ -308,6 +306,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		die(_("%s reset is not allowed in a bare repository"),
 		    _(reset_type_names[reset_type]));
 
+	if (pathspec)
+		return read_from_tree(pathspec, sha1,
+				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+
 	/* Soft reset does not touch the index file nor the working tree
 	 * at all, but requires them in a good order.  Other resets reset
 	 * the index file to the tree object we are switching to. */
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 05/19] reset.c: extract function for parsing arguments

Declutter cmd_reset() a bit by moving out the argument parsing to its own function.

Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c | 70 +++++++++++++++++++++++++++++++--------------------------
 1 file changed, 38 insertions(+), 32 deletions(-)
Show changes to builtin/reset.c +38 −32
diff --git a/builtin/reset.c b/builtin/reset.c
index 664fad9..58f0f61 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -198,36 +198,11 @@ static void die_if_unmerged_cache(int reset_type)
 
 }
 
-int cmd_reset(int argc, const char **argv, const char *prefix)
+static const char **parse_args(int argc, const char **argv, const char *prefix, const char **rev_ret)
 {
-	int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
-	int patch_mode = 0;
+	int i = 0;
 	const char *rev = "HEAD";
-	unsigned char sha1[20], *orig = NULL, sha1_orig[20],
-				*old_orig = NULL, sha1_old_orig[20];
-	const char **pathspec = NULL;
-	struct commit *commit;
-	struct strbuf msg = STRBUF_INIT;
-	const struct option options[] = {
-		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
-		OPT_SET_INT(0, "mixed", &reset_type,
-						N_("reset HEAD and index"), MIXED),
-		OPT_SET_INT(0, "soft", &reset_type, N_("reset only HEAD"), SOFT),
-		OPT_SET_INT(0, "hard", &reset_type,
-				N_("reset HEAD, index and working tree"), HARD),
-		OPT_SET_INT(0, "merge", &reset_type,
-				N_("reset HEAD, index and working tree"), MERGE),
-		OPT_SET_INT(0, "keep", &reset_type,
-				N_("reset HEAD but keep local changes"), KEEP),
-		OPT_BOOLEAN('p', "patch", &patch_mode, N_("select hunks interactively")),
-		OPT_END()
-	};
-
-	git_config(git_default_config, NULL);
-
-	argc = parse_options(argc, argv, prefix, options, git_reset_usage,
-						PARSE_OPT_KEEP_DASHDASH);
-
+	unsigned char unused[20];
 	/*
 	 * Possible arguments are:
 	 *
@@ -250,7 +225,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_committish(argv[i], unused)) {
 			/*
 			 * Ok, argv[i] looks like a rev; it should not
 			 * be a filename.
@@ -262,6 +237,40 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 			verify_filename(prefix, argv[i], 1);
 		}
 	}
+	*rev_ret = rev;
+	return i < argc ? get_pathspec(prefix, argv + i) : NULL;
+}
+
+int cmd_reset(int argc, const char **argv, const char *prefix)
+{
+	int reset_type = NONE, update_ref_status = 0, quiet = 0;
+	int patch_mode = 0;
+	const char *rev;
+	unsigned char sha1[20], *orig = NULL, sha1_orig[20],
+				*old_orig = NULL, sha1_old_orig[20];
+	const char **pathspec = NULL;
+	struct commit *commit;
+	struct strbuf msg = STRBUF_INIT;
+	const struct option options[] = {
+		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
+		OPT_SET_INT(0, "mixed", &reset_type,
+						N_("reset HEAD and index"), MIXED),
+		OPT_SET_INT(0, "soft", &reset_type, N_("reset only HEAD"), SOFT),
+		OPT_SET_INT(0, "hard", &reset_type,
+				N_("reset HEAD, index and working tree"), HARD),
+		OPT_SET_INT(0, "merge", &reset_type,
+				N_("reset HEAD, index and working tree"), MERGE),
+		OPT_SET_INT(0, "keep", &reset_type,
+				N_("reset HEAD but keep local changes"), KEEP),
+		OPT_BOOLEAN('p', "patch", &patch_mode, N_("select hunks interactively")),
+		OPT_END()
+	};
+
+	git_config(git_default_config, NULL);
+
+	argc = parse_options(argc, argv, prefix, options, git_reset_usage,
+						PARSE_OPT_KEEP_DASHDASH);
+	pathspec = parse_args(argc, argv, prefix, &rev);
 
 	if (get_sha1_committish(rev, sha1))
 		die(_("Failed to resolve '%s' as a valid ref."), rev);
@@ -277,9 +286,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		die(_("Could not parse object '%s'."), rev);
 	hashcpy(sha1, commit->object.sha1);
 
-	if (i < argc)
-		pathspec = get_pathspec(prefix, argv + i);
-
 	if (patch_mode) {
 		if (reset_type != NONE)
 			die(_("--patch is incompatible with --{hard,mixed,soft}"));
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 06/19] reset.c: remove unnecessary variable 'i'

Throughout most of parse_args(), the variable 'i' remains at 0. Many references are still made to the variable even when it could only have the value 0. This made at least me, who has relatively little experience with C programming styles, think that parts of the function was meant to be part of a loop. To avoid such confusion, remove the variable and also the 'argc' parameter and check for NULL trailing argv instead.

Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
I explained a bit more why I was confused by the current style, but
I'm also perfectly happy if you just drop the patch (there would be
some minor conflicts in a later patch, though).
 builtin/reset.c | 33 ++++++++++++++++-----------------
 1 file changed, 16 insertions(+), 17 deletions(-)
Show changes to builtin/reset.c +16 −17
diff --git a/builtin/reset.c b/builtin/reset.c
index 58f0f61..d89cf4d 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -198,9 +198,8 @@ static void die_if_unmerged_cache(int reset_type)
 
 }
 
-static const char **parse_args(int argc, const char **argv, const char *prefix, const char **rev_ret)
+static const char **parse_args(const char **argv, const char *prefix, const char **rev_ret)
 {
-	int i = 0;
 	const char *rev = "HEAD";
 	unsigned char unused[20];
 	/*
@@ -211,34 +210,34 @@ static const char **parse_args(int argc, const char **argv, const char *prefix,
 	 * git reset [-opts] -- <paths>...
 	 * git reset [-opts] <paths>...
 	 *
-	 * At this point, argv[i] points immediately after [-opts].
+	 * At this point, argv points immediately after [-opts].
 	 */
 
-	if (i < argc) {
-		if (!strcmp(argv[i], "--")) {
-			i++; /* reset to HEAD, possibly with paths */
-		} else if (i + 1 < argc && !strcmp(argv[i+1], "--")) {
-			rev = argv[i];
-			i += 2;
+	if (argv[0]) {
+		if (!strcmp(argv[0], "--")) {
+			argv++; /* reset to HEAD, possibly with paths */
+		} else if (argv[1] && !strcmp(argv[1], "--")) {
+			rev = argv[0];
+			argv += 2;
 		}
 		/*
-		 * Otherwise, argv[i] could be either <rev> or <paths> and
+		 * Otherwise, argv[0] could be either <rev> or <paths> and
 		 * has to be unambiguous.
 		 */
-		else if (!get_sha1_committish(argv[i], unused)) {
+		else if (!get_sha1_committish(argv[0], unused)) {
 			/*
-			 * Ok, argv[i] looks like a rev; it should not
+			 * Ok, argv[0] looks like a rev; it should not
 			 * be a filename.
 			 */
-			verify_non_filename(prefix, argv[i]);
-			rev = argv[i++];
+			verify_non_filename(prefix, argv[0]);
+			rev = *argv++;
 		} else {
 			/* Otherwise we treat this as a filename */
-			verify_filename(prefix, argv[i], 1);
+			verify_filename(prefix, argv[0], 1);
 		}
 	}
 	*rev_ret = rev;
-	return i < argc ? get_pathspec(prefix, argv + i) : NULL;
+	return argv[0] ? get_pathspec(prefix, argv) : NULL;
 }
 
 int cmd_reset(int argc, const char **argv, const char *prefix)
@@ -270,7 +269,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 
 	argc = parse_options(argc, argv, prefix, options, git_reset_usage,
 						PARSE_OPT_KEEP_DASHDASH);
-	pathspec = parse_args(argc, argv, prefix, &rev);
+	pathspec = parse_args(argv, prefix, &rev);
 
 	if (get_sha1_committish(rev, sha1))
 		die(_("Failed to resolve '%s' as a valid ref."), rev);
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 07/19] reset.c: extract function for updating {ORIG_,}HEAD

By extracting the code for updating the HEAD and ORIG_HEAD symbolic references to a separate function, we declutter cmd_reset() a bit and we make it clear that e.g. the four variables {,sha1_}{,old_}orig are only used by this code.

Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c | 39 +++++++++++++++++++++++----------------
 1 file changed, 23 insertions(+), 16 deletions(-)
Show changes to builtin/reset.c +23 −16
diff --git a/builtin/reset.c b/builtin/reset.c
index d89cf4d..2187d64 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -240,16 +240,35 @@ static const char **parse_args(const char **argv, const char *prefix, const char
 	return argv[0] ? get_pathspec(prefix, argv) : NULL;
 }
 
+static int update_refs(const char *rev, const unsigned char *sha1)
+{
+	int update_ref_status;
+	struct strbuf msg = STRBUF_INIT;
+	unsigned char *orig = NULL, sha1_orig[20],
+		*old_orig = NULL, sha1_old_orig[20];
+
+	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);
+	strbuf_release(&msg);
+	return update_ref_status;
+}
+
 int cmd_reset(int argc, const char **argv, const char *prefix)
 {
 	int reset_type = NONE, update_ref_status = 0, quiet = 0;
 	int patch_mode = 0;
 	const char *rev;
-	unsigned char sha1[20], *orig = NULL, sha1_orig[20],
-				*old_orig = NULL, sha1_old_orig[20];
+	unsigned char sha1[20];
 	const char **pathspec = NULL;
 	struct commit *commit;
-	struct strbuf msg = STRBUF_INIT;
 	const struct option options[] = {
 		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
 		OPT_SET_INT(0, "mixed", &reset_type,
@@ -333,17 +352,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 
 	/* 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);
+	update_ref_status = update_refs(rev, sha1);
 
 	switch (reset_type) {
 	case HARD:
@@ -360,7 +369,5 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 
 	remove_branch_state();
 
-	strbuf_release(&msg);
-
 	return update_ref_status;
 }
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 08/19] reset.c: share call to die_if_unmerged_cache()

Use a single condition to guard the call to die_if_unmerged_cache for both --soft and --keep. This avoids the small distraction of the precondition check from the logic following it.

Also change an instance of
  if (e)
    err = err || f();
to the almost as short, but clearer
  if (e && !err)
    err = f();
(which is equivalent since we only care whether exit code is 0)
Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)
Show changes to builtin/reset.c +6 −8
diff --git a/builtin/reset.c b/builtin/reset.c
index 2187d64..4e34195 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -337,15 +337,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	/* Soft reset does not touch the index file nor the working tree
 	 * at all, but requires them in a good order.  Other resets reset
 	 * the index file to the tree object we are switching to. */
-	if (reset_type == SOFT)
+	if (reset_type == SOFT || reset_type == KEEP)
 		die_if_unmerged_cache(reset_type);
-	else {
-		int err;
-		if (reset_type == KEEP)
-			die_if_unmerged_cache(reset_type);
-		err = reset_index_file(sha1, reset_type, quiet);
-		if (reset_type == KEEP)
-			err = err || reset_index_file(sha1, MIXED, quiet);
+
+	if (reset_type != SOFT) {
+		int err = reset_index_file(sha1, reset_type, quiet);
+		if (reset_type == KEEP && !err)
+			err = reset_index_file(sha1, MIXED, quiet);
 		if (err)
 			die(_("Could not reset index file to revision '%s'."), rev);
 	}
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 09/19] reset --keep: only write index file once

"git reset --keep" calls reset_index_file() twice, first doing a two-way merge to the target revision, updating the index and worktree, and then resetting the index. After each call, we write the index file.

In the unlikely event that the second call to reset_index_file() fails, the index will have been merged to the target revision, but HEAD will not be updated, leaving the user with a dirty index.

By moving the locking, writing and committing out of reset_index_file() and into the caller, we can avoid writing the index twice, thereby making the sure we don't end up in the half-way reset state. As a bonus, we speed up "git reset --keep" a little on the linux-2.6 repo (best of five, warm cache):

        Before      After
real    0m0.315s    0m0.296s
user    0m0.290s    0m0.280s
sys     0m0.020s    0m0.010s
Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c | 21 ++++++++++-----------
 1 file changed, 10 insertions(+), 11 deletions(-)
Show changes to builtin/reset.c +10 −11
diff --git a/builtin/reset.c b/builtin/reset.c
index 4e34195..7c440ad 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -38,14 +38,12 @@ static inline int is_merge(void)
 	return !access(git_path("MERGE_HEAD"), F_OK);
 }
 
-static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
+static int reset_index(const unsigned char *sha1, int reset_type, int quiet)
 {
 	int nr = 1;
-	int newfd;
 	struct tree_desc desc[2];
 	struct tree *tree;
 	struct unpack_trees_options opts;
-	struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 
 	memset(&opts, 0, sizeof(opts));
 	opts.head_idx = 1;
@@ -67,8 +65,6 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
 		opts.reset = 1;
 	}
 
-	newfd = hold_locked_index(lock, 1);
-
 	read_cache_unmerged();
 
 	if (reset_type == KEEP) {
@@ -91,10 +87,6 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
 		prime_cache_tree(&active_cache_tree, tree);
 	}
 
-	if (write_cache(newfd, active_cache, active_nr) ||
-	    commit_locked_index(lock))
-		return error(_("Could not write new index file."));
-
 	return 0;
 }
 
@@ -341,9 +333,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		die_if_unmerged_cache(reset_type);
 
 	if (reset_type != SOFT) {
-		int err = reset_index_file(sha1, reset_type, quiet);
+		struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+		int newfd = hold_locked_index(lock, 1);
+		int err = reset_index(sha1, reset_type, quiet);
 		if (reset_type == KEEP && !err)
-			err = reset_index_file(sha1, MIXED, quiet);
+			err = reset_index(sha1, MIXED, quiet);
+		if (!err &&
+		    (write_cache(newfd, active_cache, active_nr) ||
+		     commit_locked_index(lock))) {
+			err = error(_("Could not write new index file."));
+		}
 		if (err)
 			die(_("Could not reset index file to revision '%s'."), rev);
 	}
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 10/19] reset: avoid redundant error message

If writing or committing the new index file fails, we print "Could not write new index file." followed by "Could not reset index file to revision $rev.". The first message seems to imply the second, so print only the first message.

Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)
Show changes to builtin/reset.c +3 −5
diff --git a/builtin/reset.c b/builtin/reset.c
index 7c440ad..97fa9f7 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -338,13 +338,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		int err = reset_index(sha1, reset_type, quiet);
 		if (reset_type == KEEP && !err)
 			err = reset_index(sha1, MIXED, quiet);
-		if (!err &&
-		    (write_cache(newfd, active_cache, active_nr) ||
-		     commit_locked_index(lock))) {
-			err = error(_("Could not write new index file."));
-		}
 		if (err)
 			die(_("Could not reset index file to revision '%s'."), rev);
+		if (write_cache(newfd, active_cache, active_nr) ||
+		    commit_locked_index(lock))
+			die(_("Could not write new index file."));
 	}
 
 	/* Any resets update HEAD to the head being switched to,
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 11/19] reset.c: replace switch by if-else

The switch statement towards the end of reset.c is missing case arms for KEEP and MERGE for no obvious reason, and soon the only non-empty case arm will be the one for HARD. So let's proactively replace it by if-else, which will let us move one if statement out without leaving funny-looking left-overs.

Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c | 13 +++----------
 1 file changed, 3 insertions(+), 10 deletions(-)
Show changes to builtin/reset.c +3 −10
diff --git a/builtin/reset.c b/builtin/reset.c
index 97fa9f7..c3eb2eb 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -349,18 +349,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	 * saving the previous head in ORIG_HEAD before. */
 	update_ref_status = update_refs(rev, sha1);
 
-	switch (reset_type) {
-	case HARD:
-		if (!update_ref_status && !quiet)
-			print_new_head_line(commit);
-		break;
-	case SOFT: /* Nothing else to do. */
-		break;
-	case MIXED: /* Report what has not been updated. */
+	if (reset_type == HARD && !update_ref_status && !quiet)
+		print_new_head_line(commit);
+	else if (reset_type == MIXED) /* Report what has not been updated. */
 		update_index_refresh(0, NULL,
 				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
-		break;
-	}
 
 	remove_branch_state();
 
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 12/19] reset.c: move update_index_refresh() call out of read_from_tree()

The final part of cmd_reset() essentially looks like:
  if (pathspec) {
    ...
    read_from_tree(...);
  } else {
    ...
    reset_index(...);
    update_index_refresh(...);
    ...
  }

where read_from_tree() internally also calls update_index_refresh(). Move the call to update_index_refresh() out of read_from_tree for symmetry with the 'else' block, making read_from_tree() and reset_index() closer in functionality.

Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)
Show changes to builtin/reset.c +9 −9
diff --git a/builtin/reset.c b/builtin/reset.c
index c3eb2eb..70733c2 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -145,11 +145,8 @@ static void update_index_from_diff(struct diff_queue_struct *q,
 	}
 }
 
-static int read_from_tree(const char **pathspec, unsigned char *tree_sha1,
-			  int refresh_flags)
+static int read_from_tree(const char **pathspec, unsigned char *tree_sha1)
 {
-	struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-	int index_fd;
 	struct diff_options opt;
 
 	memset(&opt, 0, sizeof(opt));
@@ -157,7 +154,6 @@ static int read_from_tree(const char **pathspec, unsigned char *tree_sha1,
 	opt.output_format = DIFF_FORMAT_CALLBACK;
 	opt.format_callback = update_index_from_diff;
 
-	index_fd = hold_locked_index(lock, 1);
 	read_cache();
 	if (do_diff_cache(tree_sha1, &opt))
 		return 1;
@@ -165,7 +161,7 @@ static int read_from_tree(const char **pathspec, unsigned char *tree_sha1,
 	diff_flush(&opt);
 	diff_tree_release_paths(&opt);
 
-	return update_index_refresh(index_fd, lock, refresh_flags);
+	return 0;
 }
 
 static void set_reflog_message(struct strbuf *sb, const char *action,
@@ -322,9 +318,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		die(_("%s reset is not allowed in a bare repository"),
 		    _(reset_type_names[reset_type]));
 
-	if (pathspec)
-		return read_from_tree(pathspec, sha1,
-				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+	if (pathspec) {
+		struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+		int index_fd = hold_locked_index(lock, 1);
+		return read_from_tree(pathspec, sha1) ||
+			update_index_refresh(index_fd, lock,
+					quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+	}
 
 	/* Soft reset does not touch the index file nor the working tree
 	 * at all, but requires them in a good order.  Other resets reset
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 13/19] reset.c: move lock, write and commit out of update_index_refresh()

In preparation for the/a following patch, move the locking, writing and committing of the index file out of update_index_refresh(). The code duplication caused will soon be taken care of. What remains of update_index_refresh() is just one line, but it is still called from two places, so let's leave it for now.

In the process, we expose and fix the minor UI bug that makes us print "Could not refresh index" when we fail to write the index file when invoked with a pathspec. Copy the error message from the pathspec-less codepath ("Could not write new index file.").

Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c | 34 ++++++++++++++++++----------------
 1 file changed, 18 insertions(+), 16 deletions(-)
Show changes to builtin/reset.c +18 −16
diff --git a/builtin/reset.c b/builtin/reset.c
index 70733c2..c1d6ef2 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -109,19 +109,10 @@ static void print_new_head_line(struct commit *commit)
 		printf("\n");
 }
 
-static int update_index_refresh(int fd, struct lock_file *index_lock, int flags)
+static void update_index_refresh(int flags)
 {
-	if (!index_lock) {
-		index_lock = xcalloc(1, sizeof(struct lock_file));
-		fd = hold_locked_index(index_lock, 1);
-	}
-
 	refresh_index(&the_index, (flags), NULL, NULL,
 		      _("Unstaged changes after reset:"));
-	if (write_cache(fd, active_cache, active_nr) ||
-			commit_locked_index(index_lock))
-		return error ("Could not refresh index");
-	return 0;
 }
 
 static void update_index_from_diff(struct diff_queue_struct *q,
@@ -321,9 +312,14 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	if (pathspec) {
 		struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 		int index_fd = hold_locked_index(lock, 1);
-		return read_from_tree(pathspec, sha1) ||
-			update_index_refresh(index_fd, lock,
-					quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+		if (read_from_tree(pathspec, sha1))
+			return 1;
+		update_index_refresh(
+			quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+		if (write_cache(index_fd, active_cache, active_nr) ||
+		    commit_locked_index(lock))
+			return error("Could not write new index file.");
+		return 0;
 	}
 
 	/* Soft reset does not touch the index file nor the working tree
@@ -351,9 +347,15 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 
 	if (reset_type == HARD && !update_ref_status && !quiet)
 		print_new_head_line(commit);
-	else if (reset_type == MIXED) /* Report what has not been updated. */
-		update_index_refresh(0, NULL,
-				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+	else if (reset_type == MIXED) { /* Report what has not been updated. */
+		struct lock_file *index_lock = xcalloc(1, sizeof(struct lock_file));
+		int fd = hold_locked_index(index_lock, 1);
+		update_index_refresh(
+			quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+		if (write_cache(fd, active_cache, active_nr) ||
+		    commit_locked_index(index_lock))
+			error("Could not refresh index");
+	}
 
 	remove_branch_state();
 
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 14/19] reset [--mixed]: only write index file once

When doing a mixed reset without paths, the index is locked, read, reset, and written back as part of the actual reset operation (in reset_index()). Then, when showing the list of worktree modifications, we lock the index again, refresh it, and write it.

Change this so we only write the index once, making "git reset" a little faster. It does mean that the index lock will be held a little longer, but the difference is small compared to the time spent refreshing the index.

There is one minor functional difference: We used to say "Could not write new index file." if the first write failed, and "Could not refresh index" if the second write failed. Now, we will only use the first message.

This speeds up "git reset" a little on the linux-2.6 repo (best of five, warm cache):

        Before      After
real    0m0.239s    0m0.214s
user    0m0.160s    0m0.130s
sys     0m0.070s    0m0.080s
Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)
Show changes to builtin/reset.c +5 −9
diff --git a/builtin/reset.c b/builtin/reset.c
index c1d6ef2..e8a3e41 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -336,6 +336,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 			err = reset_index(sha1, MIXED, quiet);
 		if (err)
 			die(_("Could not reset index file to revision '%s'."), rev);
+
+		if (reset_type == MIXED) /* Report what has not been updated. */
+			update_index_refresh(
+				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+
 		if (write_cache(newfd, active_cache, active_nr) ||
 		    commit_locked_index(lock))
 			die(_("Could not write new index file."));
@@ -347,15 +352,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 
 	if (reset_type == HARD && !update_ref_status && !quiet)
 		print_new_head_line(commit);
-	else if (reset_type == MIXED) { /* Report what has not been updated. */
-		struct lock_file *index_lock = xcalloc(1, sizeof(struct lock_file));
-		int fd = hold_locked_index(index_lock, 1);
-		update_index_refresh(
-			quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
-		if (write_cache(fd, active_cache, active_nr) ||
-		    commit_locked_index(index_lock))
-			error("Could not refresh index");
-	}
 
 	remove_branch_state();
 
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 15/19] reset.c: finish entire cmd_reset() whether or not pathspec is given

By not returning from inside the "if (pathspec)" block, we can let the pathspec-aware and pathspec-less code share a bit more, making it easier to make future changes that should affect both cases. This also highlights the similarity between read_from_tree() and reset_index().

Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c | 42 ++++++++++++++++++------------------------
 1 file changed, 18 insertions(+), 24 deletions(-)
Show changes to builtin/reset.c +18 −24
diff --git a/builtin/reset.c b/builtin/reset.c
index e8a3e41..c316d9b 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -309,19 +309,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		die(_("%s reset is not allowed in a bare repository"),
 		    _(reset_type_names[reset_type]));
 
-	if (pathspec) {
-		struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-		int index_fd = hold_locked_index(lock, 1);
-		if (read_from_tree(pathspec, sha1))
-			return 1;
-		update_index_refresh(
-			quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
-		if (write_cache(index_fd, active_cache, active_nr) ||
-		    commit_locked_index(lock))
-			return error("Could not write new index file.");
-		return 0;
-	}
-
 	/* Soft reset does not touch the index file nor the working tree
 	 * at all, but requires them in a good order.  Other resets reset
 	 * the index file to the tree object we are switching to. */
@@ -331,11 +318,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	if (reset_type != SOFT) {
 		struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 		int newfd = hold_locked_index(lock, 1);
-		int err = reset_index(sha1, reset_type, quiet);
-		if (reset_type == KEEP && !err)
-			err = reset_index(sha1, MIXED, quiet);
-		if (err)
-			die(_("Could not reset index file to revision '%s'."), rev);
+		if (pathspec) {
+			if (read_from_tree(pathspec, sha1))
+				return 1;
+		} else {
+			int err = reset_index(sha1, reset_type, quiet);
+			if (reset_type == KEEP && !err)
+				err = reset_index(sha1, MIXED, quiet);
+			if (err)
+				die(_("Could not reset index file to revision '%s'."), rev);
+		}
 
 		if (reset_type == MIXED) /* Report what has not been updated. */
 			update_index_refresh(
@@ -346,14 +338,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 			die(_("Could not write new index file."));
 	}
 
-	/* Any resets update HEAD to the head being switched to,
-	 * saving the previous head in ORIG_HEAD before. */
-	update_ref_status = update_refs(rev, sha1);
+	if (!pathspec) {
+		/* Any resets without paths update HEAD to the head being
+		 * switched to, saving the previous head in ORIG_HEAD before. */
+		update_ref_status = update_refs(rev, sha1);
 
-	if (reset_type == HARD && !update_ref_status && !quiet)
-		print_new_head_line(commit);
+		if (reset_type == HARD && !update_ref_status && !quiet)
+			print_new_head_line(commit);
 
-	remove_branch_state();
+		remove_branch_state();
+	}
 
 	return update_ref_status;
 }
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 16/19] reset.c: inline update_index_refresh()

Now that there is only one caller left to the single-line method update_index_refresh(), inline it.

Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)
Show changes to builtin/reset.c +5 −9
diff --git a/builtin/reset.c b/builtin/reset.c
index c316d9b..520c1a5 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -109,12 +109,6 @@ static void print_new_head_line(struct commit *commit)
 		printf("\n");
 }
 
-static void update_index_refresh(int flags)
-{
-	refresh_index(&the_index, (flags), NULL, NULL,
-		      _("Unstaged changes after reset:"));
-}
-
 static void update_index_from_diff(struct diff_queue_struct *q,
 		struct diff_options *opt, void *data)
 {
@@ -329,9 +323,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 				die(_("Could not reset index file to revision '%s'."), rev);
 		}
 
-		if (reset_type == MIXED) /* Report what has not been updated. */
-			update_index_refresh(
-				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+		if (reset_type == MIXED) { /* Report what has not been updated. */
+			int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
+			refresh_index(&the_index, flags, NULL, NULL,
+				      _("Unstaged changes after reset:"));
+		}
 
 		if (write_cache(newfd, active_cache, active_nr) ||
 		    commit_locked_index(lock))
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 17/19] reset $sha1 $pathspec: require $sha1 only to be treeish

Resetting with paths does not update HEAD and there is nothing else that a commit should be needed for. Relax the argument parsing so only a tree is required.

The sha1 is only passed to read_from_tree(), which already only requires a tree.

The "rev" variable we pass to run_add_interactive() will resolve to a tree. This is fine since interactive_reset only needs the parameter to be a treeish and doesn't use it for display purposes.

Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c  | 48 +++++++++++++++++++++++++++---------------------
 t/t7102-reset.sh |  8 ++++++++
 2 files changed, 35 insertions(+), 21 deletions(-)
Show changes to 2 files +35 −21

builtin/reset.c, t/t7102-reset.sh

diff --git a/builtin/reset.c b/builtin/reset.c
index 520c1a5..b776867 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -178,9 +178,10 @@ static const char **parse_args(const char **argv, const char *prefix, const char
 	/*
 	 * Possible arguments are:
 	 *
-	 * git reset [-opts] <rev> <paths>...
-	 * git reset [-opts] <rev> -- <paths>...
-	 * git reset [-opts] -- <paths>...
+	 * git reset [-opts] [<rev>]
+	 * git reset [-opts] <tree> [<paths>...]
+	 * git reset [-opts] <tree> -- [<paths>...]
+	 * git reset [-opts] -- [<paths>...]
 	 * git reset [-opts] <paths>...
 	 *
 	 * At this point, argv points immediately after [-opts].
@@ -195,11 +196,13 @@ static const char **parse_args(const char **argv, const char *prefix, const char
 		}
 		/*
 		 * Otherwise, argv[0] could be either <rev> or <paths> and
-		 * has to be unambiguous.
+		 * has to be unambiguous. If there is a single argument, it
+		 * can not be a tree
 		 */
-		else if (!get_sha1_committish(argv[0], unused)) {
+		else if ((!argv[1] && !get_sha1_committish(argv[0], unused)) ||
+			 (argv[1] && !get_sha1_treeish(argv[0], unused))) {
 			/*
-			 * Ok, argv[0] looks like a rev; it should not
+			 * Ok, argv[0] looks like a commit/tree; it should not
 			 * be a filename.
 			 */
 			verify_non_filename(prefix, argv[0]);
@@ -241,7 +244,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	const char *rev;
 	unsigned char sha1[20];
 	const char **pathspec = NULL;
-	struct commit *commit;
 	const struct option options[] = {
 		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
 		OPT_SET_INT(0, "mixed", &reset_type,
@@ -263,19 +265,23 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 						PARSE_OPT_KEEP_DASHDASH);
 	pathspec = parse_args(argv, prefix, &rev);
 
-	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);
+	if (!pathspec) {
+		struct commit *commit;
+		if (get_sha1_committish(rev, sha1))
+			die(_("Failed to resolve '%s' as a valid revision."), rev);
+		commit = lookup_commit_reference(sha1);
+		if (!commit)
+			die(_("Could not parse object '%s'."), rev);
+		hashcpy(sha1, commit->object.sha1);
+	} else {
+		struct tree *tree;
+		if (get_sha1_treeish(rev, sha1))
+			die(_("Failed to resolve '%s' as a valid tree."), rev);
+		tree = parse_tree_indirect(sha1);
+		if (!tree)
+			die(_("Could not parse object '%s'."), rev);
+		hashcpy(sha1, tree->object.sha1);
+	}
 
 	if (patch_mode) {
 		if (reset_type != NONE)
@@ -340,7 +346,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		update_ref_status = update_refs(rev, sha1);
 
 		if (reset_type == HARD && !update_ref_status && !quiet)
-			print_new_head_line(commit);
+			print_new_head_line(lookup_commit_reference(sha1));
 
 		remove_branch_state();
 	}
diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh
index 81b2570..1fa2a5f 100755
--- a/t/t7102-reset.sh
+++ b/t/t7102-reset.sh
@@ -497,4 +497,12 @@ test_expect_success 'disambiguation (4)' '
 	test ! -f secondfile
 '
 
+test_expect_success 'reset with paths accepts 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_done
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 16, 2013, 18:00 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 17/19] fixup! reset $sha1 $pathspec: require $sha1 only to be treeish

---

Sorry, I forgot the documentation updates. I hope this looks ok. Can you squash this in, Junio? Thanks.

I don't think any documentation update is necessary for the "reset on unborn branch" patch. Let me know if you think differently.

 Documentation/git-reset.txt | 18 +++++++++---------
 builtin/reset.c             |  4 ++--
 2 files changed, 11 insertions(+), 11 deletions(-)
Show changes to 2 files +11 −11

Documentation/git-reset.txt, builtin/reset.c

diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 978d8da..a404b47 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -8,20 +8,20 @@ git-reset - Reset current HEAD to the specified state
 SYNOPSIS
 --------
 [verse]
-'git reset' [-q] [<commit>] [--] <paths>...
-'git reset' (--patch | -p) [<commit>] [--] [<paths>...]
+'git reset' [-q] [<tree-ish>] [--] <paths>...
+'git reset' (--patch | -p) [<tree-sh>] [--] [<paths>...]
 'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>]
 
 DESCRIPTION
 -----------
-In the first and second form, copy entries from <commit> to the index.
+In the first and second form, copy entries from <tree-ish> to the index.
 In the third form, set the current branch head (HEAD) to <commit>, optionally
-modifying index and working tree to match.  The <commit> defaults to HEAD
-in all forms.
+modifying index and working tree to match.  The <tree-ish>/<commit> defaults
+to HEAD in all forms.
 
-'git reset' [-q] [<commit>] [--] <paths>...::
+'git reset' [-q] [<tree-ish>] [--] <paths>...::
 	This form resets the index entries for all <paths> to their
-	state at <commit>.  (It does not affect the working tree, nor
+	state at <tree-ish>.  (It does not affect the working tree, nor
 	the current branch.)
 +
 This means that `git reset <paths>` is the opposite of `git add
@@ -34,9 +34,9 @@ Alternatively, using linkgit:git-checkout[1] and specifying a commit, you
 can copy the contents of a path out of a commit to the index and to the
 working tree in one go.
 
-'git reset' (--patch | -p) [<commit>] [--] [<paths>...]::
+'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...]::
 	Interactively select hunks in the difference between the index
-	and <commit> (defaults to HEAD).  The chosen hunks are applied
+	and <tree-ish> (defaults to HEAD).  The chosen hunks are applied
 	in reverse to the index.
 +
 This means that `git reset -p` is the opposite of `git add -p`, i.e.
diff --git a/builtin/reset.c b/builtin/reset.c
index b776867..cb84f1b 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -23,8 +23,8 @@
 
 static const char * const git_reset_usage[] = {
 	N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
-	N_("git reset [-q] <commit> [--] <paths>..."),
-	N_("git reset --patch [<commit>] [--] [<paths>...]"),
+	N_("git reset [-q] <tree-ish> [--] <paths>..."),
+	N_("git reset --patch [<tree-ish>] [--] [<paths>...]"),
 	NULL
 };
 
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 16, 2013, 18:08 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH v2 17/19] fixup! reset $sha1 $pathspec: require $sha1 only to be treeish

On Wed, Jan 16, 2013 at 10:00 AM, Martin von Zweigbergk <martinvonz@gmail.com> wrote:

> ---
>
> Sorry, I forgot the documentation updates. I hope this looks ok. Can
> you squash this in, Junio? Thanks.

I see the series just entered 'next', so I guess it would have to go on top then. Perhaps with a commit message like as simple as the following. Let me know if you prefer it to be resent as a proper patch. Sorry about the noise.

reset: update documentation to require only tree-ish with paths

When resetting with paths, we no longer require a commit argument, but only a tree-ish. Update the documentation and synopsis accordingly.

Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 18/19] reset: allow reset on unborn branch

Some users seem to think, knowingly or not, that being on an unborn branch is like having a commit with an empty tree checked out, but when run on an unborn branch, "git reset" currently fails with:

  fatal: Failed to resolve 'HEAD' as a valid ref.
Instead of making users figure out that they should run
 git rm --cached -r .

, let's teach "git reset" without a revision argument, when on an unborn branch, to behave as if the user asked to reset to an empty tree. Don't take the analogy with an empty commit too far, though, but still disallow explictly referring to HEAD in "git reset HEAD".

Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
 builtin/reset.c                | 16 ++++++++-----
 t/t7106-reset-unborn-branch.sh | 52 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 62 insertions(+), 6 deletions(-)
 create mode 100755 t/t7106-reset-unborn-branch.sh
Show changes to 2 files +62 −6

builtin/reset.c, t/t7106-reset-unborn-branch.sh

diff --git a/builtin/reset.c b/builtin/reset.c
index b776867..45b01eb 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -240,7 +240,7 @@ static int update_refs(const char *rev, const unsigned char *sha1)
 int cmd_reset(int argc, const char **argv, const char *prefix)
 {
 	int reset_type = NONE, update_ref_status = 0, quiet = 0;
-	int patch_mode = 0;
+	int patch_mode = 0, unborn;
 	const char *rev;
 	unsigned char sha1[20];
 	const char **pathspec = NULL;
@@ -265,7 +265,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 						PARSE_OPT_KEEP_DASHDASH);
 	pathspec = parse_args(argv, prefix, &rev);
 
-	if (!pathspec) {
+	unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", sha1);
+	if (unborn) {
+		/* reset on unborn branch: treat as reset to empty tree */
+		hashcpy(sha1, EMPTY_TREE_SHA1_BIN);
+	} else if (!pathspec) {
 		struct commit *commit;
 		if (get_sha1_committish(rev, sha1))
 			die(_("Failed to resolve '%s' as a valid revision."), rev);
@@ -286,7 +290,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	if (patch_mode) {
 		if (reset_type != NONE)
 			die(_("--patch is incompatible with --{hard,mixed,soft}"));
-		return run_add_interactive(rev, "--patch=reset", pathspec);
+		return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", pathspec);
 	}
 
 	/* git reset tree [--] paths... can be used to
@@ -340,16 +344,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 			die(_("Could not write new index file."));
 	}
 
-	if (!pathspec) {
+	if (!pathspec && !unborn) {
 		/* Any resets without paths update HEAD to the head being
 		 * switched to, saving the previous head in ORIG_HEAD before. */
 		update_ref_status = update_refs(rev, sha1);
 
 		if (reset_type == HARD && !update_ref_status && !quiet)
 			print_new_head_line(lookup_commit_reference(sha1));
-
-		remove_branch_state();
 	}
+	if (!pathspec)
+		remove_branch_state();
 
 	return update_ref_status;
 }
diff --git a/t/t7106-reset-unborn-branch.sh b/t/t7106-reset-unborn-branch.sh
new file mode 100755
index 0000000..8062cf5
--- /dev/null
+++ b/t/t7106-reset-unborn-branch.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='git reset should work on unborn branch'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	echo a >a &&
+	echo b >b
+'
+
+test_expect_success 'reset' '
+	git add a b &&
+	git reset &&
+	test "$(git ls-files)" = ""
+'
+
+test_expect_success 'reset HEAD' '
+	rm .git/index &&
+	git add a b &&
+	test_must_fail git reset HEAD
+'
+
+test_expect_success 'reset $file' '
+	rm .git/index &&
+	git add a b &&
+	git reset a &&
+	test "$(git ls-files)" = "b"
+'
+
+test_expect_success 'reset -p' '
+	rm .git/index &&
+	git add a &&
+	echo y | git reset -p &&
+	test "$(git ls-files)" = ""
+'
+
+test_expect_success 'reset --soft is a no-op' '
+	rm .git/index &&
+	git add a &&
+	git reset --soft
+	test "$(git ls-files)" = "a"
+'
+
+test_expect_success 'reset --hard' '
+	rm .git/index &&
+	git add a &&
+	git reset --hard &&
+	test "$(git ls-files)" = "" &&
+	test_path_is_missing a
+'
+
+test_done
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 05:47 UTC · re: Martin von Zweigbergk · lore

[PATCH v2 19/19] reset [--mixed]: use diff-based reset whether or not pathspec was given

Thanks to b65982b (Optimize "diff-index --cached" using cache-tree, 2009-05-20), resetting with paths is much faster than resetting without paths. Some timings for the linux-2.6 repo to illustrate this (best of five, warm cache):

        reset       reset .
real    0m0.219s    0m0.080s
user    0m0.140s    0m0.040s
sys     0m0.070s    0m0.030s

These two commands should do the same thing, so instead of having the user type the trailing " ." to get the faster do_diff_cache()-based implementation, always use it when doing a mixed reset, with or without paths (so "git reset $rev" would also be faster).

Timing "git reset" shows that it indeed becomes as fast as "git reset ." after this patch.

Signed-off-by: Martin von Zweigbergk <martinvonz@gmail.com>
---
It seems like a better solution would be for unpack_trees() learn the
same tricks as do_diff_cache(). I'm leaving that a challange for the
reader :-). I did have a look a unpack_trees(), but it looked rather
overwhelming.
 builtin/reset.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/reset.c +1 −1
diff --git a/builtin/reset.c b/builtin/reset.c
index 45b01eb..921afbe 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -322,7 +322,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	if (reset_type != SOFT) {
 		struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 		int newfd = hold_locked_index(lock, 1);
-		if (pathspec) {
+		if (reset_type == MIXED) {
 			if (read_from_tree(pathspec, sha1))
 				return 1;
 		} else {
-- 
1.8.1.1.454.gce43f05
Martin von Zweigbergk· Jan 15, 2013, 18:36 UTC · lore

Re: [PATCH v2 06/19] reset.c: remove unnecessary variable 'i'

I suppose this was meant for everyone. Adding back the others.

On Tue, Jan 15, 2013 at 10:27 AM, Holding, Lawrence (NZ) <Lawrence.Holding@cubic.com> wrote:

> Maybe use *argv instead of argv[0]?

Sure. Everywhere? Also in the lines added in patch 17/19 that refer to both argv[0] and argv[1], such as "argv[1] && !get_sha1_treeish(argv[0], unused)"? Or is this just a sign that I'm making the code _more_ confusing to those who are more familiar with C?

← back to recent threads