Re: Git terminology: remote, add, track, stage, etc.

6 messages, 2 authors, 2016-06-15 · open the first message on its own page

Re: Git terminology: remote, add, track, stage, etc.

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:49:49

Jonathan Nieder [off-list ref] writes:
Wouldn't it make sense to make "git reset" basically a synonym for
"git rm --cached" when in the 'branch yet to be born' case?
Hmm,...

While you are on 'master', shouldn't these behave identically?

    $ git reset master -- frotz.c
    $ git reset HEAD -- frotz.c
    $ git reset -- frotz.c

while shouldn't this fail if there is no 'naster' branch?

    $ git reset naster -- frotz.c

It is probably Ok to limit the scope of this change to the case without
any explicit rev, e.g. "git reset -- frotz.c", but at that point I somehow
don't think it will reduce confusion but rather will make things worse.
+	if (!strcmp(rev, "HEAD")) {
Comparing the address of the "HEAD" used for initialization with rev may
make sure that the code will catch only "no explicit rev" case here, but
that is not what is happening here, which is even less consistent.

[RFC/PATCH 0/4] reset: be more flexible about <rev>

From: Jonathan Nieder <hidden>
Date: 2016-06-15 22:49:49

Junio C Hamano wrote:
It is probably Ok to limit the scope of this change to the case without
any explicit rev, e.g. "git reset -- frotz.c", but at that point I somehow
don't think it will reduce confusion but rather will make things worse.
I wouldn't be surprised to find people using

	git reset HEAD <paths>

just because '--' did not come to mind quickly enough.  For example, I
have a faint memory of doing that myself a couple of years ago.  Why
should Git mind?

Patch 1 below teaches reset -p to accept an arbitrary tree for <rev>.
Unfortunately add--interactive notices but does not error out when
<rev> is a blob; that should be fixed in the add--interactive script
by checking the exit status of commands it runs, I think (help from
those more comfortable in perl would be appreciated).

Patch 2 removes the arbitrary restriction in "git reset <rev>
<path>" that <rev> be a commit.  It also paves the way for writing
patch 3 more clearly.

Patch 3 is the "probably Ok" change you mentioned above.  It allows
use of "git reset" to un-add a file from an unborn branch.

Patch 4 is like patch 3, but for "git reset HEAD".

Help on finishing up patch 1 (or comments to the effect that it is
pointless) would be welcome.

Jonathan Nieder (4):
  reset -p: accept "git reset -p <tree>"
  reset: accept "git reset <tree> <path>"
  reset: accept "git reset -- <path>" from unborn branch
  reset: accept "git reset HEAD <path>" from unborn branch

 builtin/reset.c         |   27 ++++++++++++++++-------
 t/t7102-reset.sh        |   31 +++++++++++++++++++++++++++
 t/t7105-reset-patch.sh  |   12 ++++++++++
 t/t7106-reset-unborn.sh |   53 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 115 insertions(+), 8 deletions(-)
 create mode 100755 t/t7106-reset-unborn.sh

-- 
1.7.2.3

[WIP/PATCH 1/4] reset -p: accept "git reset -p <tree>"

From: Jonathan Nieder <hidden>
Date: 2016-06-15 22:49:49

When reset -p was implemented (v1.6.5-rc0~5^2~7, 2009-08-15), it
piggy-backed on an existing "git reset" check to verify that the
<rev> argument represents a valid commit.  By dropping that
check, we can use reset -p to apply changes from an arbitrary
tree; for example, from the linux-2.6 tree:

	git reset -p 2.6.11 -- Makefile

add--interactive already rejects invalid refs.

	$ git init >dev/null 2>&1; git reset -p HEAD; echo $?
	fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree.
	Use '--' to separate paths from revisions
	128

Unfortunate side-effect: reset -p will accept a blob for <rev>,
too.

	$ git reset -p HEAD:git.c; echo $?
	error: bad tree object HEAD:git.c
	No changes.
	0

Signed-off-by: Jonathan Nieder <redacted>
---
 builtin/reset.c        |   12 ++++++------
 t/t7105-reset-patch.sh |   12 ++++++++++++
 2 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/builtin/reset.c b/builtin/reset.c
index 0037be4..a52e6f8 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -297,24 +297,24 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 			/* Otherwise we treat this as a filename */
 			verify_filename(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);
+	}
+
 	if (get_sha1(rev, sha1))
 		die("Failed to resolve '%s' as a valid ref.", rev);
 
 	commit = lookup_commit_reference(sha1);
 	if (!commit)
 		die("Could not parse object '%s'.", rev);
 	hashcpy(sha1, commit->object.sha1);
 
-	if (patch_mode) {
-		if (reset_type != NONE)
-			die("--patch is incompatible with --{hard,mixed,soft}");
-		return interactive_reset(rev, argv + i, prefix);
-	}
-
 	/* 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 (reset_type == MIXED)
diff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh
index 9891e2c..ba3ff42 100755
--- a/t/t7105-reset-patch.sh
+++ b/t/t7105-reset-patch.sh
@@ -46,10 +46,22 @@ test_expect_success PERL 'git reset -p dir' '
 	(echo y; echo n) | git reset -p dir &&
 	verify_state dir/foo work head &&
 	verify_saved_state bar
 '
 
+test_expect_success PERL 'git reset -p <tree> dir' '
+	set_state dir/foo work work &&
+	(echo y; echo n) | git reset -p HEAD^{tree} dir &&
+	verify_state dir/foo work head &&
+	verify_saved_state bar
+'
+
+test_expect_failure PERL 'git reset -p <blob>' '
+	set_state dir/foo work work &&
+	test_must_fail git reset -p HEAD:dir/foo
+'
+
 test_expect_success PERL 'git reset -p -- foo (inside dir)' '
 	set_state dir/foo work work
 	(echo y; echo n) | (cd dir && git reset -p -- foo) &&
 	verify_state dir/foo work head &&
 	verify_saved_state bar
-- 
1.7.2.3

[PATCH 2/4] reset: accept "git reset <tree> <path>"

From: Jonathan Nieder <hidden>
Date: 2016-06-15 22:49:49

The <rev> argument in

	git reset --hard <rev>
	git reset --soft <rev>

needs to be a commit to be sensible.  But in

	git reset <rev> -- <path> <path> ...

using other trees can be useful.

For example, to apply changes from a branch that has the current
branch merged as a subtree:

	git reset master:gitk-git -- .

Signed-off-by: Jonathan Nieder <redacted>
---
 builtin/reset.c  |   11 ++++++-----
 t/t7102-reset.sh |   31 +++++++++++++++++++++++++++++++
 2 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/builtin/reset.c b/builtin/reset.c
index a52e6f8..2375472 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -306,15 +306,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 	}
 
 	if (get_sha1(rev, sha1))
 		die("Failed to resolve '%s' as a valid ref.", rev);
 
-	commit = lookup_commit_reference(sha1);
-	if (!commit)
-		die("Could not parse object '%s'.", rev);
-	hashcpy(sha1, commit->object.sha1);
-
 	/* 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 (reset_type == MIXED)
@@ -323,10 +318,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 			die("Cannot do %s reset with paths.",
 					reset_type_names[reset_type]);
 		return read_from_tree(prefix, argv + i, sha1,
 				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
 	}
+
+	commit = lookup_commit_reference(sha1);
+	if (!commit)
+		die("Could not parse object '%s'.", rev);
+	hashcpy(sha1, commit->object.sha1);
+
 	if (reset_type == NONE)
 		reset_type = MIXED; /* by default */
 
 	if (reset_type != SOFT && reset_type != MIXED)
 		setup_work_tree();
diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh
index b8cf260..3e95da3 100755
--- a/t/t7102-reset.sh
+++ b/t/t7102-reset.sh
@@ -7,10 +7,18 @@ test_description='git reset
 
 Documented tests for git reset'
 
 . ./test-lib.sh
 
+test_exit_code () {
+	echo $1 >expect.code &&
+	shift &&
+	"$@"
+	echo $? >actual.code &&
+	test_cmp expect.code actual.code
+}
+
 test_expect_success 'creating initial files and commits' '
 	test_tick &&
 	echo "1st file" >first &&
 	git add first &&
 	git commit -m "create 1st file" &&
@@ -409,17 +417,40 @@ test_expect_success 'test resetting the index at give paths' '
 	test_must_fail git diff-index --cached --exit-code "$T" &&
 	test "$T" != "$U"
 
 '
 
+test_expect_success 'reset modified path from tree' '
+	echo hello >other &&
+	git reset --hard &&
+	git add other &&
+	T=$(git write-tree) &&
+	git rm -f other &&
+	test_exit_code 1 git reset $T other &&
+	git diff-index --cached --exit-code "$T"
+'
+
+test_expect_success 'try to reset from blob' '
+	git reset --hard &&
+	B=$(git rev-parse --verify HEAD:file1) &&
+	test_exit_code 128 git reset $B -- .
+'
+
 test_expect_success 'resetting an unmodified path is a no-op' '
 	git reset --hard &&
 	git reset -- file1 &&
 	git diff-files --exit-code &&
 	git diff-index --cached --exit-code HEAD
 '
 
+test_expect_success 'reset unmodified path from tree' '
+	git reset --hard &&
+	git reset HEAD^{tree} -- file1 &&
+	git diff-files --exit-code &&
+	git diff-index --cached --exit-code HEAD
+'
+
 cat > expect << EOF
 Unstaged changes after reset:
 M	file2
 EOF
 
-- 
1.7.2.3

[PATCH 3/4] reset: accept "git reset -- <path>" from unborn branch

From: Jonathan Nieder <hidden>
Date: 2016-06-15 22:49:49

A common workflow:

	git checkout <branch>
	... hack hack hack ...
	git add -- <path1> <path2>
	git reset -- <path1>; # oops, that one isn't ready yet
	git commit

One might try to use 'git reset' to undo the effect of 'git add' on an
unborn branch, too, but it doesn't work:

	... hack hack hack ...
	$ git add -- <path1> <path2>
	$ git reset -- <path1>
	fatal: Failed to resolve 'HEAD' as a valid ref.

It is obvious that the operator meant to remove the entry for <path1>,
so just do that.

This patch only affects the "git reset <path>" syntax; explicit
use of "git reset HEAD <path>" will still error out.

Suggested-by: Sverre Rabbelier <redacted>
Signed-off-by: Jonathan Nieder <redacted>
---
 builtin/reset.c         |   15 ++++++++++++-
 t/t7106-reset-unborn.sh |   50 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 63 insertions(+), 2 deletions(-)
 create mode 100755 t/t7106-reset-unborn.sh
diff --git a/builtin/reset.c b/builtin/reset.c
index 2375472..ff57764 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -234,12 +234,14 @@ 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 unborn_branch = 0;
 	int patch_mode = 0;
-	const char *rev = "HEAD";
+	const char *implicit_HEAD = "HEAD";
+	const char *rev = implicit_HEAD;
 	unsigned char sha1[20], *orig = NULL, sha1_orig[20],
 				*old_orig = NULL, sha1_old_orig[20];
 	struct commit *commit;
 	char *reflog_action, msg[1024];
 	const struct option options[] = {
@@ -303,11 +305,18 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		if (reset_type != NONE)
 			die("--patch is incompatible with --{hard,mixed,soft}");
 		return interactive_reset(rev, argv + i, prefix);
 	}
 
-	if (get_sha1(rev, sha1))
+	if (rev == implicit_HEAD) {
+		/* We may be on a branch yet to be born. */
+		resolve_ref("HEAD", sha1, 0, NULL);
+		if (is_null_sha1(sha1)) {
+			unborn_branch = 1;
+			hashcpy(sha1, (const unsigned char *) EMPTY_TREE_SHA1_BIN);
+		}
+	} else if (get_sha1(rev, sha1))
 		die("Failed to resolve '%s' as a valid ref.", rev);
 
 	/* git reset tree [--] paths... can be used to
 	 * load chosen paths from the tree into the index without
 	 * affecting the working tree nor HEAD. */
@@ -319,10 +328,12 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 					reset_type_names[reset_type]);
 		return read_from_tree(prefix, argv + i, sha1,
 				quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
 	}
 
+	if (unborn_branch)
+		die("Failed to resolve 'HEAD' as a valid ref.");
 	commit = lookup_commit_reference(sha1);
 	if (!commit)
 		die("Could not parse object '%s'.", rev);
 	hashcpy(sha1, commit->object.sha1);
 
diff --git a/t/t7106-reset-unborn.sh b/t/t7106-reset-unborn.sh
new file mode 100755
index 0000000..7baaffd
--- /dev/null
+++ b/t/t7106-reset-unborn.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+test_description='git reset from a branch yet to be born'
+. ./test-lib.sh
+
+>empty
+
+index_is_empty () {
+	git ls-files >actual &&
+	test_cmp empty actual
+}
+
+test_expect_success 'reset to remove file' '
+	echo one >file &&
+	git add file &&
+	git reset file &&
+	index_is_empty
+'
+
+test_expect_success 'reset after rm to remove file' '
+	echo one >file &&
+	git add file &&
+	rm file &&
+	git reset -- file &&
+	index_is_empty
+'
+
+test_expect_success 'reset file that does not match index' '
+	echo one >file &&
+	git add file &&
+	echo two >file &&
+	git reset -- file &&
+	index_is_empty
+'
+
+test_expect_success 'reset absent file' '
+	git reset -- file &&
+	index_is_empty
+'
+
+test_expect_success 'reset HEAD <files> from unborn branch' '
+	test_must_fail git reset HEAD -- .
+'
+
+test_expect_success 'reset HEAD from unborn branch' '
+	test_must_fail git reset HEAD &&
+	test_must_fail git reset HEAD --
+'
+
+test_done
-- 
1.7.2.3

[PATCH 4/4] reset: accept "git reset HEAD <path>" from unborn branch

From: Jonathan Nieder <hidden>
Date: 2016-06-15 22:49:49

If I try:

	... hack hack hack ...
	git add .
	rm <path1>; # bad file!
	git reset <path1>

git will respond by informing me that this use of <path1> is
ambiguous.  So I might try to disambiguate:

	git reset HEAD <path1>

This works when HEAD represents a branch, but not in the 'new
repository' ('branch yet to be born') case.

Even in the unborn branch case, it is clear what the operator meant
to do (namely, remove the entry for <path1>), so do that.

Signed-off-by: Jonathan Nieder <redacted>
---
 builtin/reset.c         |    5 ++---
 t/t7106-reset-unborn.sh |    5 ++++-
 2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/builtin/reset.c b/builtin/reset.c
index ff57764..d9a5702 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -236,12 +236,11 @@ 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 unborn_branch = 0;
 	int patch_mode = 0;
-	const char *implicit_HEAD = "HEAD";
-	const char *rev = implicit_HEAD;
+	const char *rev = "HEAD";
 	unsigned char sha1[20], *orig = NULL, sha1_orig[20],
 				*old_orig = NULL, sha1_old_orig[20];
 	struct commit *commit;
 	char *reflog_action, msg[1024];
 	const struct option options[] = {
@@ -305,11 +304,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		if (reset_type != NONE)
 			die("--patch is incompatible with --{hard,mixed,soft}");
 		return interactive_reset(rev, argv + i, prefix);
 	}
 
-	if (rev == implicit_HEAD) {
+	if (!strcmp(rev, "HEAD")) {
 		/* We may be on a branch yet to be born. */
 		resolve_ref("HEAD", sha1, 0, NULL);
 		if (is_null_sha1(sha1)) {
 			unborn_branch = 1;
 			hashcpy(sha1, (const unsigned char *) EMPTY_TREE_SHA1_BIN);
diff --git a/t/t7106-reset-unborn.sh b/t/t7106-reset-unborn.sh
index 7baaffd..c03f309 100755
--- a/t/t7106-reset-unborn.sh
+++ b/t/t7106-reset-unborn.sh
@@ -37,11 +37,14 @@ test_expect_success 'reset absent file' '
 	git reset -- file &&
 	index_is_empty
 '
 
 test_expect_success 'reset HEAD <files> from unborn branch' '
-	test_must_fail git reset HEAD -- .
+	echo one >file &&
+	git add file &&
+	git reset HEAD -- . &&
+	index_is_empty
 '
 
 test_expect_success 'reset HEAD from unborn branch' '
 	test_must_fail git reset HEAD &&
 	test_must_fail git reset HEAD --
-- 
1.7.2.3
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help