git@vger.kernel.org list mirror (unofficial, one of many)
 help / color / mirror / Atom feed
* [PATCH v12 00/26] Convert "git stash" to C builtin
       [not found] <https://public-inbox.org/git/cover.1542925164.git.ungureanupaulsebastian@gmail.com/>
@ 2018-12-20 19:44 ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 01/26] sha1-name.c: add `get_oidf()` which acts like `get_oid()` Paul-Sebastian Ungureanu
                     ` (26 more replies)
  0 siblings, 27 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

Hello,

This is a new iteration of git-stash which also takes
sd/stash-wo-user-name into account. I cherry-picked
some of dscho's commits (from [1]) to keep the scripted
version of `git stash` as `git-legacy-stash`.

Thank you for your suggestions!

Best,
Paul

[1]
https://github.com/dscho/git

Joel Teichroeb (5):
  stash: improve option parsing test coverage
  stash: convert apply to builtin
  stash: convert drop and clear to builtin
  stash: convert branch to builtin
  stash: convert pop to builtin

Johannes Schindelin (4):
  ident: add the ability to provide a "fallback identity"
  stash: add back the original, scripted `git stash`
  stash: optionally use the scripted version again
  tests: add a special setup where stash.useBuiltin is off

Paul-Sebastian Ungureanu (17):
  sha1-name.c: add `get_oidf()` which acts like `get_oid()`
  strbuf.c: add `strbuf_join_argv()`
  strbuf.c: add `strbuf_insertf()` and `strbuf_vinsertf()`
  t3903: modernize style
  stash: rename test cases to be more descriptive
  stash: add tests for `git stash show` config
  stash: mention options in `show` synopsis
  stash: convert list to builtin
  stash: convert show to builtin
  stash: convert store to builtin
  stash: convert create to builtin
  stash: convert push to builtin
  stash: make push -q quiet
  stash: convert save to builtin
  stash: optimize `get_untracked_files()` and `check_changes()`
  stash: replace all `write-tree` child processes with API calls
  stash: convert `stash--helper.c` into `stash.c`

 .gitignore                          |    1 +
 Documentation/git-stash.txt         |    4 +-
 Makefile                            |    3 +-
 builtin.h                           |    1 +
 builtin/stash.c                     | 1636 +++++++++++++++++++++++++++
 cache.h                             |    2 +
 git-stash.sh => git-legacy-stash.sh |   34 +-
 git-sh-setup.sh                     |    1 +
 git.c                               |    6 +
 ident.c                             |   20 +
 sha1-name.c                         |   19 +
 strbuf.c                            |   51 +
 strbuf.h                            |   16 +
 t/README                            |    4 +
 t/t3903-stash.sh                    |  192 ++--
 t/t3907-stash-show-config.sh        |   83 ++
 16 files changed, 2001 insertions(+), 72 deletions(-)
 create mode 100644 builtin/stash.c
 rename git-stash.sh => git-legacy-stash.sh (97%)
 create mode 100755 t/t3907-stash-show-config.sh

-- 
2.20.1.441.g764a526393


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

* [PATCH v12 01/26] sha1-name.c: add `get_oidf()` which acts like `get_oid()`
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 02/26] strbuf.c: add `strbuf_join_argv()` Paul-Sebastian Ungureanu
                     ` (25 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

Compared to `get_oid()`, `get_oidf()` has as parameters
a pointer to `object_id`, a printf format string and
additional arguments. This will help simplify the code
in subsequent commits.

Original-idea-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 cache.h     |  1 +
 sha1-name.c | 19 +++++++++++++++++++
 2 files changed, 20 insertions(+)

diff --git a/cache.h b/cache.h
index b7acd81d2e..28ccc97e10 100644
--- a/cache.h
+++ b/cache.h
@@ -1335,6 +1335,7 @@ struct object_context {
 	GET_OID_BLOB)
 
 extern int get_oid(const char *str, struct object_id *oid);
+extern int get_oidf(struct object_id *oid, const char *fmt, ...);
 extern int get_oid_commit(const char *str, struct object_id *oid);
 extern int get_oid_committish(const char *str, struct object_id *oid);
 extern int get_oid_tree(const char *str, struct object_id *oid);
diff --git a/sha1-name.c b/sha1-name.c
index b24502811b..9524a09525 100644
--- a/sha1-name.c
+++ b/sha1-name.c
@@ -1516,6 +1516,25 @@ int get_oid(const char *name, struct object_id *oid)
 	return get_oid_with_context(name, 0, oid, &unused);
 }
 
+/*
+ * This returns a non-zero value if the string (built using printf
+ * format and the given arguments) is not a valid object.
+ */
+int get_oidf(struct object_id *oid, const char *fmt, ...)
+{
+	va_list ap;
+	int ret;
+	struct strbuf sb = STRBUF_INIT;
+
+	va_start(ap, fmt);
+	strbuf_vaddf(&sb, fmt, ap);
+	va_end(ap);
+
+	ret = get_oid(sb.buf, oid);
+	strbuf_release(&sb);
+
+	return ret;
+}
 
 /*
  * Many callers know that the user meant to name a commit-ish by
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 02/26] strbuf.c: add `strbuf_join_argv()`
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 01/26] sha1-name.c: add `get_oidf()` which acts like `get_oid()` Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 03/26] strbuf.c: add `strbuf_insertf()` and `strbuf_vinsertf()` Paul-Sebastian Ungureanu
                     ` (24 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

Implement `strbuf_join_argv()` to join arguments
into a strbuf.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 strbuf.c | 15 +++++++++++++++
 strbuf.h |  7 +++++++
 2 files changed, 22 insertions(+)

diff --git a/strbuf.c b/strbuf.c
index f6a6cf78b9..82e90f1dfe 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -268,6 +268,21 @@ void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2)
 	strbuf_setlen(sb, sb->len + sb2->len);
 }
 
+const char *strbuf_join_argv(struct strbuf *buf,
+			     int argc, const char **argv, char delim)
+{
+	if (!argc)
+		return buf->buf;
+
+	strbuf_addstr(buf, *argv);
+	while (--argc) {
+		strbuf_addch(buf, delim);
+		strbuf_addstr(buf, *(++argv));
+	}
+
+	return buf->buf;
+}
+
 void strbuf_addchars(struct strbuf *sb, int c, size_t n)
 {
 	strbuf_grow(sb, n);
diff --git a/strbuf.h b/strbuf.h
index fc40873b65..be02150df3 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -288,6 +288,13 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s)
  */
 void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2);
 
+/**
+ * Join the arguments into a buffer. `delim` is put between every
+ * two arguments.
+ */
+const char *strbuf_join_argv(struct strbuf *buf, int argc,
+			     const char **argv, char delim);
+
 /**
  * This function can be used to expand a format string containing
  * placeholders. To that end, it parses the string and calls the specified
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 03/26] strbuf.c: add `strbuf_insertf()` and `strbuf_vinsertf()`
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 01/26] sha1-name.c: add `get_oidf()` which acts like `get_oid()` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 02/26] strbuf.c: add `strbuf_join_argv()` Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 04/26] ident: add the ability to provide a "fallback identity" Paul-Sebastian Ungureanu
                     ` (23 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

Implement `strbuf_insertf()` and `strbuf_vinsertf()` to
insert data using a printf format string.

Original-idea-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 strbuf.c | 36 ++++++++++++++++++++++++++++++++++++
 strbuf.h |  9 +++++++++
 2 files changed, 45 insertions(+)

diff --git a/strbuf.c b/strbuf.c
index 82e90f1dfe..bfbbdadbf3 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -249,6 +249,42 @@ void strbuf_insert(struct strbuf *sb, size_t pos, const void *data, size_t len)
 	strbuf_splice(sb, pos, 0, data, len);
 }
 
+void strbuf_vinsertf(struct strbuf *sb, size_t pos, const char *fmt, va_list ap)
+{
+	int len, len2;
+	char save;
+	va_list cp;
+
+	if (pos > sb->len)
+		die("`pos' is too far after the end of the buffer");
+	va_copy(cp, ap);
+	len = vsnprintf(sb->buf + sb->len, 0, fmt, cp);
+	va_end(cp);
+	if (len < 0)
+		BUG("your vsnprintf is broken (returned %d)", len);
+	if (!len)
+		return; /* nothing to do */
+	if (unsigned_add_overflows(sb->len, len))
+		die("you want to use way too much memory");
+	strbuf_grow(sb, len);
+	memmove(sb->buf + pos + len, sb->buf + pos, sb->len - pos);
+	/* vsnprintf() will append a NUL, overwriting one of our characters */
+	save = sb->buf[pos + len];
+	len2 = vsnprintf(sb->buf + pos, sb->alloc - sb->len, fmt, ap);
+	sb->buf[pos + len] = save;
+	if (len2 != len)
+		BUG("your vsnprintf is broken (returns inconsistent lengths)");
+	strbuf_setlen(sb, sb->len + len);
+}
+
+void strbuf_insertf(struct strbuf *sb, size_t pos, const char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+	strbuf_vinsertf(sb, pos, fmt, ap);
+	va_end(ap);
+}
+
 void strbuf_remove(struct strbuf *sb, size_t pos, size_t len)
 {
 	strbuf_splice(sb, pos, len, "", 0);
diff --git a/strbuf.h b/strbuf.h
index be02150df3..8f8fe01e68 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -244,6 +244,15 @@ void strbuf_addchars(struct strbuf *sb, int c, size_t n);
  */
 void strbuf_insert(struct strbuf *sb, size_t pos, const void *, size_t);
 
+/**
+ * Insert data to the given position of the buffer giving a printf format
+ * string. The contents will be shifted, not overwritten.
+ */
+void strbuf_vinsertf(struct strbuf *sb, size_t pos, const char *fmt,
+		     va_list ap);
+
+void strbuf_insertf(struct strbuf *sb, size_t pos, const char *fmt, ...);
+
 /**
  * Remove given amount of data from a given position of the buffer.
  */
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 04/26] ident: add the ability to provide a "fallback identity"
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (2 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 03/26] strbuf.c: add `strbuf_insertf()` and `strbuf_vinsertf()` Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-26 21:21     ` Junio C Hamano
  2018-12-20 19:44   ` [PATCH v12 05/26] stash: improve option parsing test coverage Paul-Sebastian Ungureanu
                     ` (22 subsequent siblings)
  26 siblings, 1 reply; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

In 3bc2111fc2e9 (stash: tolerate missing user identity, 2018-11-18),
`git stash` learned to provide a fallback identity for the case that no
proper name/email was given (and `git stash` does not really care about
a correct identity anyway, but it does want to create a commit object).

In preparation for the same functionality in the upcoming built-in
version of `git stash`, let's offer the same functionality as an API
function.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 cache.h |  1 +
 ident.c | 20 ++++++++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/cache.h b/cache.h
index 28ccc97e10..dd2154bcd3 100644
--- a/cache.h
+++ b/cache.h
@@ -1493,6 +1493,7 @@ extern const char *git_sequence_editor(void);
 extern const char *git_pager(int stdout_is_tty);
 extern int is_terminal_dumb(void);
 extern int git_ident_config(const char *, const char *, void *);
+void prepare_fallback_ident(const char *name, const char *email);
 extern void reset_ident_date(void);
 
 struct ident_split {
diff --git a/ident.c b/ident.c
index 33bcf40644..bce20e8652 100644
--- a/ident.c
+++ b/ident.c
@@ -505,6 +505,26 @@ int git_ident_config(const char *var, const char *value, void *data)
 	return 0;
 }
 
+static void set_env_if(const char *key, const char *value, int *given, int bit)
+{
+	if ((*given & bit) || getenv(key))
+		return; /* nothing to do */
+	setenv(key, value, 0);
+	*given |= bit;
+}
+
+void prepare_fallback_ident(const char *name, const char *email)
+{
+	set_env_if("GIT_AUTHOR_NAME", name,
+		   &author_ident_explicitly_given, IDENT_NAME_GIVEN);
+	set_env_if("GIT_AUTHOR_EMAIL", email,
+		   &author_ident_explicitly_given, IDENT_MAIL_GIVEN);
+	set_env_if("GIT_COMMITTER_NAME", name,
+		   &committer_ident_explicitly_given, IDENT_NAME_GIVEN);
+	set_env_if("GIT_COMMITTER_EMAIL", email,
+		   &committer_ident_explicitly_given, IDENT_MAIL_GIVEN);
+}
+
 static int buf_cmp(const char *a_begin, const char *a_end,
 		   const char *b_begin, const char *b_end)
 {
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 05/26] stash: improve option parsing test coverage
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (3 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 04/26] ident: add the ability to provide a "fallback identity" Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 06/26] t3903: modernize style Paul-Sebastian Ungureanu
                     ` (21 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

From: Joel Teichroeb <joel@teichroeb.net>

In preparation for converting the stash command incrementally to
a builtin command, this patch improves test coverage of the option
parsing. Both for having too many parameters, or too few.

Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 t/t3903-stash.sh | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 5f8272b6f9..ac55629737 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -444,6 +444,36 @@ test_expect_failure 'stash file to directory' '
 	test foo = "$(cat file/file)"
 '
 
+test_expect_success 'giving too many ref arguments does not modify files' '
+	git stash clear &&
+	test_when_finished "git reset --hard HEAD" &&
+	echo foo >file2 &&
+	git stash &&
+	echo bar >file2 &&
+	git stash &&
+	test-tool chmtime =123456789 file2 &&
+	for type in apply pop "branch stash-branch"
+	do
+		test_must_fail git stash $type stash@{0} stash@{1} 2>err &&
+		test_i18ngrep "Too many revisions" err &&
+		test 123456789 = $(test-tool chmtime -g file2) || return 1
+	done
+'
+
+test_expect_success 'drop: too many arguments errors out (does nothing)' '
+	git stash list >expect &&
+	test_must_fail git stash drop stash@{0} stash@{1} 2>err &&
+	test_i18ngrep "Too many revisions" err &&
+	git stash list >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'show: too many arguments errors out (does nothing)' '
+	test_must_fail git stash show stash@{0} stash@{1} 2>err 1>out &&
+	test_i18ngrep "Too many revisions" err &&
+	test_must_be_empty out
+'
+
 test_expect_success 'stash create - no changes' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
@@ -479,6 +509,11 @@ test_expect_success 'stash branch - stashes on stack, stash-like argument' '
 	test $(git ls-files --modified | wc -l) -eq 1
 '
 
+test_expect_success 'stash branch complains with no arguments' '
+	test_must_fail git stash branch 2>err &&
+	test_i18ngrep "No branch name specified" err
+'
+
 test_expect_success 'stash show format defaults to --stat' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 06/26] t3903: modernize style
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (4 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 05/26] stash: improve option parsing test coverage Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 07/26] stash: rename test cases to be more descriptive Paul-Sebastian Ungureanu
                     ` (20 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

Remove whitespaces after redirection operators and wrap
long lines.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 t/t3903-stash.sh | 120 ++++++++++++++++++++++++-----------------------
 1 file changed, 61 insertions(+), 59 deletions(-)

diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index ac55629737..4e83facf23 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -8,22 +8,22 @@ test_description='Test git stash'
 . ./test-lib.sh
 
 test_expect_success 'stash some dirty working directory' '
-	echo 1 > file &&
+	echo 1 >file &&
 	git add file &&
 	echo unrelated >other-file &&
 	git add other-file &&
 	test_tick &&
 	git commit -m initial &&
-	echo 2 > file &&
+	echo 2 >file &&
 	git add file &&
-	echo 3 > file &&
+	echo 3 >file &&
 	test_tick &&
 	git stash &&
 	git diff-files --quiet &&
 	git diff-index --cached --quiet HEAD
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 diff --git a/file b/file
 index 0cfbf08..00750ed 100644
 --- a/file
@@ -35,7 +35,7 @@ EOF
 
 test_expect_success 'parents of stash' '
 	test $(git rev-parse stash^) = $(git rev-parse HEAD) &&
-	git diff stash^2..stash > output &&
+	git diff stash^2..stash >output &&
 	test_cmp expect output
 '
 
@@ -74,7 +74,7 @@ test_expect_success 'apply stashed changes' '
 
 test_expect_success 'apply stashed changes (including index)' '
 	git reset --hard HEAD^ &&
-	echo 6 > other-file &&
+	echo 6 >other-file &&
 	git add other-file &&
 	test_tick &&
 	git commit -m other-file &&
@@ -99,12 +99,12 @@ test_expect_success 'stash drop complains of extra options' '
 
 test_expect_success 'drop top stash' '
 	git reset --hard &&
-	git stash list > stashlist1 &&
-	echo 7 > file &&
+	git stash list >expected &&
+	echo 7 >file &&
 	git stash &&
 	git stash drop &&
-	git stash list > stashlist2 &&
-	test_cmp stashlist1 stashlist2 &&
+	git stash list >actual &&
+	test_cmp expected actual &&
 	git stash apply &&
 	test 3 = $(cat file) &&
 	test 1 = $(git show :file) &&
@@ -113,9 +113,9 @@ test_expect_success 'drop top stash' '
 
 test_expect_success 'drop middle stash' '
 	git reset --hard &&
-	echo 8 > file &&
+	echo 8 >file &&
 	git stash &&
-	echo 9 > file &&
+	echo 9 >file &&
 	git stash &&
 	git stash drop stash@{1} &&
 	test 2 = $(git stash list | wc -l) &&
@@ -160,7 +160,7 @@ test_expect_success 'stash pop' '
 	test 0 = $(git stash list | wc -l)
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 diff --git a/file2 b/file2
 new file mode 100644
 index 0000000..1fe912c
@@ -170,7 +170,7 @@ index 0000000..1fe912c
 +bar2
 EOF
 
-cat > expect1 << EOF
+cat >expect1 <<EOF
 diff --git a/file b/file
 index 257cc56..5716ca5 100644
 --- a/file
@@ -180,7 +180,7 @@ index 257cc56..5716ca5 100644
 +bar
 EOF
 
-cat > expect2 << EOF
+cat >expect2 <<EOF
 diff --git a/file b/file
 index 7601807..5716ca5 100644
 --- a/file
@@ -198,79 +198,79 @@ index 0000000..1fe912c
 EOF
 
 test_expect_success 'stash branch' '
-	echo foo > file &&
+	echo foo >file &&
 	git commit file -m first &&
-	echo bar > file &&
-	echo bar2 > file2 &&
+	echo bar >file &&
+	echo bar2 >file2 &&
 	git add file2 &&
 	git stash &&
-	echo baz > file &&
+	echo baz >file &&
 	git commit file -m second &&
 	git stash branch stashbranch &&
 	test refs/heads/stashbranch = $(git symbolic-ref HEAD) &&
 	test $(git rev-parse HEAD) = $(git rev-parse master^) &&
-	git diff --cached > output &&
+	git diff --cached >output &&
 	test_cmp expect output &&
-	git diff > output &&
+	git diff >output &&
 	test_cmp expect1 output &&
 	git add file &&
 	git commit -m alternate\ second &&
-	git diff master..stashbranch > output &&
+	git diff master..stashbranch >output &&
 	test_cmp output expect2 &&
 	test 0 = $(git stash list | wc -l)
 '
 
 test_expect_success 'apply -q is quiet' '
-	echo foo > file &&
+	echo foo >file &&
 	git stash &&
-	git stash apply -q > output.out 2>&1 &&
+	git stash apply -q >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
 test_expect_success 'save -q is quiet' '
-	git stash save --quiet > output.out 2>&1 &&
+	git stash save --quiet >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
 test_expect_success 'pop -q is quiet' '
-	git stash pop -q > output.out 2>&1 &&
+	git stash pop -q >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
 test_expect_success 'pop -q --index works and is quiet' '
-	echo foo > file &&
+	echo foo >file &&
 	git add file &&
 	git stash save --quiet &&
-	git stash pop -q --index > output.out 2>&1 &&
+	git stash pop -q --index >output.out 2>&1 &&
 	test foo = "$(git show :file)" &&
 	test_must_be_empty output.out
 '
 
 test_expect_success 'drop -q is quiet' '
 	git stash &&
-	git stash drop -q > output.out 2>&1 &&
+	git stash drop -q >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
 test_expect_success 'stash -k' '
-	echo bar3 > file &&
-	echo bar4 > file2 &&
+	echo bar3 >file &&
+	echo bar4 >file2 &&
 	git add file2 &&
 	git stash -k &&
 	test bar,bar4 = $(cat file),$(cat file2)
 '
 
 test_expect_success 'stash --no-keep-index' '
-	echo bar33 > file &&
-	echo bar44 > file2 &&
+	echo bar33 >file &&
+	echo bar44 >file2 &&
 	git add file2 &&
 	git stash --no-keep-index &&
 	test bar,bar2 = $(cat file),$(cat file2)
 '
 
 test_expect_success 'stash --invalid-option' '
-	echo bar5 > file &&
-	echo bar6 > file2 &&
+	echo bar5 >file &&
+	echo bar6 >file2 &&
 	git add file2 &&
 	test_must_fail git stash --invalid-option &&
 	test_must_fail git stash save --invalid-option &&
@@ -486,11 +486,12 @@ test_expect_success 'stash branch - no stashes on stack, stash-like argument' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
 	git reset --hard &&
-	echo foo >> file &&
+	echo foo >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
 	git stash branch stash-branch ${STASH_ID} &&
-	test_when_finished "git reset --hard HEAD && git checkout master && git branch -D stash-branch" &&
+	test_when_finished "git reset --hard HEAD && git checkout master &&
+	git branch -D stash-branch" &&
 	test $(git ls-files --modified | wc -l) -eq 1
 '
 
@@ -498,14 +499,15 @@ test_expect_success 'stash branch - stashes on stack, stash-like argument' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
 	git reset --hard &&
-	echo foo >> file &&
+	echo foo >>file &&
 	git stash &&
 	test_when_finished "git stash drop" &&
-	echo bar >> file &&
+	echo bar >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
 	git stash branch stash-branch ${STASH_ID} &&
-	test_when_finished "git reset --hard HEAD && git checkout master && git branch -D stash-branch" &&
+	test_when_finished "git reset --hard HEAD && git checkout master &&
+	git branch -D stash-branch" &&
 	test $(git ls-files --modified | wc -l) -eq 1
 '
 
@@ -518,10 +520,10 @@ test_expect_success 'stash show format defaults to --stat' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
 	git reset --hard &&
-	echo foo >> file &&
+	echo foo >>file &&
 	git stash &&
 	test_when_finished "git stash drop" &&
-	echo bar >> file &&
+	echo bar >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
 	cat >expected <<-EOF &&
@@ -536,10 +538,10 @@ test_expect_success 'stash show - stashes on stack, stash-like argument' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
 	git reset --hard &&
-	echo foo >> file &&
+	echo foo >>file &&
 	git stash &&
 	test_when_finished "git stash drop" &&
-	echo bar >> file &&
+	echo bar >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
 	echo "1	0	file" >expected &&
@@ -551,10 +553,10 @@ test_expect_success 'stash show -p - stashes on stack, stash-like argument' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
 	git reset --hard &&
-	echo foo >> file &&
+	echo foo >>file &&
 	git stash &&
 	test_when_finished "git stash drop" &&
-	echo bar >> file &&
+	echo bar >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
 	cat >expected <<-EOF &&
@@ -574,7 +576,7 @@ test_expect_success 'stash show - no stashes on stack, stash-like argument' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
 	git reset --hard &&
-	echo foo >> file &&
+	echo foo >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
 	echo "1	0	file" >expected &&
@@ -586,7 +588,7 @@ test_expect_success 'stash show -p - no stashes on stack, stash-like argument' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
 	git reset --hard &&
-	echo foo >> file &&
+	echo foo >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
 	cat >expected <<-EOF &&
@@ -606,9 +608,9 @@ test_expect_success 'stash drop - fail early if specified stash is not a stash r
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD && git stash clear" &&
 	git reset --hard &&
-	echo foo > file &&
+	echo foo >file &&
 	git stash &&
-	echo bar > file &&
+	echo bar >file &&
 	git stash &&
 	test_must_fail git stash drop $(git rev-parse stash@{0}) &&
 	git stash pop &&
@@ -620,9 +622,9 @@ test_expect_success 'stash pop - fail early if specified stash is not a stash re
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD && git stash clear" &&
 	git reset --hard &&
-	echo foo > file &&
+	echo foo >file &&
 	git stash &&
-	echo bar > file &&
+	echo bar >file &&
 	git stash &&
 	test_must_fail git stash pop $(git rev-parse stash@{0}) &&
 	git stash pop &&
@@ -632,8 +634,8 @@ test_expect_success 'stash pop - fail early if specified stash is not a stash re
 
 test_expect_success 'ref with non-existent reflog' '
 	git stash clear &&
-	echo bar5 > file &&
-	echo bar6 > file2 &&
+	echo bar5 >file &&
+	echo bar6 >file2 &&
 	git add file2 &&
 	git stash &&
 	test_must_fail git rev-parse --quiet --verify does-not-exist &&
@@ -653,8 +655,8 @@ test_expect_success 'ref with non-existent reflog' '
 test_expect_success 'invalid ref of the form stash@{n}, n >= N' '
 	git stash clear &&
 	test_must_fail git stash drop stash@{0} &&
-	echo bar5 > file &&
-	echo bar6 > file2 &&
+	echo bar5 >file &&
+	echo bar6 >file2 &&
 	git add file2 &&
 	git stash &&
 	test_must_fail git stash drop stash@{1} &&
@@ -724,7 +726,7 @@ test_expect_success 'stash apply shows status same as git status (relative to cu
 	test_i18ncmp expect actual
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 diff --git a/HEAD b/HEAD
 new file mode 100644
 index 0000000..fe0cbee
@@ -737,14 +739,14 @@ EOF
 test_expect_success 'stash where working directory contains "HEAD" file' '
 	git stash clear &&
 	git reset --hard &&
-	echo file-not-a-ref > HEAD &&
+	echo file-not-a-ref >HEAD &&
 	git add HEAD &&
 	test_tick &&
 	git stash &&
 	git diff-files --quiet &&
 	git diff-index --cached --quiet HEAD &&
 	test "$(git rev-parse stash^)" = "$(git rev-parse HEAD)" &&
-	git diff stash^..stash > output &&
+	git diff stash^..stash >output &&
 	test_cmp expect output
 '
 
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 07/26] stash: rename test cases to be more descriptive
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (5 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 06/26] t3903: modernize style Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 08/26] stash: add tests for `git stash show` config Paul-Sebastian Ungureanu
                     ` (19 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

Rename some test cases' labels to be more descriptive and under 80
characters per line.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 t/t3903-stash.sh | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 4e83facf23..98c25a671c 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -604,7 +604,7 @@ test_expect_success 'stash show -p - no stashes on stack, stash-like argument' '
 	test_cmp expected actual
 '
 
-test_expect_success 'stash drop - fail early if specified stash is not a stash reference' '
+test_expect_success 'drop: fail early if specified stash is not a stash ref' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD && git stash clear" &&
 	git reset --hard &&
@@ -618,7 +618,7 @@ test_expect_success 'stash drop - fail early if specified stash is not a stash r
 	git reset --hard HEAD
 '
 
-test_expect_success 'stash pop - fail early if specified stash is not a stash reference' '
+test_expect_success 'pop: fail early if specified stash is not a stash ref' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD && git stash clear" &&
 	git reset --hard &&
@@ -682,7 +682,7 @@ test_expect_success 'invalid ref of the form "n", n >= N' '
 	git stash drop
 '
 
-test_expect_success 'stash branch should not drop the stash if the branch exists' '
+test_expect_success 'branch: do not drop the stash if the branch exists' '
 	git stash clear &&
 	echo foo >file &&
 	git add file &&
@@ -693,7 +693,7 @@ test_expect_success 'stash branch should not drop the stash if the branch exists
 	git rev-parse stash@{0} --
 '
 
-test_expect_success 'stash branch should not drop the stash if the apply fails' '
+test_expect_success 'branch: should not drop the stash if the apply fails' '
 	git stash clear &&
 	git reset HEAD~1 --hard &&
 	echo foo >file &&
@@ -707,7 +707,7 @@ test_expect_success 'stash branch should not drop the stash if the apply fails'
 	git rev-parse stash@{0} --
 '
 
-test_expect_success 'stash apply shows status same as git status (relative to current directory)' '
+test_expect_success 'apply: show same status as git status (relative to ./)' '
 	git stash clear &&
 	echo 1 >subdir/subfile1 &&
 	echo 2 >subdir/subfile2 &&
@@ -1048,7 +1048,7 @@ test_expect_success 'stash push -p with pathspec shows no changes only once' '
 	test_i18ncmp expect actual
 '
 
-test_expect_success 'stash push with pathspec shows no changes when there are none' '
+test_expect_success 'push <pathspec>: show no changes when there are none' '
 	>foo &&
 	git add foo &&
 	git commit -m "tmp" &&
@@ -1058,7 +1058,7 @@ test_expect_success 'stash push with pathspec shows no changes when there are no
 	test_i18ncmp expect actual
 '
 
-test_expect_success 'stash push with pathspec not in the repository errors out' '
+test_expect_success 'push: <pathspec> not in the repository errors out' '
 	>untracked &&
 	test_must_fail git stash push untracked &&
 	test_path_is_file untracked
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 08/26] stash: add tests for `git stash show` config
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (6 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 07/26] stash: rename test cases to be more descriptive Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 09/26] stash: mention options in `show` synopsis Paul-Sebastian Ungureanu
                     ` (18 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

This commit introduces tests for `git stash show`
config. It tests all the cases where `stash.showStat`
and `stash.showPatch` are unset or set to true / false.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 t/t3907-stash-show-config.sh | 83 ++++++++++++++++++++++++++++++++++++
 1 file changed, 83 insertions(+)
 create mode 100755 t/t3907-stash-show-config.sh

diff --git a/t/t3907-stash-show-config.sh b/t/t3907-stash-show-config.sh
new file mode 100755
index 0000000000..10914bba7b
--- /dev/null
+++ b/t/t3907-stash-show-config.sh
@@ -0,0 +1,83 @@
+#!/bin/sh
+
+test_description='Test git stash show configuration.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit file
+'
+
+# takes three parameters:
+# 1. the stash.showStat value (or "<unset>")
+# 2. the stash.showPatch value (or "<unset>")
+# 3. the diff options of the expected output (or nothing for no output)
+test_stat_and_patch () {
+	if test "<unset>" = "$1"
+	then
+		test_unconfig stash.showStat
+	else
+		test_config stash.showStat "$1"
+	fi &&
+
+	if test "<unset>" = "$2"
+	then
+		test_unconfig stash.showPatch
+	else
+		test_config stash.showPatch "$2"
+	fi &&
+
+	shift 2 &&
+	echo 2 >file.t &&
+	if test $# != 0
+	then
+		git diff "$@" >expect
+	fi &&
+	git stash &&
+	git stash show >actual &&
+
+	if test $# = 0
+	then
+		test_must_be_empty actual
+	else
+		test_cmp expect actual
+	fi
+}
+
+test_expect_success 'showStat unset showPatch unset' '
+	test_stat_and_patch "<unset>" "<unset>" --stat
+'
+
+test_expect_success 'showStat unset showPatch false' '
+	test_stat_and_patch "<unset>" false --stat
+'
+
+test_expect_success 'showStat unset showPatch true' '
+	test_stat_and_patch "<unset>" true --stat -p
+'
+
+test_expect_success 'showStat false showPatch unset' '
+	test_stat_and_patch false "<unset>"
+'
+
+test_expect_success 'showStat false showPatch false' '
+	test_stat_and_patch false false
+'
+
+test_expect_success 'showStat false showPatch true' '
+	test_stat_and_patch false true -p
+'
+
+test_expect_success 'showStat true showPatch unset' '
+	test_stat_and_patch true "<unset>" --stat
+'
+
+test_expect_success 'showStat true showPatch false' '
+	test_stat_and_patch true false --stat
+'
+
+test_expect_success 'showStat true showPatch true' '
+	test_stat_and_patch true true --stat -p
+'
+
+test_done
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 09/26] stash: mention options in `show` synopsis
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (7 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 08/26] stash: add tests for `git stash show` config Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 10/26] stash: convert apply to builtin Paul-Sebastian Ungureanu
                     ` (17 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

Mention in the documentation, that `show` accepts any
option known to `git diff`.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 Documentation/git-stash.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 7ef8c47911..e31ea7d303 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git stash' list [<options>]
-'git stash' show [<stash>]
+'git stash' show [<options>] [<stash>]
 'git stash' drop [-q|--quiet] [<stash>]
 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
 'git stash' branch <branchname> [<stash>]
@@ -106,7 +106,7 @@ stash@{1}: On master: 9cc0589... Add git-stash
 The command takes options applicable to the 'git log'
 command to control what is shown and how. See linkgit:git-log[1].
 
-show [<stash>]::
+show [<options>] [<stash>]::
 
 	Show the changes recorded in the stash entry as a diff between the
 	stashed contents and the commit back when the stash entry was first
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 10/26] stash: convert apply to builtin
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (8 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 09/26] stash: mention options in `show` synopsis Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 11/26] stash: convert drop and clear " Paul-Sebastian Ungureanu
                     ` (16 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

From: Joel Teichroeb <joel@teichroeb.net>

Add a builtin helper for performing stash commands. Converting
all at once proved hard to review, so starting with just apply
lets conversion get started without the other commands being
finished.

The helper is being implemented as a drop in replacement for
stash so that when it is complete it can simply be renamed and
the shell script deleted.

Delete the contents of the apply_stash shell function and replace
it with a call to stash--helper apply until pop is also
converted.

Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 .gitignore              |   1 +
 Makefile                |   1 +
 builtin.h               |   1 +
 builtin/stash--helper.c | 452 ++++++++++++++++++++++++++++++++++++++++
 git-stash.sh            |  78 +------
 git.c                   |   1 +
 6 files changed, 463 insertions(+), 71 deletions(-)
 create mode 100644 builtin/stash--helper.c

diff --git a/.gitignore b/.gitignore
index 0d77ea5894..6ecab90ab2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -162,6 +162,7 @@
 /git-show-ref
 /git-stage
 /git-stash
+/git-stash--helper
 /git-status
 /git-stripspace
 /git-submodule
diff --git a/Makefile b/Makefile
index 1a44c811aa..c246fc7078 100644
--- a/Makefile
+++ b/Makefile
@@ -1117,6 +1117,7 @@ BUILTIN_OBJS += builtin/shortlog.o
 BUILTIN_OBJS += builtin/show-branch.o
 BUILTIN_OBJS += builtin/show-index.o
 BUILTIN_OBJS += builtin/show-ref.o
+BUILTIN_OBJS += builtin/stash--helper.o
 BUILTIN_OBJS += builtin/stripspace.o
 BUILTIN_OBJS += builtin/submodule--helper.o
 BUILTIN_OBJS += builtin/symbolic-ref.o
diff --git a/builtin.h b/builtin.h
index 6538932e99..ff4460aff7 100644
--- a/builtin.h
+++ b/builtin.h
@@ -225,6 +225,7 @@ extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_show_index(int argc, const char **argv, const char *prefix);
 extern int cmd_status(int argc, const char **argv, const char *prefix);
+extern int cmd_stash__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
new file mode 100644
index 0000000000..997b1c0ecf
--- /dev/null
+++ b/builtin/stash--helper.c
@@ -0,0 +1,452 @@
+#include "builtin.h"
+#include "config.h"
+#include "parse-options.h"
+#include "refs.h"
+#include "lockfile.h"
+#include "cache-tree.h"
+#include "unpack-trees.h"
+#include "merge-recursive.h"
+#include "argv-array.h"
+#include "run-command.h"
+#include "dir.h"
+#include "rerere.h"
+
+static const char * const git_stash_helper_usage[] = {
+	N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
+	NULL
+};
+
+static const char * const git_stash_helper_apply_usage[] = {
+	N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
+	NULL
+};
+
+static const char *ref_stash = "refs/stash";
+static struct strbuf stash_index_path = STRBUF_INIT;
+
+/*
+ * w_commit is set to the commit containing the working tree
+ * b_commit is set to the base commit
+ * i_commit is set to the commit containing the index tree
+ * u_commit is set to the commit containing the untracked files tree
+ * w_tree is set to the working tree
+ * b_tree is set to the base tree
+ * i_tree is set to the index tree
+ * u_tree is set to the untracked files tree
+ */
+
+struct stash_info {
+	struct object_id w_commit;
+	struct object_id b_commit;
+	struct object_id i_commit;
+	struct object_id u_commit;
+	struct object_id w_tree;
+	struct object_id b_tree;
+	struct object_id i_tree;
+	struct object_id u_tree;
+	struct strbuf revision;
+	int is_stash_ref;
+	int has_u;
+};
+
+static void free_stash_info(struct stash_info *info)
+{
+	strbuf_release(&info->revision);
+}
+
+static void assert_stash_like(struct stash_info *info, const char *revision)
+{
+	if (get_oidf(&info->b_commit, "%s^1", revision) ||
+	    get_oidf(&info->w_tree, "%s:", revision) ||
+	    get_oidf(&info->b_tree, "%s^1:", revision) ||
+	    get_oidf(&info->i_tree, "%s^2:", revision))
+		die(_("'%s' is not a stash-like commit"), revision);
+}
+
+static int get_stash_info(struct stash_info *info, int argc, const char **argv)
+{
+	int ret;
+	char *end_of_rev;
+	char *expanded_ref;
+	const char *revision;
+	const char *commit = NULL;
+	struct object_id dummy;
+	struct strbuf symbolic = STRBUF_INIT;
+
+	if (argc > 1) {
+		int i;
+		struct strbuf refs_msg = STRBUF_INIT;
+
+		for (i = 0; i < argc; i++)
+			strbuf_addf(&refs_msg, " '%s'", argv[i]);
+
+		fprintf_ln(stderr, _("Too many revisions specified:%s"),
+			   refs_msg.buf);
+		strbuf_release(&refs_msg);
+
+		return -1;
+	}
+
+	if (argc == 1)
+		commit = argv[0];
+
+	strbuf_init(&info->revision, 0);
+	if (!commit) {
+		if (!ref_exists(ref_stash)) {
+			free_stash_info(info);
+			fprintf_ln(stderr, _("No stash entries found."));
+			return -1;
+		}
+
+		strbuf_addf(&info->revision, "%s@{0}", ref_stash);
+	} else if (strspn(commit, "0123456789") == strlen(commit)) {
+		strbuf_addf(&info->revision, "%s@{%s}", ref_stash, commit);
+	} else {
+		strbuf_addstr(&info->revision, commit);
+	}
+
+	revision = info->revision.buf;
+
+	if (get_oid(revision, &info->w_commit)) {
+		error(_("%s is not a valid reference"), revision);
+		free_stash_info(info);
+		return -1;
+	}
+
+	assert_stash_like(info, revision);
+
+	info->has_u = !get_oidf(&info->u_tree, "%s^3:", revision);
+
+	end_of_rev = strchrnul(revision, '@');
+	strbuf_add(&symbolic, revision, end_of_rev - revision);
+
+	ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref);
+	strbuf_release(&symbolic);
+	switch (ret) {
+	case 0: /* Not found, but valid ref */
+		info->is_stash_ref = 0;
+		break;
+	case 1:
+		info->is_stash_ref = !strcmp(expanded_ref, ref_stash);
+		break;
+	default: /* Invalid or ambiguous */
+		free_stash_info(info);
+	}
+
+	free(expanded_ref);
+	return !(ret == 0 || ret == 1);
+}
+
+static int reset_tree(struct object_id *i_tree, int update, int reset)
+{
+	int nr_trees = 1;
+	struct unpack_trees_options opts;
+	struct tree_desc t[MAX_UNPACK_TREES];
+	struct tree *tree;
+	struct lock_file lock_file = LOCK_INIT;
+
+	read_cache_preload(NULL);
+	if (refresh_cache(REFRESH_QUIET))
+		return -1;
+
+	hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
+
+	memset(&opts, 0, sizeof(opts));
+
+	tree = parse_tree_indirect(i_tree);
+	if (parse_tree(tree))
+		return -1;
+
+	init_tree_desc(t, tree->buffer, tree->size);
+
+	opts.head_idx = 1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.merge = 1;
+	opts.reset = reset;
+	opts.update = update;
+	opts.fn = oneway_merge;
+
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+
+	if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
+		return error(_("unable to write new index file"));
+
+	return 0;
+}
+
+static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	const char *w_commit_hex = oid_to_hex(w_commit);
+
+	/*
+	 * Diff-tree would not be very hard to replace with a native function,
+	 * however it should be done together with apply_cached.
+	 */
+	cp.git_cmd = 1;
+	argv_array_pushl(&cp.args, "diff-tree", "--binary", NULL);
+	argv_array_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex);
+
+	return pipe_command(&cp, NULL, 0, out, 0, NULL, 0);
+}
+
+static int apply_cached(struct strbuf *out)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+
+	/*
+	 * Apply currently only reads either from stdin or a file, thus
+	 * apply_all_patches would have to be updated to optionally take a
+	 * buffer.
+	 */
+	cp.git_cmd = 1;
+	argv_array_pushl(&cp.args, "apply", "--cached", NULL);
+	return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0);
+}
+
+static int reset_head(void)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+
+	/*
+	 * Reset is overall quite simple, however there is no current public
+	 * API for resetting.
+	 */
+	cp.git_cmd = 1;
+	argv_array_push(&cp.args, "reset");
+
+	return run_command(&cp);
+}
+
+static int get_newly_staged(struct strbuf *out, struct object_id *c_tree)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	const char *c_tree_hex = oid_to_hex(c_tree);
+
+	/*
+	 * diff-index is very similar to diff-tree above, and should be
+	 * converted together with update_index.
+	 */
+	cp.git_cmd = 1;
+	argv_array_pushl(&cp.args, "diff-index", "--cached", "--name-only",
+			 "--diff-filter=A", NULL);
+	argv_array_push(&cp.args, c_tree_hex);
+	return pipe_command(&cp, NULL, 0, out, 0, NULL, 0);
+}
+
+static int update_index(struct strbuf *out)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+
+	/*
+	 * Update-index is very complicated and may need to have a public
+	 * function exposed in order to remove this forking.
+	 */
+	cp.git_cmd = 1;
+	argv_array_pushl(&cp.args, "update-index", "--add", "--stdin", NULL);
+	return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0);
+}
+
+static int restore_untracked(struct object_id *u_tree)
+{
+	int res;
+	struct child_process cp = CHILD_PROCESS_INIT;
+
+	/*
+	 * We need to run restore files from a given index, but without
+	 * affecting the current index, so we use GIT_INDEX_FILE with
+	 * run_command to fork processes that will not interfere.
+	 */
+	cp.git_cmd = 1;
+	argv_array_push(&cp.args, "read-tree");
+	argv_array_push(&cp.args, oid_to_hex(u_tree));
+	argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+	if (run_command(&cp)) {
+		remove_path(stash_index_path.buf);
+		return -1;
+	}
+
+	child_process_init(&cp);
+	cp.git_cmd = 1;
+	argv_array_pushl(&cp.args, "checkout-index", "--all", NULL);
+	argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+
+	res = run_command(&cp);
+	remove_path(stash_index_path.buf);
+	return res;
+}
+
+static int do_apply_stash(const char *prefix, struct stash_info *info,
+			  int index, int quiet)
+{
+	int ret;
+	int has_index = index;
+	struct merge_options o;
+	struct object_id c_tree;
+	struct object_id index_tree;
+	struct commit *result;
+	const struct object_id *bases[1];
+
+	read_cache_preload(NULL);
+	if (refresh_cache(REFRESH_QUIET))
+		return -1;
+
+	if (write_cache_as_tree(&c_tree, 0, NULL) || reset_tree(&c_tree, 0, 0))
+		return error(_("cannot apply a stash in the middle of a merge"));
+
+	if (index) {
+		if (oideq(&info->b_tree, &info->i_tree) ||
+		    oideq(&c_tree, &info->i_tree)) {
+			has_index = 0;
+		} else {
+			struct strbuf out = STRBUF_INIT;
+
+			if (diff_tree_binary(&out, &info->w_commit)) {
+				strbuf_release(&out);
+				return error(_("could not generate diff %s^!."),
+					     oid_to_hex(&info->w_commit));
+			}
+
+			ret = apply_cached(&out);
+			strbuf_release(&out);
+			if (ret)
+				return error(_("conflicts in index."
+					       "Try without --index."));
+
+			discard_cache();
+			read_cache();
+			if (write_cache_as_tree(&index_tree, 0, NULL))
+				return error(_("could not save index tree"));
+
+			reset_head();
+		}
+	}
+
+	if (info->has_u && restore_untracked(&info->u_tree))
+		return error(_("could not restore untracked files from stash"));
+
+	init_merge_options(&o);
+
+	o.branch1 = "Updated upstream";
+	o.branch2 = "Stashed changes";
+
+	if (oideq(&info->b_tree, &c_tree))
+		o.branch1 = "Version stash was based on";
+
+	if (quiet)
+		o.verbosity = 0;
+
+	if (o.verbosity >= 3)
+		printf_ln(_("Merging %s with %s"), o.branch1, o.branch2);
+
+	bases[0] = &info->b_tree;
+
+	ret = merge_recursive_generic(&o, &c_tree, &info->w_tree, 1, bases,
+				      &result);
+	if (ret) {
+		rerere(0);
+
+		if (index)
+			fprintf_ln(stderr, _("Index was not unstashed."));
+
+		return ret;
+	}
+
+	if (has_index) {
+		if (reset_tree(&index_tree, 0, 0))
+			return -1;
+	} else {
+		struct strbuf out = STRBUF_INIT;
+
+		if (get_newly_staged(&out, &c_tree)) {
+			strbuf_release(&out);
+			return -1;
+		}
+
+		if (reset_tree(&c_tree, 0, 1)) {
+			strbuf_release(&out);
+			return -1;
+		}
+
+		ret = update_index(&out);
+		strbuf_release(&out);
+		if (ret)
+			return -1;
+
+		discard_cache();
+	}
+
+	if (quiet) {
+		if (refresh_cache(REFRESH_QUIET))
+			warning("could not refresh index");
+	} else {
+		struct child_process cp = CHILD_PROCESS_INIT;
+
+		/*
+		 * Status is quite simple and could be replaced with calls to
+		 * wt_status in the future, but it adds complexities which may
+		 * require more tests.
+		 */
+		cp.git_cmd = 1;
+		cp.dir = prefix;
+		argv_array_push(&cp.args, "status");
+		run_command(&cp);
+	}
+
+	return 0;
+}
+
+static int apply_stash(int argc, const char **argv, const char *prefix)
+{
+	int ret;
+	int quiet = 0;
+	int index = 0;
+	struct stash_info info;
+	struct option options[] = {
+		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
+		OPT_BOOL(0, "index", &index,
+			 N_("attempt to recreate the index")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_apply_usage, 0);
+
+	if (get_stash_info(&info, argc, argv))
+		return -1;
+
+	ret = do_apply_stash(prefix, &info, index, quiet);
+	free_stash_info(&info);
+	return ret;
+}
+
+int cmd_stash__helper(int argc, const char **argv, const char *prefix)
+{
+	pid_t pid = getpid();
+	const char *index_file;
+
+	struct option options[] = {
+		OPT_END()
+	};
+
+	git_config(git_default_config, NULL);
+
+	argc = parse_options(argc, argv, prefix, options, git_stash_helper_usage,
+			     PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
+
+	index_file = get_index_file();
+	strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file,
+		    (uintmax_t)pid);
+
+	if (argc < 1)
+		usage_with_options(git_stash_helper_usage, options);
+	if (!strcmp(argv[0], "apply"))
+		return !!apply_stash(argc, argv, prefix);
+
+	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
+		      git_stash_helper_usage, options);
+}
diff --git a/git-stash.sh b/git-stash.sh
index 789ce2f41d..366a082853 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -583,76 +583,11 @@ assert_stash_ref() {
 }
 
 apply_stash () {
-
-	assert_stash_like "$@"
-
-	git update-index -q --refresh || die "$(gettext "unable to refresh index")"
-
-	# current index state
-	c_tree=$(git write-tree) ||
-		die "$(gettext "Cannot apply a stash in the middle of a merge")"
-
-	unstashed_index_tree=
-	if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" &&
-			test "$c_tree" != "$i_tree"
-	then
-		git diff-tree --binary $s^2^..$s^2 | git apply --cached
-		test $? -ne 0 &&
-			die "$(gettext "Conflicts in index. Try without --index.")"
-		unstashed_index_tree=$(git write-tree) ||
-			die "$(gettext "Could not save index tree")"
-		git reset
-	fi
-
-	if test -n "$u_tree"
-	then
-		GIT_INDEX_FILE="$TMPindex" git read-tree "$u_tree" &&
-		GIT_INDEX_FILE="$TMPindex" git checkout-index --all &&
-		rm -f "$TMPindex" ||
-		die "$(gettext "Could not restore untracked files from stash entry")"
-	fi
-
-	eval "
-		GITHEAD_$w_tree='Stashed changes' &&
-		GITHEAD_$c_tree='Updated upstream' &&
-		GITHEAD_$b_tree='Version stash was based on' &&
-		export GITHEAD_$w_tree GITHEAD_$c_tree GITHEAD_$b_tree
-	"
-
-	if test -n "$GIT_QUIET"
-	then
-		GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY
-	fi
-	if git merge-recursive $b_tree -- $c_tree $w_tree
-	then
-		# No conflict
-		if test -n "$unstashed_index_tree"
-		then
-			git read-tree "$unstashed_index_tree"
-		else
-			a="$TMP-added" &&
-			git diff-index --cached --name-only --diff-filter=A $c_tree >"$a" &&
-			git read-tree --reset $c_tree &&
-			git update-index --add --stdin <"$a" ||
-				die "$(gettext "Cannot unstage modified files")"
-			rm -f "$a"
-		fi
-		squelch=
-		if test -n "$GIT_QUIET"
-		then
-			squelch='>/dev/null 2>&1'
-		fi
-		(cd "$START_DIR" && eval "git status $squelch") || :
-	else
-		# Merge conflict; keep the exit status from merge-recursive
-		status=$?
-		git rerere
-		if test -n "$INDEX_OPTION"
-		then
-			gettextln "Index was not unstashed." >&2
-		fi
-		exit $status
-	fi
+	cd "$START_DIR"
+	git stash--helper apply "$@"
+	res=$?
+	cd_to_toplevel
+	return $res
 }
 
 pop_stash() {
@@ -730,7 +665,8 @@ push)
 	;;
 apply)
 	shift
-	apply_stash "$@"
+	cd "$START_DIR"
+	git stash--helper apply "$@"
 	;;
 clear)
 	shift
diff --git a/git.c b/git.c
index 4d53a3d50d..de9c774573 100644
--- a/git.c
+++ b/git.c
@@ -554,6 +554,7 @@ static struct cmd_struct commands[] = {
 	{ "show-index", cmd_show_index },
 	{ "show-ref", cmd_show_ref, RUN_SETUP },
 	{ "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
+	{ "stash--helper", cmd_stash__helper, RUN_SETUP | NEED_WORK_TREE },
 	{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 	{ "stripspace", cmd_stripspace },
 	{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 11/26] stash: convert drop and clear to builtin
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (9 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 10/26] stash: convert apply to builtin Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 12/26] stash: convert branch " Paul-Sebastian Ungureanu
                     ` (15 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

From: Joel Teichroeb <joel@teichroeb.net>

Add the drop and clear commands to the builtin helper. These two
are each simple, but are being added together as they are quite
related.

We have to unfortunately keep the drop and clear functions in the
shell script as functions are called with parameters internally
that are not valid when the commands are called externally. Once
pop is converted they can both be removed.

Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 builtin/stash--helper.c | 117 ++++++++++++++++++++++++++++++++++++++++
 git-stash.sh            |   4 +-
 2 files changed, 119 insertions(+), 2 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 997b1c0ecf..07b8ec5bcb 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -12,7 +12,14 @@
 #include "rerere.h"
 
 static const char * const git_stash_helper_usage[] = {
+	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
 	N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
+	N_("git stash--helper clear"),
+	NULL
+};
+
+static const char * const git_stash_helper_drop_usage[] = {
+	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
 	NULL
 };
 
@@ -21,6 +28,11 @@ static const char * const git_stash_helper_apply_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_clear_usage[] = {
+	N_("git stash--helper clear"),
+	NULL
+};
+
 static const char *ref_stash = "refs/stash";
 static struct strbuf stash_index_path = STRBUF_INIT;
 
@@ -137,6 +149,32 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)
 	return !(ret == 0 || ret == 1);
 }
 
+static int do_clear_stash(void)
+{
+	struct object_id obj;
+	if (get_oid(ref_stash, &obj))
+		return 0;
+
+	return delete_ref(NULL, ref_stash, &obj, 0);
+}
+
+static int clear_stash(int argc, const char **argv, const char *prefix)
+{
+	struct option options[] = {
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_clear_usage,
+			     PARSE_OPT_STOP_AT_NON_OPTION);
+
+	if (argc)
+		return error(_("git stash clear with parameters is "
+			       "unimplemented"));
+
+	return do_clear_stash();
+}
+
 static int reset_tree(struct object_id *i_tree, int update, int reset)
 {
 	int nr_trees = 1;
@@ -424,6 +462,81 @@ static int apply_stash(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
+static int do_drop_stash(const char *prefix, struct stash_info *info, int quiet)
+{
+	int ret;
+	struct child_process cp_reflog = CHILD_PROCESS_INIT;
+	struct child_process cp = CHILD_PROCESS_INIT;
+
+	/*
+	 * reflog does not provide a simple function for deleting refs. One will
+	 * need to be added to avoid implementing too much reflog code here
+	 */
+
+	cp_reflog.git_cmd = 1;
+	argv_array_pushl(&cp_reflog.args, "reflog", "delete", "--updateref",
+			 "--rewrite", NULL);
+	argv_array_push(&cp_reflog.args, info->revision.buf);
+	ret = run_command(&cp_reflog);
+	if (!ret) {
+		if (!quiet)
+			printf_ln(_("Dropped %s (%s)"), info->revision.buf,
+				  oid_to_hex(&info->w_commit));
+	} else {
+		return error(_("%s: Could not drop stash entry"),
+			     info->revision.buf);
+	}
+
+	/*
+	 * This could easily be replaced by get_oid, but currently it will throw
+	 * a fatal error when a reflog is empty, which we can not recover from.
+	 */
+	cp.git_cmd = 1;
+	/* Even though --quiet is specified, rev-parse still outputs the hash */
+	cp.no_stdout = 1;
+	argv_array_pushl(&cp.args, "rev-parse", "--verify", "--quiet", NULL);
+	argv_array_pushf(&cp.args, "%s@{0}", ref_stash);
+	ret = run_command(&cp);
+
+	/* do_clear_stash if we just dropped the last stash entry */
+	if (ret)
+		do_clear_stash();
+
+	return 0;
+}
+
+static void assert_stash_ref(struct stash_info *info)
+{
+	if (!info->is_stash_ref) {
+		free_stash_info(info);
+		error(_("'%s' is not a stash reference"), info->revision.buf);
+		exit(128);
+	}
+}
+
+static int drop_stash(int argc, const char **argv, const char *prefix)
+{
+	int ret;
+	int quiet = 0;
+	struct stash_info info;
+	struct option options[] = {
+		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_drop_usage, 0);
+
+	if (get_stash_info(&info, argc, argv))
+		return -1;
+
+	assert_stash_ref(&info);
+
+	ret = do_drop_stash(prefix, &info, quiet);
+	free_stash_info(&info);
+	return ret;
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -446,6 +559,10 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_stash_helper_usage, options);
 	if (!strcmp(argv[0], "apply"))
 		return !!apply_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "clear"))
+		return !!clear_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "drop"))
+		return !!drop_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index 366a082853..b8f70230f9 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -670,7 +670,7 @@ apply)
 	;;
 clear)
 	shift
-	clear_stash "$@"
+	git stash--helper clear "$@"
 	;;
 create)
 	shift
@@ -682,7 +682,7 @@ store)
 	;;
 drop)
 	shift
-	drop_stash "$@"
+	git stash--helper drop "$@"
 	;;
 pop)
 	shift
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 12/26] stash: convert branch to builtin
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (10 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 11/26] stash: convert drop and clear " Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 13/26] stash: convert pop " Paul-Sebastian Ungureanu
                     ` (14 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

From: Joel Teichroeb <joel@teichroeb.net>

Add stash branch to the helper and delete the apply_to_branch
function from the shell script.

Checkout does not currently provide a function for checking out
a branch as cmd_checkout does a large amount of sanity checks
first that we require here.

Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 builtin/stash--helper.c | 46 +++++++++++++++++++++++++++++++++++++++++
 git-stash.sh            | 17 ++-------------
 2 files changed, 48 insertions(+), 15 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 07b8ec5bcb..68b65165e4 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -14,6 +14,7 @@
 static const char * const git_stash_helper_usage[] = {
 	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
 	N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
+	N_("git stash--helper branch <branchname> [<stash>]"),
 	N_("git stash--helper clear"),
 	NULL
 };
@@ -28,6 +29,11 @@ static const char * const git_stash_helper_apply_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_branch_usage[] = {
+	N_("git stash--helper branch <branchname> [<stash>]"),
+	NULL
+};
+
 static const char * const git_stash_helper_clear_usage[] = {
 	N_("git stash--helper clear"),
 	NULL
@@ -537,6 +543,44 @@ static int drop_stash(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
+static int branch_stash(int argc, const char **argv, const char *prefix)
+{
+	int ret;
+	const char *branch = NULL;
+	struct stash_info info;
+	struct child_process cp = CHILD_PROCESS_INIT;
+	struct option options[] = {
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_branch_usage, 0);
+
+	if (!argc) {
+		fprintf_ln(stderr, _("No branch name specified"));
+		return -1;
+	}
+
+	branch = argv[0];
+
+	if (get_stash_info(&info, argc - 1, argv + 1))
+		return -1;
+
+	cp.git_cmd = 1;
+	argv_array_pushl(&cp.args, "checkout", "-b", NULL);
+	argv_array_push(&cp.args, branch);
+	argv_array_push(&cp.args, oid_to_hex(&info.b_commit));
+	ret = run_command(&cp);
+	if (!ret)
+		ret = do_apply_stash(prefix, &info, 1, 0);
+	if (!ret && info.is_stash_ref)
+		ret = do_drop_stash(prefix, &info, 0);
+
+	free_stash_info(&info);
+
+	return ret;
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -563,6 +607,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!clear_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "drop"))
 		return !!drop_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "branch"))
+		return !!branch_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index b8f70230f9..67db321a4c 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -615,20 +615,6 @@ drop_stash () {
 	clear_stash
 }
 
-apply_to_branch () {
-	test -n "$1" || die "$(gettext "No branch name specified")"
-	branch=$1
-	shift 1
-
-	set -- --index "$@"
-	assert_stash_like "$@"
-
-	git checkout -b $branch $REV^ &&
-	apply_stash "$@" && {
-		test -z "$IS_STASH_REF" || drop_stash "$@"
-	}
-}
-
 test "$1" = "-p" && set "push" "$@"
 
 PARSE_CACHE='--not-parsed'
@@ -690,7 +676,8 @@ pop)
 	;;
 branch)
 	shift
-	apply_to_branch "$@"
+	cd "$START_DIR"
+	git stash--helper branch "$@"
 	;;
 *)
 	case $# in
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 13/26] stash: convert pop to builtin
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (11 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 12/26] stash: convert branch " Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 14/26] stash: convert list " Paul-Sebastian Ungureanu
                     ` (13 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

From: Joel Teichroeb <joel@teichroeb.net>

Add stash pop to the helper and delete the pop_stash, drop_stash,
assert_stash_ref functions from the shell script now that they
are no longer needed.

Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 builtin/stash--helper.c | 39 +++++++++++++++++++++++++++++++++-
 git-stash.sh            | 47 ++---------------------------------------
 2 files changed, 40 insertions(+), 46 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 68b65165e4..d7ff78784b 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -13,7 +13,7 @@
 
 static const char * const git_stash_helper_usage[] = {
 	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
-	N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
+	N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
 	N_("git stash--helper branch <branchname> [<stash>]"),
 	N_("git stash--helper clear"),
 	NULL
@@ -24,6 +24,11 @@ static const char * const git_stash_helper_drop_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_pop_usage[] = {
+	N_("git stash--helper pop [--index] [-q|--quiet] [<stash>]"),
+	NULL
+};
+
 static const char * const git_stash_helper_apply_usage[] = {
 	N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
 	NULL
@@ -543,6 +548,36 @@ static int drop_stash(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
+static int pop_stash(int argc, const char **argv, const char *prefix)
+{
+	int ret;
+	int index = 0;
+	int quiet = 0;
+	struct stash_info info;
+	struct option options[] = {
+		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
+		OPT_BOOL(0, "index", &index,
+			 N_("attempt to recreate the index")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_pop_usage, 0);
+
+	if (get_stash_info(&info, argc, argv))
+		return -1;
+
+	assert_stash_ref(&info);
+	if ((ret = do_apply_stash(prefix, &info, index, quiet)))
+		printf_ln(_("The stash entry is kept in case "
+			    "you need it again."));
+	else
+		ret = do_drop_stash(prefix, &info, quiet);
+
+	free_stash_info(&info);
+	return ret;
+}
+
 static int branch_stash(int argc, const char **argv, const char *prefix)
 {
 	int ret;
@@ -607,6 +642,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!clear_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "drop"))
 		return !!drop_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "pop"))
+		return !!pop_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "branch"))
 		return !!branch_stash(argc, argv, prefix);
 
diff --git a/git-stash.sh b/git-stash.sh
index 67db321a4c..8a9f907aa9 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -571,50 +571,6 @@ assert_stash_like() {
 	}
 }
 
-is_stash_ref() {
-	is_stash_like "$@" && test -n "$IS_STASH_REF"
-}
-
-assert_stash_ref() {
-	is_stash_ref "$@" || {
-		args="$*"
-		die "$(eval_gettext "'\$args' is not a stash reference")"
-	}
-}
-
-apply_stash () {
-	cd "$START_DIR"
-	git stash--helper apply "$@"
-	res=$?
-	cd_to_toplevel
-	return $res
-}
-
-pop_stash() {
-	assert_stash_ref "$@"
-
-	if apply_stash "$@"
-	then
-		drop_stash "$@"
-	else
-		status=$?
-		say "$(gettext "The stash entry is kept in case you need it again.")"
-		exit $status
-	fi
-}
-
-drop_stash () {
-	assert_stash_ref "$@"
-
-	git reflog delete --updateref --rewrite "${REV}" &&
-		say "$(eval_gettext "Dropped \${REV} (\$s)")" ||
-		die "$(eval_gettext "\${REV}: Could not drop stash entry")"
-
-	# clear_stash if we just dropped the last stash entry
-	git rev-parse --verify --quiet "$ref_stash@{0}" >/dev/null ||
-	clear_stash
-}
-
 test "$1" = "-p" && set "push" "$@"
 
 PARSE_CACHE='--not-parsed'
@@ -672,7 +628,8 @@ drop)
 	;;
 pop)
 	shift
-	pop_stash "$@"
+	cd "$START_DIR"
+	git stash--helper pop "$@"
 	;;
 branch)
 	shift
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 14/26] stash: convert list to builtin
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (12 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 13/26] stash: convert pop " Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 15/26] stash: convert show " Paul-Sebastian Ungureanu
                     ` (12 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

Add stash list to the helper and delete the list_stash function
from the shell script.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 builtin/stash--helper.c | 31 +++++++++++++++++++++++++++++++
 git-stash.sh            |  7 +------
 2 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index d7ff78784b..d66a4589a5 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -12,6 +12,7 @@
 #include "rerere.h"
 
 static const char * const git_stash_helper_usage[] = {
+	N_("git stash--helper list [<options>]"),
 	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
 	N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
 	N_("git stash--helper branch <branchname> [<stash>]"),
@@ -19,6 +20,11 @@ static const char * const git_stash_helper_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_list_usage[] = {
+	N_("git stash--helper list [<options>]"),
+	NULL
+};
+
 static const char * const git_stash_helper_drop_usage[] = {
 	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
 	NULL
@@ -616,6 +622,29 @@ static int branch_stash(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
+static int list_stash(int argc, const char **argv, const char *prefix)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	struct option options[] = {
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_list_usage,
+			     PARSE_OPT_KEEP_UNKNOWN);
+
+	if (!ref_exists(ref_stash))
+		return 0;
+
+	cp.git_cmd = 1;
+	argv_array_pushl(&cp.args, "log", "--format=%gd: %gs", "-g",
+			 "--first-parent", "-m", NULL);
+	argv_array_pushv(&cp.args, argv);
+	argv_array_push(&cp.args, ref_stash);
+	argv_array_push(&cp.args, "--");
+	return run_command(&cp);
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -646,6 +675,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!pop_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "branch"))
 		return !!branch_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "list"))
+		return !!list_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index 8a9f907aa9..ab3992b59d 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -399,11 +399,6 @@ have_stash () {
 	git rev-parse --verify --quiet $ref_stash >/dev/null
 }
 
-list_stash () {
-	have_stash || return 0
-	git log --format="%gd: %gs" -g --first-parent -m "$@" $ref_stash --
-}
-
 show_stash () {
 	ALLOW_UNKNOWN_FLAGS=t
 	assert_stash_like "$@"
@@ -591,7 +586,7 @@ test -n "$seen_non_option" || set "push" "$@"
 case "$1" in
 list)
 	shift
-	list_stash "$@"
+	git stash--helper list "$@"
 	;;
 show)
 	shift
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 15/26] stash: convert show to builtin
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (13 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 14/26] stash: convert list " Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 16/26] stash: convert store " Paul-Sebastian Ungureanu
                     ` (11 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

Add stash show to the helper and delete the show_stash, have_stash,
assert_stash_like, is_stash_like and parse_flags_and_rev functions
from the shell script now that they are no longer needed.

In shell version, although `git stash show` accepts `--index` and
`--quiet` options, it ignores them. In C, both options are passed
further to `git diff`.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 builtin/stash--helper.c |  87 ++++++++++++++++++++++++++
 git-stash.sh            | 132 +---------------------------------------
 2 files changed, 88 insertions(+), 131 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index d66a4589a5..36651f745a 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -10,9 +10,12 @@
 #include "run-command.h"
 #include "dir.h"
 #include "rerere.h"
+#include "revision.h"
+#include "log-tree.h"
 
 static const char * const git_stash_helper_usage[] = {
 	N_("git stash--helper list [<options>]"),
+	N_("git stash--helper show [<options>] [<stash>]"),
 	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
 	N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
 	N_("git stash--helper branch <branchname> [<stash>]"),
@@ -25,6 +28,11 @@ static const char * const git_stash_helper_list_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_show_usage[] = {
+	N_("git stash--helper show [<options>] [<stash>]"),
+	NULL
+};
+
 static const char * const git_stash_helper_drop_usage[] = {
 	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
 	NULL
@@ -645,6 +653,83 @@ static int list_stash(int argc, const char **argv, const char *prefix)
 	return run_command(&cp);
 }
 
+static int show_stat = 1;
+static int show_patch;
+
+static int git_stash_config(const char *var, const char *value, void *cb)
+{
+	if (!strcmp(var, "stash.showstat")) {
+		show_stat = git_config_bool(var, value);
+		return 0;
+	}
+	if (!strcmp(var, "stash.showpatch")) {
+		show_patch = git_config_bool(var, value);
+		return 0;
+	}
+	return git_default_config(var, value, cb);
+}
+
+static int show_stash(int argc, const char **argv, const char *prefix)
+{
+	int i;
+	int opts = 0;
+	int ret = 0;
+	struct stash_info info;
+	struct rev_info rev;
+	struct argv_array stash_args = ARGV_ARRAY_INIT;
+	struct option options[] = {
+		OPT_END()
+	};
+
+	init_diff_ui_defaults();
+	git_config(git_diff_ui_config, NULL);
+	init_revisions(&rev, prefix);
+
+	for (i = 1; i < argc; i++) {
+		if (argv[i][0] != '-')
+			argv_array_push(&stash_args, argv[i]);
+		else
+			opts++;
+	}
+
+	ret = get_stash_info(&info, stash_args.argc, stash_args.argv);
+	argv_array_clear(&stash_args);
+	if (ret)
+		return -1;
+
+	/*
+	 * The config settings are applied only if there are not passed
+	 * any options.
+	 */
+	if (!opts) {
+		git_config(git_stash_config, NULL);
+		if (show_stat)
+			rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT;
+
+		if (show_patch)
+			rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
+
+		if (!show_stat && !show_patch) {
+			free_stash_info(&info);
+			return 0;
+		}
+	}
+
+	argc = setup_revisions(argc, argv, &rev, NULL);
+	if (argc > 1) {
+		free_stash_info(&info);
+		usage_with_options(git_stash_helper_show_usage, options);
+	}
+
+	rev.diffopt.flags.recursive = 1;
+	setup_diff_pager(&rev.diffopt);
+	diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt);
+	log_tree_diff_flush(&rev);
+
+	free_stash_info(&info);
+	return diff_result_code(&rev.diffopt, 0);
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -677,6 +762,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!branch_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "list"))
 		return !!list_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "show"))
+		return !!show_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index ab3992b59d..d0318f859e 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -395,35 +395,6 @@ save_stash () {
 	fi
 }
 
-have_stash () {
-	git rev-parse --verify --quiet $ref_stash >/dev/null
-}
-
-show_stash () {
-	ALLOW_UNKNOWN_FLAGS=t
-	assert_stash_like "$@"
-
-	if test -z "$FLAGS"
-	then
-		if test "$(git config --bool stash.showStat || echo true)" = "true"
-		then
-			FLAGS=--stat
-		fi
-
-		if test "$(git config --bool stash.showPatch || echo false)" = "true"
-		then
-			FLAGS=${FLAGS}${FLAGS:+ }-p
-		fi
-
-		if test -z "$FLAGS"
-		then
-			return 0
-		fi
-	fi
-
-	git diff ${FLAGS} $b_commit $w_commit
-}
-
 show_help () {
 	exec git help stash
 	exit 1
@@ -465,107 +436,6 @@ show_help () {
 #   * unknown flags were set and ALLOW_UNKNOWN_FLAGS is not "t"
 #
 
-parse_flags_and_rev()
-{
-	test "$PARSE_CACHE" = "$*" && return 0 # optimisation
-	PARSE_CACHE="$*"
-
-	IS_STASH_LIKE=
-	IS_STASH_REF=
-	INDEX_OPTION=
-	s=
-	w_commit=
-	b_commit=
-	i_commit=
-	u_commit=
-	w_tree=
-	b_tree=
-	i_tree=
-	u_tree=
-
-	FLAGS=
-	REV=
-	for opt
-	do
-		case "$opt" in
-			-q|--quiet)
-				GIT_QUIET=-t
-			;;
-			--index)
-				INDEX_OPTION=--index
-			;;
-			--help)
-				show_help
-			;;
-			-*)
-				test "$ALLOW_UNKNOWN_FLAGS" = t ||
-					die "$(eval_gettext "unknown option: \$opt")"
-				FLAGS="${FLAGS}${FLAGS:+ }$opt"
-			;;
-			*)
-				REV="${REV}${REV:+ }'$opt'"
-			;;
-		esac
-	done
-
-	eval set -- $REV
-
-	case $# in
-		0)
-			have_stash || die "$(gettext "No stash entries found.")"
-			set -- ${ref_stash}@{0}
-		;;
-		1)
-			:
-		;;
-		*)
-			die "$(eval_gettext "Too many revisions specified: \$REV")"
-		;;
-	esac
-
-	case "$1" in
-		*[!0-9]*)
-			:
-		;;
-		*)
-			set -- "${ref_stash}@{$1}"
-		;;
-	esac
-
-	REV=$(git rev-parse --symbolic --verify --quiet "$1") || {
-		reference="$1"
-		die "$(eval_gettext "\$reference is not a valid reference")"
-	}
-
-	i_commit=$(git rev-parse --verify --quiet "$REV^2") &&
-	set -- $(git rev-parse "$REV" "$REV^1" "$REV:" "$REV^1:" "$REV^2:" 2>/dev/null) &&
-	s=$1 &&
-	w_commit=$1 &&
-	b_commit=$2 &&
-	w_tree=$3 &&
-	b_tree=$4 &&
-	i_tree=$5 &&
-	IS_STASH_LIKE=t &&
-	test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
-	IS_STASH_REF=t
-
-	u_commit=$(git rev-parse --verify --quiet "$REV^3") &&
-	u_tree=$(git rev-parse "$REV^3:" 2>/dev/null)
-}
-
-is_stash_like()
-{
-	parse_flags_and_rev "$@"
-	test -n "$IS_STASH_LIKE"
-}
-
-assert_stash_like() {
-	is_stash_like "$@" || {
-		args="$*"
-		die "$(eval_gettext "'\$args' is not a stash-like commit")"
-	}
-}
-
 test "$1" = "-p" && set "push" "$@"
 
 PARSE_CACHE='--not-parsed'
@@ -590,7 +460,7 @@ list)
 	;;
 show)
 	shift
-	show_stash "$@"
+	git stash--helper show "$@"
 	;;
 save)
 	shift
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 16/26] stash: convert store to builtin
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (14 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 15/26] stash: convert show " Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 17/26] stash: convert create " Paul-Sebastian Ungureanu
                     ` (10 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

Add stash store to the helper and delete the store_stash function
from the shell script.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 builtin/stash--helper.c | 62 +++++++++++++++++++++++++++++++++++++++++
 git-stash.sh            | 43 ++--------------------------
 2 files changed, 64 insertions(+), 41 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 36651f745a..5dc6c068d7 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -58,6 +58,11 @@ static const char * const git_stash_helper_clear_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_store_usage[] = {
+	N_("git stash--helper store [-m|--message <message>] [-q|--quiet] <commit>"),
+	NULL
+};
+
 static const char *ref_stash = "refs/stash";
 static struct strbuf stash_index_path = STRBUF_INIT;
 
@@ -730,6 +735,61 @@ static int show_stash(int argc, const char **argv, const char *prefix)
 	return diff_result_code(&rev.diffopt, 0);
 }
 
+static int do_store_stash(const struct object_id *w_commit, const char *stash_msg,
+			  int quiet)
+{
+	if (!stash_msg)
+		stash_msg = "Created via \"git stash store\".";
+
+	if (update_ref(stash_msg, ref_stash, w_commit, NULL,
+		       REF_FORCE_CREATE_REFLOG,
+		       quiet ? UPDATE_REFS_QUIET_ON_ERR :
+		       UPDATE_REFS_MSG_ON_ERR)) {
+		if (!quiet) {
+			fprintf_ln(stderr, _("Cannot update %s with %s"),
+				   ref_stash, oid_to_hex(w_commit));
+		}
+		return -1;
+	}
+
+	return 0;
+}
+
+static int store_stash(int argc, const char **argv, const char *prefix)
+{
+	int quiet = 0;
+	const char *stash_msg = NULL;
+	struct object_id obj;
+	struct object_context dummy;
+	struct option options[] = {
+		OPT__QUIET(&quiet, N_("be quiet")),
+		OPT_STRING('m', "message", &stash_msg, "message",
+			   N_("stash message")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_store_usage,
+			     PARSE_OPT_KEEP_UNKNOWN);
+
+	if (argc != 1) {
+		if (!quiet)
+			fprintf_ln(stderr, _("\"git stash store\" requires one "
+					     "<commit> argument"));
+		return -1;
+	}
+
+	if (get_oid_with_context(argv[0], quiet ? GET_OID_QUIETLY : 0, &obj,
+				 &dummy)) {
+		if (!quiet)
+			fprintf_ln(stderr, _("Cannot update %s with %s"),
+					     ref_stash, argv[0]);
+		return -1;
+	}
+
+	return do_store_stash(&obj, stash_msg, quiet);
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -764,6 +824,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!list_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "show"))
 		return !!show_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "store"))
+		return !!store_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index d0318f859e..ff5556ccb0 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -208,45 +208,6 @@ create_stash () {
 	die "$(gettext "Cannot record working tree state")"
 }
 
-store_stash () {
-	while test $# != 0
-	do
-		case "$1" in
-		-m|--message)
-			shift
-			stash_msg="$1"
-			;;
-		-m*)
-			stash_msg=${1#-m}
-			;;
-		--message=*)
-			stash_msg=${1#--message=}
-			;;
-		-q|--quiet)
-			quiet=t
-			;;
-		*)
-			break
-			;;
-		esac
-		shift
-	done
-	test $# = 1 ||
-	die "$(eval_gettext "\"$dashless store\" requires one <commit> argument")"
-
-	w_commit="$1"
-	if test -z "$stash_msg"
-	then
-		stash_msg="Created via \"git stash store\"."
-	fi
-
-	git update-ref --create-reflog -m "$stash_msg" $ref_stash $w_commit
-	ret=$?
-	test $ret != 0 && test -z "$quiet" &&
-	die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")"
-	return $ret
-}
-
 push_stash () {
 	keep_index=
 	patch_mode=
@@ -325,7 +286,7 @@ push_stash () {
 		clear_stash || die "$(gettext "Cannot initialize stash")"
 
 	create_stash -m "$stash_msg" -u "$untracked" -- "$@"
-	store_stash -m "$stash_msg" -q $w_commit ||
+	git stash--helper store -m "$stash_msg" -q $w_commit ||
 	die "$(gettext "Cannot save the current status")"
 	say "$(eval_gettext "Saved working directory and index state \$stash_msg")"
 
@@ -485,7 +446,7 @@ create)
 	;;
 store)
 	shift
-	store_stash "$@"
+	git stash--helper store "$@"
 	;;
 drop)
 	shift
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 17/26] stash: convert create to builtin
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (15 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 16/26] stash: convert store " Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 18/26] stash: convert push " Paul-Sebastian Ungureanu
                     ` (9 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

Add stash create to the helper.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 builtin/stash--helper.c | 453 +++++++++++++++++++++++++++++++++++++++-
 git-stash.sh            |   2 +-
 2 files changed, 453 insertions(+), 2 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 5dc6c068d7..080c2f7aa6 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -12,6 +12,9 @@
 #include "rerere.h"
 #include "revision.h"
 #include "log-tree.h"
+#include "diffcore.h"
+
+#define INCLUDE_ALL_FILES 2
 
 static const char * const git_stash_helper_usage[] = {
 	N_("git stash--helper list [<options>]"),
@@ -63,6 +66,11 @@ static const char * const git_stash_helper_store_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_create_usage[] = {
+	N_("git stash--helper create [<message>]"),
+	NULL
+};
+
 static const char *ref_stash = "refs/stash";
 static struct strbuf stash_index_path = STRBUF_INIT;
 
@@ -288,6 +296,24 @@ static int reset_head(void)
 	return run_command(&cp);
 }
 
+static void add_diff_to_buf(struct diff_queue_struct *q,
+			    struct diff_options *options,
+			    void *data)
+{
+	int i;
+
+	for (i = 0; i < q->nr; i++) {
+		strbuf_addstr(data, q->queue[i]->one->path);
+
+		/*
+		 * The reason we add "0" at the end of this strbuf
+		 * is because we will pass the output further to
+		 * "git update-index -z ...".
+		 */
+		strbuf_addch(data, '\0');
+	}
+}
+
 static int get_newly_staged(struct strbuf *out, struct object_id *c_tree)
 {
 	struct child_process cp = CHILD_PROCESS_INIT;
@@ -790,6 +816,429 @@ static int store_stash(int argc, const char **argv, const char *prefix)
 	return do_store_stash(&obj, stash_msg, quiet);
 }
 
+static void add_pathspecs(struct argv_array *args,
+			  struct pathspec ps) {
+	int i;
+
+	for (i = 0; i < ps.nr; i++)
+		argv_array_push(args, ps.items[i].match);
+}
+
+/*
+ * `untracked_files` will be filled with the names of untracked files.
+ * The return value is:
+ *
+ * = 0 if there are not any untracked files
+ * > 0 if there are untracked files
+ */
+static int get_untracked_files(struct pathspec ps, int include_untracked,
+			       struct strbuf *untracked_files)
+{
+	int i;
+	int max_len;
+	int found = 0;
+	char *seen;
+	struct dir_struct dir;
+
+	memset(&dir, 0, sizeof(dir));
+	if (include_untracked != INCLUDE_ALL_FILES)
+		setup_standard_excludes(&dir);
+
+	seen = xcalloc(ps.nr, 1);
+
+	max_len = fill_directory(&dir, the_repository->index, &ps);
+	for (i = 0; i < dir.nr; i++) {
+		struct dir_entry *ent = dir.entries[i];
+		if (dir_path_match(&the_index, ent, &ps, max_len, seen)) {
+			found++;
+			strbuf_addstr(untracked_files, ent->name);
+			/* NUL-terminate: will be fed to update-index -z */
+			strbuf_addch(untracked_files, 0);
+		}
+		free(ent);
+	}
+
+	free(seen);
+	free(dir.entries);
+	free(dir.ignored);
+	clear_directory(&dir);
+	return found;
+}
+
+/*
+ * The return value of `check_changes()` can be:
+ *
+ * < 0 if there was an error
+ * = 0 if there are no changes.
+ * > 0 if there are changes.
+ */
+static int check_changes(struct pathspec ps, int include_untracked)
+{
+	int result;
+	struct rev_info rev;
+	struct object_id dummy;
+	struct strbuf out = STRBUF_INIT;
+
+	/* No initial commit. */
+	if (get_oid("HEAD", &dummy))
+		return -1;
+
+	if (read_cache() < 0)
+		return -1;
+
+	init_revisions(&rev, NULL);
+	rev.prune_data = ps;
+
+	rev.diffopt.flags.quick = 1;
+	rev.diffopt.flags.ignore_submodules = 1;
+	rev.abbrev = 0;
+
+	add_head_to_pending(&rev);
+	diff_setup_done(&rev.diffopt);
+
+	result = run_diff_index(&rev, 1);
+	if (diff_result_code(&rev.diffopt, result))
+		return 1;
+
+	object_array_clear(&rev.pending);
+	result = run_diff_files(&rev, 0);
+	if (diff_result_code(&rev.diffopt, result))
+		return 1;
+
+	if (include_untracked && get_untracked_files(ps, include_untracked,
+						     &out)) {
+		strbuf_release(&out);
+		return 1;
+	}
+
+	strbuf_release(&out);
+	return 0;
+}
+
+static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
+				struct strbuf files)
+{
+	int ret = 0;
+	struct strbuf untracked_msg = STRBUF_INIT;
+	struct strbuf out = STRBUF_INIT;
+	struct child_process cp_upd_index = CHILD_PROCESS_INIT;
+	struct child_process cp_write_tree = CHILD_PROCESS_INIT;
+
+	cp_upd_index.git_cmd = 1;
+	argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add",
+			 "--remove", "--stdin", NULL);
+	argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+
+	strbuf_addf(&untracked_msg, "untracked files on %s\n", msg->buf);
+	if (pipe_command(&cp_upd_index, files.buf, files.len, NULL, 0,
+			 NULL, 0)) {
+		ret = -1;
+		goto done;
+	}
+
+	cp_write_tree.git_cmd = 1;
+	argv_array_push(&cp_write_tree.args, "write-tree");
+	argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+	if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) {
+		ret = -1;
+		goto done;
+	}
+	get_oid_hex(out.buf, &info->u_tree);
+
+	if (commit_tree(untracked_msg.buf, untracked_msg.len,
+			&info->u_tree, NULL, &info->u_commit, NULL, NULL)) {
+		ret = -1;
+		goto done;
+	}
+
+done:
+	strbuf_release(&untracked_msg);
+	strbuf_release(&out);
+	remove_path(stash_index_path.buf);
+	return ret;
+}
+
+static int stash_patch(struct stash_info *info, struct pathspec ps,
+		       struct strbuf *out_patch)
+{
+	int ret = 0;
+	struct strbuf out = STRBUF_INIT;
+	struct child_process cp_read_tree = CHILD_PROCESS_INIT;
+	struct child_process cp_add_i = CHILD_PROCESS_INIT;
+	struct child_process cp_write_tree = CHILD_PROCESS_INIT;
+	struct child_process cp_diff_tree = CHILD_PROCESS_INIT;
+
+	remove_path(stash_index_path.buf);
+
+	cp_read_tree.git_cmd = 1;
+	argv_array_pushl(&cp_read_tree.args, "read-tree", "HEAD", NULL);
+	argv_array_pushf(&cp_read_tree.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+	if (run_command(&cp_read_tree)) {
+		ret = -1;
+		goto done;
+	}
+
+	/* Find out what the user wants. */
+	cp_add_i.git_cmd = 1;
+	argv_array_pushl(&cp_add_i.args, "add--interactive", "--patch=stash",
+			 "--", NULL);
+	add_pathspecs(&cp_add_i.args, ps);
+	argv_array_pushf(&cp_add_i.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+	if (run_command(&cp_add_i)) {
+		ret = -1;
+		goto done;
+	}
+
+	/* State of the working tree. */
+	cp_write_tree.git_cmd = 1;
+	argv_array_push(&cp_write_tree.args, "write-tree");
+	argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+	if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) {
+		ret = -1;
+		goto done;
+	}
+
+	get_oid_hex(out.buf, &info->w_tree);
+
+	cp_diff_tree.git_cmd = 1;
+	argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD",
+			 oid_to_hex(&info->w_tree), "--", NULL);
+	if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {
+		ret = -1;
+		goto done;
+	}
+
+	if (!out_patch->len) {
+		fprintf_ln(stderr, _("No changes selected"));
+		ret = 1;
+	}
+
+done:
+	strbuf_release(&out);
+	remove_path(stash_index_path.buf);
+	return ret;
+}
+
+static int stash_working_tree(struct stash_info *info, struct pathspec ps)
+{
+	int ret = 0;
+	struct rev_info rev;
+	struct child_process cp_upd_index = CHILD_PROCESS_INIT;
+	struct child_process cp_write_tree = CHILD_PROCESS_INIT;
+	struct strbuf out = STRBUF_INIT;
+	struct strbuf diff_output = STRBUF_INIT;
+
+	set_alternate_index_output(stash_index_path.buf);
+	if (reset_tree(&info->i_tree, 0, 0)) {
+		ret = -1;
+		goto done;
+	}
+	set_alternate_index_output(NULL);
+
+	init_revisions(&rev, NULL);
+	rev.prune_data = ps;
+	rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+	rev.diffopt.format_callback = add_diff_to_buf;
+	rev.diffopt.format_callback_data = &diff_output;
+
+	if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
+		ret = -1;
+		goto done;
+	}
+
+	add_pending_object(&rev, parse_object(the_repository, &info->b_commit),
+			   "");
+	if (run_diff_index(&rev, 0)) {
+		ret = -1;
+		goto done;
+	}
+
+	cp_upd_index.git_cmd = 1;
+	argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add",
+			 "--remove", "--stdin", NULL);
+	argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+
+	if (pipe_command(&cp_upd_index, diff_output.buf, diff_output.len,
+			 NULL, 0, NULL, 0)) {
+		ret = -1;
+		goto done;
+	}
+
+	cp_write_tree.git_cmd = 1;
+	argv_array_push(&cp_write_tree.args, "write-tree");
+	argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+	if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) {
+		ret = -1;
+		goto done;
+	}
+
+	get_oid_hex(out.buf, &info->w_tree);
+
+done:
+	UNLEAK(rev);
+	strbuf_release(&out);
+	object_array_clear(&rev.pending);
+	strbuf_release(&diff_output);
+	remove_path(stash_index_path.buf);
+	return ret;
+}
+
+static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
+			   int include_untracked, int patch_mode,
+			   struct stash_info *info)
+{
+	int ret = 0;
+	int flags = 0;
+	int untracked_commit_option = 0;
+	const char *head_short_sha1 = NULL;
+	const char *branch_ref = NULL;
+	const char *branch_name = "(no branch)";
+	struct commit *head_commit = NULL;
+	struct commit_list *parents = NULL;
+	struct strbuf msg = STRBUF_INIT;
+	struct strbuf commit_tree_label = STRBUF_INIT;
+	struct strbuf untracked_files = STRBUF_INIT;
+	struct strbuf patch = STRBUF_INIT;
+
+	prepare_fallback_ident("git stash", "git@stash");
+
+	read_cache_preload(NULL);
+	refresh_cache(REFRESH_QUIET);
+
+	if (get_oid("HEAD", &info->b_commit)) {
+		fprintf_ln(stderr, _("You do not have the initial commit yet"));
+		ret = -1;
+		goto done;
+	} else {
+		head_commit = lookup_commit(the_repository, &info->b_commit);
+	}
+
+	if (!check_changes(ps, include_untracked)) {
+		ret = 1;
+		goto done;
+	}
+
+	branch_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
+	if (flags & REF_ISSYMREF)
+		branch_name = strrchr(branch_ref, '/') + 1;
+	head_short_sha1 = find_unique_abbrev(&head_commit->object.oid,
+					     DEFAULT_ABBREV);
+	strbuf_addf(&msg, "%s: %s ", branch_name, head_short_sha1);
+	pp_commit_easy(CMIT_FMT_ONELINE, head_commit, &msg);
+
+	strbuf_addf(&commit_tree_label, "index on %s\n", msg.buf);
+	commit_list_insert(head_commit, &parents);
+	if (write_cache_as_tree(&info->i_tree, 0, NULL) ||
+	    commit_tree(commit_tree_label.buf, commit_tree_label.len,
+			&info->i_tree, parents, &info->i_commit, NULL, NULL)) {
+		fprintf_ln(stderr, _("Cannot save the current index state"));
+		ret = -1;
+		goto done;
+	}
+
+	if (include_untracked && get_untracked_files(ps, include_untracked,
+						     &untracked_files)) {
+		if (save_untracked_files(info, &msg, untracked_files)) {
+			fprintf_ln(stderr, _("Cannot save "
+					     "the untracked files"));
+			ret = -1;
+			goto done;
+		}
+		untracked_commit_option = 1;
+	}
+	if (patch_mode) {
+		ret = stash_patch(info, ps, &patch);
+		if (ret < 0) {
+			fprintf_ln(stderr, _("Cannot save the current "
+					     "worktree state"));
+			goto done;
+		} else if (ret > 0) {
+			goto done;
+		}
+	} else {
+		if (stash_working_tree(info, ps)) {
+			fprintf_ln(stderr, _("Cannot save the current "
+					     "worktree state"));
+			ret = -1;
+			goto done;
+		}
+	}
+
+	if (!stash_msg_buf->len)
+		strbuf_addf(stash_msg_buf, "WIP on %s", msg.buf);
+	else
+		strbuf_insertf(stash_msg_buf, 0, "On %s: ", branch_name);
+
+	/*
+	 * `parents` will be empty after calling `commit_tree()`, so there is
+	 * no need to call `free_commit_list()`
+	 */
+	parents = NULL;
+	if (untracked_commit_option)
+		commit_list_insert(lookup_commit(the_repository,
+						 &info->u_commit),
+				   &parents);
+	commit_list_insert(lookup_commit(the_repository, &info->i_commit),
+			   &parents);
+	commit_list_insert(head_commit, &parents);
+
+	if (commit_tree(stash_msg_buf->buf, stash_msg_buf->len, &info->w_tree,
+			parents, &info->w_commit, NULL, NULL)) {
+		fprintf_ln(stderr, _("Cannot record working tree state"));
+		ret = -1;
+		goto done;
+	}
+
+done:
+	strbuf_release(&commit_tree_label);
+	strbuf_release(&msg);
+	strbuf_release(&untracked_files);
+	return ret;
+}
+
+static int create_stash(int argc, const char **argv, const char *prefix)
+{
+	int include_untracked = 0;
+	int ret = 0;
+	const char *stash_msg = NULL;
+	struct strbuf stash_msg_buf = STRBUF_INIT;
+	struct stash_info info;
+	struct pathspec ps;
+	struct option options[] = {
+		OPT_BOOL('u', "include-untracked", &include_untracked,
+			 N_("include untracked files in stash")),
+		OPT_STRING('m', "message", &stash_msg, N_("message"),
+			 N_("stash message")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_create_usage,
+			     0);
+
+	memset(&ps, 0, sizeof(ps));
+	strbuf_addstr(&stash_msg_buf, stash_msg);
+	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info);
+
+	if (!ret)
+		printf_ln("%s", oid_to_hex(&info.w_commit));
+
+	strbuf_release(&stash_msg_buf);
+
+	/*
+	 * ret can be 1 if there were no changes. In this case, we should
+	 * not error out.
+	 */
+	return ret < 0;
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -799,7 +1248,7 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
-	git_config(git_default_config, NULL);
+	git_config(git_diff_basic_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, options, git_stash_helper_usage,
 			     PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
@@ -826,6 +1275,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!show_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "store"))
 		return !!store_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "create"))
+		return !!create_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index ff5556ccb0..a9b3064ff0 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -442,7 +442,7 @@ clear)
 	;;
 create)
 	shift
-	create_stash -m "$*" && echo "$w_commit"
+	git stash--helper create --message "$*"
 	;;
 store)
 	shift
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 18/26] stash: convert push to builtin
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (16 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 17/26] stash: convert create " Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2019-02-08 11:30     ` SZEDER Gábor
  2018-12-20 19:44   ` [PATCH v12 19/26] stash: make push -q quiet Paul-Sebastian Ungureanu
                     ` (8 subsequent siblings)
  26 siblings, 1 reply; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

Add stash push to the helper.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 builtin/stash--helper.c | 245 +++++++++++++++++++++++++++++++++++++++-
 git-stash.sh            |   6 +-
 2 files changed, 245 insertions(+), 6 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 080c2f7aa6..c77f62c895 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -23,6 +23,9 @@ static const char * const git_stash_helper_usage[] = {
 	N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
 	N_("git stash--helper branch <branchname> [<stash>]"),
 	N_("git stash--helper clear"),
+	N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+	   "          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
+	   "          [--] [<pathspec>...]]"),
 	NULL
 };
 
@@ -71,6 +74,13 @@ static const char * const git_stash_helper_create_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_push_usage[] = {
+	N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+	   "          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
+	   "          [--] [<pathspec>...]]"),
+	NULL
+};
+
 static const char *ref_stash = "refs/stash";
 static struct strbuf stash_index_path = STRBUF_INIT;
 
@@ -1092,7 +1102,7 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
 
 static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 			   int include_untracked, int patch_mode,
-			   struct stash_info *info)
+			   struct stash_info *info, struct strbuf *patch)
 {
 	int ret = 0;
 	int flags = 0;
@@ -1105,7 +1115,6 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 	struct strbuf msg = STRBUF_INIT;
 	struct strbuf commit_tree_label = STRBUF_INIT;
 	struct strbuf untracked_files = STRBUF_INIT;
-	struct strbuf patch = STRBUF_INIT;
 
 	prepare_fallback_ident("git stash", "git@stash");
 
@@ -1154,7 +1163,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 		untracked_commit_option = 1;
 	}
 	if (patch_mode) {
-		ret = stash_patch(info, ps, &patch);
+		ret = stash_patch(info, ps, patch);
 		if (ret < 0) {
 			fprintf_ln(stderr, _("Cannot save the current "
 					     "worktree state"));
@@ -1225,7 +1234,8 @@ static int create_stash(int argc, const char **argv, const char *prefix)
 
 	memset(&ps, 0, sizeof(ps));
 	strbuf_addstr(&stash_msg_buf, stash_msg);
-	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info);
+	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
+			      NULL);
 
 	if (!ret)
 		printf_ln("%s", oid_to_hex(&info.w_commit));
@@ -1239,6 +1249,231 @@ static int create_stash(int argc, const char **argv, const char *prefix)
 	return ret < 0;
 }
 
+static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
+			 int keep_index, int patch_mode, int include_untracked)
+{
+	int ret = 0;
+	struct stash_info info;
+	struct strbuf patch = STRBUF_INIT;
+	struct strbuf stash_msg_buf = STRBUF_INIT;
+
+	if (patch_mode && keep_index == -1)
+		keep_index = 1;
+
+	if (patch_mode && include_untracked) {
+		fprintf_ln(stderr, _("Can't use --patch and --include-untracked"
+				     " or --all at the same time"));
+		ret = -1;
+		goto done;
+	}
+
+	read_cache_preload(NULL);
+	if (!include_untracked && ps.nr) {
+		int i;
+		char *ps_matched = xcalloc(ps.nr, 1);
+
+		for (i = 0; i < active_nr; i++)
+			ce_path_match(&the_index, active_cache[i], &ps,
+				      ps_matched);
+
+		if (report_path_error(ps_matched, &ps, NULL)) {
+			fprintf_ln(stderr, _("Did you forget to 'git add'?"));
+			ret = -1;
+			free(ps_matched);
+			goto done;
+		}
+		free(ps_matched);
+	}
+
+	if (refresh_cache(REFRESH_QUIET)) {
+		ret = -1;
+		goto done;
+	}
+
+	if (!check_changes(ps, include_untracked)) {
+		if (!quiet)
+			printf_ln(_("No local changes to save"));
+		goto done;
+	}
+
+	if (!reflog_exists(ref_stash) && do_clear_stash()) {
+		ret = -1;
+		fprintf_ln(stderr, _("Cannot initialize stash"));
+		goto done;
+	}
+
+	if (stash_msg)
+		strbuf_addstr(&stash_msg_buf, stash_msg);
+	if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,
+			    &info, &patch)) {
+		ret = -1;
+		goto done;
+	}
+
+	if (do_store_stash(&info.w_commit, stash_msg_buf.buf, 1)) {
+		ret = -1;
+		fprintf_ln(stderr, _("Cannot save the current status"));
+		goto done;
+	}
+
+	printf_ln(_("Saved working directory and index state %s"),
+		  stash_msg_buf.buf);
+
+	if (!patch_mode) {
+		if (include_untracked && !ps.nr) {
+			struct child_process cp = CHILD_PROCESS_INIT;
+
+			cp.git_cmd = 1;
+			argv_array_pushl(&cp.args, "clean", "--force",
+					 "--quiet", "-d", NULL);
+			if (include_untracked == INCLUDE_ALL_FILES)
+				argv_array_push(&cp.args, "-x");
+			if (run_command(&cp)) {
+				ret = -1;
+				goto done;
+			}
+		}
+		if (ps.nr) {
+			struct child_process cp_add = CHILD_PROCESS_INIT;
+			struct child_process cp_diff = CHILD_PROCESS_INIT;
+			struct child_process cp_apply = CHILD_PROCESS_INIT;
+			struct strbuf out = STRBUF_INIT;
+
+			cp_add.git_cmd = 1;
+			argv_array_push(&cp_add.args, "add");
+			if (!include_untracked)
+				argv_array_push(&cp_add.args, "-u");
+			if (include_untracked == INCLUDE_ALL_FILES)
+				argv_array_push(&cp_add.args, "--force");
+			argv_array_push(&cp_add.args, "--");
+			add_pathspecs(&cp_add.args, ps);
+			if (run_command(&cp_add)) {
+				ret = -1;
+				goto done;
+			}
+
+			cp_diff.git_cmd = 1;
+			argv_array_pushl(&cp_diff.args, "diff-index", "-p",
+					 "--cached", "--binary", "HEAD", "--",
+					 NULL);
+			add_pathspecs(&cp_diff.args, ps);
+			if (pipe_command(&cp_diff, NULL, 0, &out, 0, NULL, 0)) {
+				ret = -1;
+				goto done;
+			}
+
+			cp_apply.git_cmd = 1;
+			argv_array_pushl(&cp_apply.args, "apply", "--index",
+					 "-R", NULL);
+			if (pipe_command(&cp_apply, out.buf, out.len, NULL, 0,
+					 NULL, 0)) {
+				ret = -1;
+				goto done;
+			}
+		} else {
+			struct child_process cp = CHILD_PROCESS_INIT;
+			cp.git_cmd = 1;
+			argv_array_pushl(&cp.args, "reset", "--hard", "-q",
+					 NULL);
+			if (run_command(&cp)) {
+				ret = -1;
+				goto done;
+			}
+		}
+
+		if (keep_index == 1 && !is_null_oid(&info.i_tree)) {
+			struct child_process cp_ls = CHILD_PROCESS_INIT;
+			struct child_process cp_checkout = CHILD_PROCESS_INIT;
+			struct strbuf out = STRBUF_INIT;
+
+			if (reset_tree(&info.i_tree, 0, 1)) {
+				ret = -1;
+				goto done;
+			}
+
+			cp_ls.git_cmd = 1;
+			argv_array_pushl(&cp_ls.args, "ls-files", "-z",
+					 "--modified", "--", NULL);
+
+			add_pathspecs(&cp_ls.args, ps);
+			if (pipe_command(&cp_ls, NULL, 0, &out, 0, NULL, 0)) {
+				ret = -1;
+				goto done;
+			}
+
+			cp_checkout.git_cmd = 1;
+			argv_array_pushl(&cp_checkout.args, "checkout-index",
+					 "-z", "--force", "--stdin", NULL);
+			if (pipe_command(&cp_checkout, out.buf, out.len, NULL,
+					 0, NULL, 0)) {
+				ret = -1;
+				goto done;
+			}
+		}
+		goto done;
+	} else {
+		struct child_process cp = CHILD_PROCESS_INIT;
+
+		cp.git_cmd = 1;
+		argv_array_pushl(&cp.args, "apply", "-R", NULL);
+
+		if (pipe_command(&cp, patch.buf, patch.len, NULL, 0, NULL, 0)) {
+			fprintf_ln(stderr, _("Cannot remove worktree changes"));
+			ret = -1;
+			goto done;
+		}
+
+		if (keep_index < 1) {
+			struct child_process cp = CHILD_PROCESS_INIT;
+
+			cp.git_cmd = 1;
+			argv_array_pushl(&cp.args, "reset", "-q", "--", NULL);
+			add_pathspecs(&cp.args, ps);
+			if (run_command(&cp)) {
+				ret = -1;
+				goto done;
+			}
+		}
+		goto done;
+	}
+
+done:
+	strbuf_release(&stash_msg_buf);
+	return ret;
+}
+
+static int push_stash(int argc, const char **argv, const char *prefix)
+{
+	int keep_index = -1;
+	int patch_mode = 0;
+	int include_untracked = 0;
+	int quiet = 0;
+	const char *stash_msg = NULL;
+	struct pathspec ps;
+	struct option options[] = {
+		OPT_BOOL('k', "keep-index", &keep_index,
+			 N_("keep index")),
+		OPT_BOOL('p', "patch", &patch_mode,
+			 N_("stash in patch mode")),
+		OPT__QUIET(&quiet, N_("quiet mode")),
+		OPT_BOOL('u', "include-untracked", &include_untracked,
+			 N_("include untracked files in stash")),
+		OPT_SET_INT('a', "all", &include_untracked,
+			    N_("include ignore files"), 2),
+		OPT_STRING('m', "message", &stash_msg, N_("message"),
+			   N_("stash message")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_push_usage,
+			     0);
+
+	parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL, prefix, argv);
+	return do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode,
+			     include_untracked);
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -1277,6 +1512,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!store_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "create"))
 		return !!create_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "push"))
+		return !!push_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index a9b3064ff0..51d7a06601 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -429,7 +429,8 @@ save)
 	;;
 push)
 	shift
-	push_stash "$@"
+	cd "$START_DIR"
+	git stash--helper push "$@"
 	;;
 apply)
 	shift
@@ -465,7 +466,8 @@ branch)
 *)
 	case $# in
 	0)
-		push_stash &&
+		cd "$START_DIR"
+		git stash--helper push &&
 		say "$(gettext "(To restore them type \"git stash apply\")")"
 		;;
 	*)
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 19/26] stash: make push -q quiet
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (17 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 18/26] stash: convert push " Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 20/26] stash: convert save to builtin Paul-Sebastian Ungureanu
                     ` (7 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

There is a change in behaviour with this commit. When there was
no initial commit, the shell version of stash would still display
a message. This commit makes `push` to not display any message if
`--quiet` or `-q` is specified. Add tests for `--quiet`.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 builtin/stash--helper.c | 56 ++++++++++++++++++++++++++---------------
 t/t3903-stash.sh        | 23 +++++++++++++++++
 2 files changed, 59 insertions(+), 20 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index c77f62c895..5f9914ca27 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -971,7 +971,7 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
 }
 
 static int stash_patch(struct stash_info *info, struct pathspec ps,
-		       struct strbuf *out_patch)
+		       struct strbuf *out_patch, int quiet)
 {
 	int ret = 0;
 	struct strbuf out = STRBUF_INIT;
@@ -1024,7 +1024,8 @@ static int stash_patch(struct stash_info *info, struct pathspec ps,
 	}
 
 	if (!out_patch->len) {
-		fprintf_ln(stderr, _("No changes selected"));
+		if (!quiet)
+			fprintf_ln(stderr, _("No changes selected"));
 		ret = 1;
 	}
 
@@ -1102,7 +1103,8 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
 
 static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 			   int include_untracked, int patch_mode,
-			   struct stash_info *info, struct strbuf *patch)
+			   struct stash_info *info, struct strbuf *patch,
+			   int quiet)
 {
 	int ret = 0;
 	int flags = 0;
@@ -1122,7 +1124,9 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 	refresh_cache(REFRESH_QUIET);
 
 	if (get_oid("HEAD", &info->b_commit)) {
-		fprintf_ln(stderr, _("You do not have the initial commit yet"));
+		if (!quiet)
+			fprintf_ln(stderr, _("You do not have "
+					     "the initial commit yet"));
 		ret = -1;
 		goto done;
 	} else {
@@ -1147,7 +1151,9 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 	if (write_cache_as_tree(&info->i_tree, 0, NULL) ||
 	    commit_tree(commit_tree_label.buf, commit_tree_label.len,
 			&info->i_tree, parents, &info->i_commit, NULL, NULL)) {
-		fprintf_ln(stderr, _("Cannot save the current index state"));
+		if (!quiet)
+			fprintf_ln(stderr, _("Cannot save the current "
+					     "index state"));
 		ret = -1;
 		goto done;
 	}
@@ -1155,26 +1161,29 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 	if (include_untracked && get_untracked_files(ps, include_untracked,
 						     &untracked_files)) {
 		if (save_untracked_files(info, &msg, untracked_files)) {
-			fprintf_ln(stderr, _("Cannot save "
-					     "the untracked files"));
+			if (!quiet)
+				fprintf_ln(stderr, _("Cannot save "
+						     "the untracked files"));
 			ret = -1;
 			goto done;
 		}
 		untracked_commit_option = 1;
 	}
 	if (patch_mode) {
-		ret = stash_patch(info, ps, patch);
+		ret = stash_patch(info, ps, patch, quiet);
 		if (ret < 0) {
-			fprintf_ln(stderr, _("Cannot save the current "
-					     "worktree state"));
+			if (!quiet)
+				fprintf_ln(stderr, _("Cannot save the current "
+						     "worktree state"));
 			goto done;
 		} else if (ret > 0) {
 			goto done;
 		}
 	} else {
 		if (stash_working_tree(info, ps)) {
-			fprintf_ln(stderr, _("Cannot save the current "
-					     "worktree state"));
+			if (!quiet)
+				fprintf_ln(stderr, _("Cannot save the current "
+						     "worktree state"));
 			ret = -1;
 			goto done;
 		}
@@ -1200,7 +1209,9 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 
 	if (commit_tree(stash_msg_buf->buf, stash_msg_buf->len, &info->w_tree,
 			parents, &info->w_commit, NULL, NULL)) {
-		fprintf_ln(stderr, _("Cannot record working tree state"));
+		if (!quiet)
+			fprintf_ln(stderr, _("Cannot record "
+					     "working tree state"));
 		ret = -1;
 		goto done;
 	}
@@ -1235,7 +1246,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)
 	memset(&ps, 0, sizeof(ps));
 	strbuf_addstr(&stash_msg_buf, stash_msg);
 	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
-			      NULL);
+			      NULL, 0);
 
 	if (!ret)
 		printf_ln("%s", oid_to_hex(&info.w_commit));
@@ -1298,26 +1309,29 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
 
 	if (!reflog_exists(ref_stash) && do_clear_stash()) {
 		ret = -1;
-		fprintf_ln(stderr, _("Cannot initialize stash"));
+		if (!quiet)
+			fprintf_ln(stderr, _("Cannot initialize stash"));
 		goto done;
 	}
 
 	if (stash_msg)
 		strbuf_addstr(&stash_msg_buf, stash_msg);
 	if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,
-			    &info, &patch)) {
+			    &info, &patch, quiet)) {
 		ret = -1;
 		goto done;
 	}
 
 	if (do_store_stash(&info.w_commit, stash_msg_buf.buf, 1)) {
 		ret = -1;
-		fprintf_ln(stderr, _("Cannot save the current status"));
+		if (!quiet)
+			fprintf_ln(stderr, _("Cannot save the current status"));
 		goto done;
 	}
 
-	printf_ln(_("Saved working directory and index state %s"),
-		  stash_msg_buf.buf);
+	if (!quiet)
+		printf_ln(_("Saved working directory and index state %s"),
+			  stash_msg_buf.buf);
 
 	if (!patch_mode) {
 		if (include_untracked && !ps.nr) {
@@ -1418,7 +1432,9 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
 		argv_array_pushl(&cp.args, "apply", "-R", NULL);
 
 		if (pipe_command(&cp, patch.buf, patch.len, NULL, 0, NULL, 0)) {
-			fprintf_ln(stderr, _("Cannot remove worktree changes"));
+			if (!quiet)
+				fprintf_ln(stderr, _("Cannot remove "
+						     "worktree changes"));
 			ret = -1;
 			goto done;
 		}
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 98c25a671c..b67d7a1120 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -1064,6 +1064,29 @@ test_expect_success 'push: <pathspec> not in the repository errors out' '
 	test_path_is_file untracked
 '
 
+test_expect_success 'push: -q is quiet with changes' '
+	>foo &&
+	git add foo &&
+	git stash push -q >output 2>&1 &&
+	test_must_be_empty output
+'
+
+test_expect_success 'push: -q is quiet with no changes' '
+	git stash push -q >output 2>&1 &&
+	test_must_be_empty output
+'
+
+test_expect_success 'push: -q is quiet even if there is no initial commit' '
+	git init foo_dir &&
+	test_when_finished rm -rf foo_dir &&
+	(
+		cd foo_dir &&
+		>bar &&
+		test_must_fail git stash push -q >output 2>&1 &&
+		test_must_be_empty output
+	)
+'
+
 test_expect_success 'untracked files are left in place when -u is not given' '
 	>file &&
 	git add file &&
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 20/26] stash: convert save to builtin
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (18 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 19/26] stash: make push -q quiet Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 21/26] stash: optimize `get_untracked_files()` and `check_changes()` Paul-Sebastian Ungureanu
                     ` (6 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

Add stash save to the helper and delete functions which are no
longer needed (`show_help()`, `save_stash()`, `push_stash()`,
`create_stash()`, `clear_stash()`, `untracked_files()` and
`no_changes()`).

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 builtin/stash--helper.c |  50 ++++++
 git-stash.sh            | 328 +---------------------------------------
 2 files changed, 52 insertions(+), 326 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 5f9914ca27..19ead63c46 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -26,6 +26,8 @@ static const char * const git_stash_helper_usage[] = {
 	N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
 	   "          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
 	   "          [--] [<pathspec>...]]"),
+	N_("git stash--helper save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+	   "          [-u|--include-untracked] [-a|--all] [<message>]"),
 	NULL
 };
 
@@ -81,6 +83,12 @@ static const char * const git_stash_helper_push_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_save_usage[] = {
+	N_("git stash--helper save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+	   "          [-u|--include-untracked] [-a|--all] [<message>]"),
+	NULL
+};
+
 static const char *ref_stash = "refs/stash";
 static struct strbuf stash_index_path = STRBUF_INIT;
 
@@ -1490,6 +1498,46 @@ static int push_stash(int argc, const char **argv, const char *prefix)
 			     include_untracked);
 }
 
+static int save_stash(int argc, const char **argv, const char *prefix)
+{
+	int keep_index = -1;
+	int patch_mode = 0;
+	int include_untracked = 0;
+	int quiet = 0;
+	int ret = 0;
+	const char *stash_msg = NULL;
+	struct pathspec ps;
+	struct strbuf stash_msg_buf = STRBUF_INIT;
+	struct option options[] = {
+		OPT_BOOL('k', "keep-index", &keep_index,
+			 N_("keep index")),
+		OPT_BOOL('p', "patch", &patch_mode,
+			 N_("stash in patch mode")),
+		OPT__QUIET(&quiet, N_("quiet mode")),
+		OPT_BOOL('u', "include-untracked", &include_untracked,
+			 N_("include untracked files in stash")),
+		OPT_SET_INT('a', "all", &include_untracked,
+			    N_("include ignore files"), 2),
+		OPT_STRING('m', "message", &stash_msg, "message",
+			   N_("stash message")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_save_usage,
+			     PARSE_OPT_KEEP_DASHDASH);
+
+	if (argc)
+		stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');
+
+	memset(&ps, 0, sizeof(ps));
+	ret = do_push_stash(ps, stash_msg, quiet, keep_index,
+			    patch_mode, include_untracked);
+
+	strbuf_release(&stash_msg_buf);
+	return ret;
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -1530,6 +1578,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!create_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "push"))
 		return !!push_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "save"))
+		return !!save_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index 51d7a06601..695f1feba3 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -36,331 +36,6 @@ else
        reset_color=
 fi
 
-no_changes () {
-	git diff-index --quiet --cached HEAD --ignore-submodules -- "$@" &&
-	git diff-files --quiet --ignore-submodules -- "$@" &&
-	(test -z "$untracked" || test -z "$(untracked_files "$@")")
-}
-
-untracked_files () {
-	if test "$1" = "-z"
-	then
-		shift
-		z=-z
-	else
-		z=
-	fi
-	excl_opt=--exclude-standard
-	test "$untracked" = "all" && excl_opt=
-	git ls-files -o $z $excl_opt -- "$@"
-}
-
-prepare_fallback_ident () {
-	if ! git -c user.useconfigonly=yes var GIT_COMMITTER_IDENT >/dev/null 2>&1
-	then
-		GIT_AUTHOR_NAME="git stash"
-		GIT_AUTHOR_EMAIL=git@stash
-		GIT_COMMITTER_NAME="git stash"
-		GIT_COMMITTER_EMAIL=git@stash
-		export GIT_AUTHOR_NAME
-		export GIT_AUTHOR_EMAIL
-		export GIT_COMMITTER_NAME
-		export GIT_COMMITTER_EMAIL
-	fi
-}
-
-clear_stash () {
-	if test $# != 0
-	then
-		die "$(gettext "git stash clear with parameters is unimplemented")"
-	fi
-	if current=$(git rev-parse --verify --quiet $ref_stash)
-	then
-		git update-ref -d $ref_stash $current
-	fi
-}
-
-create_stash () {
-
-	prepare_fallback_ident
-
-	stash_msg=
-	untracked=
-	while test $# != 0
-	do
-		case "$1" in
-		-m|--message)
-			shift
-			stash_msg=${1?"BUG: create_stash () -m requires an argument"}
-			;;
-		-m*)
-			stash_msg=${1#-m}
-			;;
-		--message=*)
-			stash_msg=${1#--message=}
-			;;
-		-u|--include-untracked)
-			shift
-			untracked=${1?"BUG: create_stash () -u requires an argument"}
-			;;
-		--)
-			shift
-			break
-			;;
-		esac
-		shift
-	done
-
-	git update-index -q --refresh
-	if no_changes "$@"
-	then
-		exit 0
-	fi
-
-	# state of the base commit
-	if b_commit=$(git rev-parse --verify HEAD)
-	then
-		head=$(git rev-list --oneline -n 1 HEAD --)
-	else
-		die "$(gettext "You do not have the initial commit yet")"
-	fi
-
-	if branch=$(git symbolic-ref -q HEAD)
-	then
-		branch=${branch#refs/heads/}
-	else
-		branch='(no branch)'
-	fi
-	msg=$(printf '%s: %s' "$branch" "$head")
-
-	# state of the index
-	i_tree=$(git write-tree) &&
-	i_commit=$(printf 'index on %s\n' "$msg" |
-		git commit-tree $i_tree -p $b_commit) ||
-		die "$(gettext "Cannot save the current index state")"
-
-	if test -n "$untracked"
-	then
-		# Untracked files are stored by themselves in a parentless commit, for
-		# ease of unpacking later.
-		u_commit=$(
-			untracked_files -z "$@" | (
-				GIT_INDEX_FILE="$TMPindex" &&
-				export GIT_INDEX_FILE &&
-				rm -f "$TMPindex" &&
-				git update-index -z --add --remove --stdin &&
-				u_tree=$(git write-tree) &&
-				printf 'untracked files on %s\n' "$msg" | git commit-tree $u_tree  &&
-				rm -f "$TMPindex"
-		) ) || die "$(gettext "Cannot save the untracked files")"
-
-		untracked_commit_option="-p $u_commit";
-	else
-		untracked_commit_option=
-	fi
-
-	if test -z "$patch_mode"
-	then
-
-		# state of the working tree
-		w_tree=$( (
-			git read-tree --index-output="$TMPindex" -m $i_tree &&
-			GIT_INDEX_FILE="$TMPindex" &&
-			export GIT_INDEX_FILE &&
-			git diff-index --name-only -z HEAD -- "$@" >"$TMP-stagenames" &&
-			git update-index -z --add --remove --stdin <"$TMP-stagenames" &&
-			git write-tree &&
-			rm -f "$TMPindex"
-		) ) ||
-			die "$(gettext "Cannot save the current worktree state")"
-
-	else
-
-		rm -f "$TMP-index" &&
-		GIT_INDEX_FILE="$TMP-index" git read-tree HEAD &&
-
-		# find out what the user wants
-		GIT_INDEX_FILE="$TMP-index" \
-			git add--interactive --patch=stash -- "$@" &&
-
-		# state of the working tree
-		w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
-		die "$(gettext "Cannot save the current worktree state")"
-
-		git diff-tree -p HEAD $w_tree -- >"$TMP-patch" &&
-		test -s "$TMP-patch" ||
-		die "$(gettext "No changes selected")"
-
-		rm -f "$TMP-index" ||
-		die "$(gettext "Cannot remove temporary index (can't happen)")"
-
-	fi
-
-	# create the stash
-	if test -z "$stash_msg"
-	then
-		stash_msg=$(printf 'WIP on %s' "$msg")
-	else
-		stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
-	fi
-	w_commit=$(printf '%s\n' "$stash_msg" |
-	git commit-tree $w_tree -p $b_commit -p $i_commit $untracked_commit_option) ||
-	die "$(gettext "Cannot record working tree state")"
-}
-
-push_stash () {
-	keep_index=
-	patch_mode=
-	untracked=
-	stash_msg=
-	while test $# != 0
-	do
-		case "$1" in
-		-k|--keep-index)
-			keep_index=t
-			;;
-		--no-keep-index)
-			keep_index=n
-			;;
-		-p|--patch)
-			patch_mode=t
-			# only default to keep if we don't already have an override
-			test -z "$keep_index" && keep_index=t
-			;;
-		-q|--quiet)
-			GIT_QUIET=t
-			;;
-		-u|--include-untracked)
-			untracked=untracked
-			;;
-		-a|--all)
-			untracked=all
-			;;
-		-m|--message)
-			shift
-			test -z ${1+x} && usage
-			stash_msg=$1
-			;;
-		-m*)
-			stash_msg=${1#-m}
-			;;
-		--message=*)
-			stash_msg=${1#--message=}
-			;;
-		--help)
-			show_help
-			;;
-		--)
-			shift
-			break
-			;;
-		-*)
-			option="$1"
-			eval_gettextln "error: unknown option for 'stash push': \$option"
-			usage
-			;;
-		*)
-			break
-			;;
-		esac
-		shift
-	done
-
-	eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")"
-
-	if test -n "$patch_mode" && test -n "$untracked"
-	then
-		die "$(gettext "Can't use --patch and --include-untracked or --all at the same time")"
-	fi
-
-	test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1
-
-	git update-index -q --refresh
-	if no_changes "$@"
-	then
-		say "$(gettext "No local changes to save")"
-		exit 0
-	fi
-
-	git reflog exists $ref_stash ||
-		clear_stash || die "$(gettext "Cannot initialize stash")"
-
-	create_stash -m "$stash_msg" -u "$untracked" -- "$@"
-	git stash--helper store -m "$stash_msg" -q $w_commit ||
-	die "$(gettext "Cannot save the current status")"
-	say "$(eval_gettext "Saved working directory and index state \$stash_msg")"
-
-	if test -z "$patch_mode"
-	then
-		test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
-		if test -n "$untracked" && test $# = 0
-		then
-			git clean --force --quiet -d $CLEAN_X_OPTION
-		fi
-
-		if test $# != 0
-		then
-			test -z "$untracked" && UPDATE_OPTION="-u" || UPDATE_OPTION=
-			test "$untracked" = "all" && FORCE_OPTION="--force" || FORCE_OPTION=
-			git add $UPDATE_OPTION $FORCE_OPTION -- "$@"
-			git diff-index -p --cached --binary HEAD -- "$@" |
-			git apply --index -R
-		else
-			git reset --hard -q
-		fi
-
-		if test "$keep_index" = "t" && test -n "$i_tree"
-		then
-			git read-tree --reset $i_tree
-			git ls-files -z --modified -- "$@" |
-			git checkout-index -z --force --stdin
-		fi
-	else
-		git apply -R < "$TMP-patch" ||
-		die "$(gettext "Cannot remove worktree changes")"
-
-		if test "$keep_index" != "t"
-		then
-			git reset -q -- "$@"
-		fi
-	fi
-}
-
-save_stash () {
-	push_options=
-	while test $# != 0
-	do
-		case "$1" in
-		--)
-			shift
-			break
-			;;
-		-*)
-			# pass all options through to push_stash
-			push_options="$push_options $1"
-			;;
-		*)
-			break
-			;;
-		esac
-		shift
-	done
-
-	stash_msg="$*"
-
-	if test -z "$stash_msg"
-	then
-		push_stash $push_options
-	else
-		push_stash $push_options -m "$stash_msg"
-	fi
-}
-
-show_help () {
-	exec git help stash
-	exit 1
-}
-
 #
 # Parses the remaining options looking for flags and
 # at most one revision defaulting to ${ref_stash}@{0}
@@ -425,7 +100,8 @@ show)
 	;;
 save)
 	shift
-	save_stash "$@"
+	cd "$START_DIR"
+	git stash--helper save "$@"
 	;;
 push)
 	shift
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 21/26] stash: optimize `get_untracked_files()` and `check_changes()`
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (19 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 20/26] stash: convert save to builtin Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2019-01-06 22:47     ` Thomas Gummerer
  2018-12-20 19:44   ` [PATCH v12 22/26] stash: replace all `write-tree` child processes with API calls Paul-Sebastian Ungureanu
                     ` (5 subsequent siblings)
  26 siblings, 1 reply; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

This commits introduces a optimization by avoiding calling the
same functions again. For example, `git stash push -u`
would call at some points the following functions:

 * `check_changes()` (inside `do_push_stash()`)
 * `do_create_stash()`, which calls: `check_changes()` and
`get_untracked_files()`

Note that `check_changes()` also calls `get_untracked_files()`.
So, `check_changes()` is called 2 times and `get_untracked_files()`
3 times.

The old function `check_changes()` now consists of two functions:
`get_untracked_files()` and `check_changes_tracked_files()`.

These are the call chains for `push` and `create`:

 * `push_stash()` -> `do_push_stash()` -> `do_create_stash()`

 * `create_stash()` -> `do_create_stash()`

To prevent calling the same functions over and over again,
`check_changes()` inside `do_create_stash()` is now placed
in the caller functions (`create_stash()` and `do_push_stash()`).
This way `check_changes()` and `get_untracked files()` are called
only one time.

https://public-inbox.org/git/20180818223329.GJ11326@hank.intra.tgummerer.com/

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 builtin/stash--helper.c | 53 +++++++++++++++++++++++------------------
 1 file changed, 30 insertions(+), 23 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 19ead63c46..4b63352927 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -884,18 +884,18 @@ static int get_untracked_files(struct pathspec ps, int include_untracked,
 }
 
 /*
- * The return value of `check_changes()` can be:
+ * The return value of `check_changes_tracked_files()` can be:
  *
  * < 0 if there was an error
  * = 0 if there are no changes.
  * > 0 if there are changes.
  */
-static int check_changes(struct pathspec ps, int include_untracked)
+
+static int check_changes_tracked_files(struct pathspec ps)
 {
 	int result;
 	struct rev_info rev;
 	struct object_id dummy;
-	struct strbuf out = STRBUF_INIT;
 
 	/* No initial commit. */
 	if (get_oid("HEAD", &dummy))
@@ -923,14 +923,26 @@ static int check_changes(struct pathspec ps, int include_untracked)
 	if (diff_result_code(&rev.diffopt, result))
 		return 1;
 
+	return 0;
+}
+
+/*
+ * The function will fill `untracked_files` with the names of untracked files
+ * It will return 1 if there were any changes and 0 if there were not.
+ */
+
+static int check_changes(struct pathspec ps, int include_untracked,
+			 struct strbuf *untracked_files)
+{
+	int ret = 0;
+	if (check_changes_tracked_files(ps))
+		ret = 1;
+
 	if (include_untracked && get_untracked_files(ps, include_untracked,
-						     &out)) {
-		strbuf_release(&out);
-		return 1;
-	}
+						     untracked_files))
+		ret = 1;
 
-	strbuf_release(&out);
-	return 0;
+	return ret;
 }
 
 static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
@@ -1141,7 +1153,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 		head_commit = lookup_commit(the_repository, &info->b_commit);
 	}
 
-	if (!check_changes(ps, include_untracked)) {
+	if (!check_changes(ps, include_untracked, &untracked_files)) {
 		ret = 1;
 		goto done;
 	}
@@ -1166,8 +1178,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 		goto done;
 	}
 
-	if (include_untracked && get_untracked_files(ps, include_untracked,
-						     &untracked_files)) {
+	if (include_untracked) {
 		if (save_untracked_files(info, &msg, untracked_files)) {
 			if (!quiet)
 				fprintf_ln(stderr, _("Cannot save "
@@ -1252,20 +1263,15 @@ static int create_stash(int argc, const char **argv, const char *prefix)
 			     0);
 
 	memset(&ps, 0, sizeof(ps));
-	strbuf_addstr(&stash_msg_buf, stash_msg);
-	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
-			      NULL, 0);
+	if (!check_changes_tracked_files(ps))
+		return 0;
 
-	if (!ret)
+	strbuf_addstr(&stash_msg_buf, stash_msg);
+	if (!(ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info, NULL, 0)))
 		printf_ln("%s", oid_to_hex(&info.w_commit));
 
 	strbuf_release(&stash_msg_buf);
-
-	/*
-	 * ret can be 1 if there were no changes. In this case, we should
-	 * not error out.
-	 */
-	return ret < 0;
+	return ret;
 }
 
 static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
@@ -1275,6 +1281,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
 	struct stash_info info;
 	struct strbuf patch = STRBUF_INIT;
 	struct strbuf stash_msg_buf = STRBUF_INIT;
+	struct strbuf untracked_files = STRBUF_INIT;
 
 	if (patch_mode && keep_index == -1)
 		keep_index = 1;
@@ -1309,7 +1316,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
 		goto done;
 	}
 
-	if (!check_changes(ps, include_untracked)) {
+	if (!check_changes(ps, include_untracked, &untracked_files)) {
 		if (!quiet)
 			printf_ln(_("No local changes to save"));
 		goto done;
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 22/26] stash: replace all `write-tree` child processes with API calls
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (20 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 21/26] stash: optimize `get_untracked_files()` and `check_changes()` Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 23/26] stash: convert `stash--helper.c` into `stash.c` Paul-Sebastian Ungureanu
                     ` (4 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

This commit replaces spawning `git write-tree` with API calls.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 builtin/stash--helper.c | 41 ++++++++++++-----------------------------
 1 file changed, 12 insertions(+), 29 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 4b63352927..39fcff32b7 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -950,9 +950,8 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
 {
 	int ret = 0;
 	struct strbuf untracked_msg = STRBUF_INIT;
-	struct strbuf out = STRBUF_INIT;
 	struct child_process cp_upd_index = CHILD_PROCESS_INIT;
-	struct child_process cp_write_tree = CHILD_PROCESS_INIT;
+	struct index_state istate = { NULL };
 
 	cp_upd_index.git_cmd = 1;
 	argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add",
@@ -967,15 +966,11 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
 		goto done;
 	}
 
-	cp_write_tree.git_cmd = 1;
-	argv_array_push(&cp_write_tree.args, "write-tree");
-	argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s",
-			 stash_index_path.buf);
-	if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) {
+	if (write_index_as_tree(&info->u_tree, &istate, stash_index_path.buf, 0,
+				NULL)) {
 		ret = -1;
 		goto done;
 	}
-	get_oid_hex(out.buf, &info->u_tree);
 
 	if (commit_tree(untracked_msg.buf, untracked_msg.len,
 			&info->u_tree, NULL, &info->u_commit, NULL, NULL)) {
@@ -984,8 +979,8 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
 	}
 
 done:
+	discard_index(&istate);
 	strbuf_release(&untracked_msg);
-	strbuf_release(&out);
 	remove_path(stash_index_path.buf);
 	return ret;
 }
@@ -994,11 +989,10 @@ static int stash_patch(struct stash_info *info, struct pathspec ps,
 		       struct strbuf *out_patch, int quiet)
 {
 	int ret = 0;
-	struct strbuf out = STRBUF_INIT;
 	struct child_process cp_read_tree = CHILD_PROCESS_INIT;
 	struct child_process cp_add_i = CHILD_PROCESS_INIT;
-	struct child_process cp_write_tree = CHILD_PROCESS_INIT;
 	struct child_process cp_diff_tree = CHILD_PROCESS_INIT;
+	struct index_state istate = { NULL };
 
 	remove_path(stash_index_path.buf);
 
@@ -1024,17 +1018,12 @@ static int stash_patch(struct stash_info *info, struct pathspec ps,
 	}
 
 	/* State of the working tree. */
-	cp_write_tree.git_cmd = 1;
-	argv_array_push(&cp_write_tree.args, "write-tree");
-	argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s",
-			 stash_index_path.buf);
-	if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) {
+	if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0,
+				NULL)) {
 		ret = -1;
 		goto done;
 	}
 
-	get_oid_hex(out.buf, &info->w_tree);
-
 	cp_diff_tree.git_cmd = 1;
 	argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD",
 			 oid_to_hex(&info->w_tree), "--", NULL);
@@ -1050,7 +1039,7 @@ static int stash_patch(struct stash_info *info, struct pathspec ps,
 	}
 
 done:
-	strbuf_release(&out);
+	discard_index(&istate);
 	remove_path(stash_index_path.buf);
 	return ret;
 }
@@ -1060,9 +1049,8 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
 	int ret = 0;
 	struct rev_info rev;
 	struct child_process cp_upd_index = CHILD_PROCESS_INIT;
-	struct child_process cp_write_tree = CHILD_PROCESS_INIT;
-	struct strbuf out = STRBUF_INIT;
 	struct strbuf diff_output = STRBUF_INIT;
+	struct index_state istate = { NULL };
 
 	set_alternate_index_output(stash_index_path.buf);
 	if (reset_tree(&info->i_tree, 0, 0)) {
@@ -1101,20 +1089,15 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
 		goto done;
 	}
 
-	cp_write_tree.git_cmd = 1;
-	argv_array_push(&cp_write_tree.args, "write-tree");
-	argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s",
-			 stash_index_path.buf);
-	if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) {
+	if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0,
+				NULL)) {
 		ret = -1;
 		goto done;
 	}
 
-	get_oid_hex(out.buf, &info->w_tree);
-
 done:
+	discard_index(&istate);
 	UNLEAK(rev);
-	strbuf_release(&out);
 	object_array_clear(&rev.pending);
 	strbuf_release(&diff_output);
 	remove_path(stash_index_path.buf);
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 23/26] stash: convert `stash--helper.c` into `stash.c`
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (21 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 22/26] stash: replace all `write-tree` child processes with API calls Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 24/26] stash: add back the original, scripted `git stash` Paul-Sebastian Ungureanu
                     ` (3 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

The old shell script `git-stash.sh`  was removed and replaced
entirely by `builtin/stash.c`. In order to do that, `create` and
`push` were adapted to work without `stash.sh`. For example, before
this commit, `git stash create` called `git stash--helper create
--message "$*"`. If it called `git stash--helper create "$@"`, then
some of these changes wouldn't have been necessary.

This commit also removes the word `helper` since now stash is
called directly and not by a shell script.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 .gitignore                           |   1 -
 Makefile                             |   3 +-
 builtin.h                            |   2 +-
 builtin/{stash--helper.c => stash.c} | 154 +++++++++++++++------------
 git-stash.sh                         | 153 --------------------------
 git.c                                |   2 +-
 6 files changed, 91 insertions(+), 224 deletions(-)
 rename builtin/{stash--helper.c => stash.c} (91%)
 delete mode 100755 git-stash.sh

diff --git a/.gitignore b/.gitignore
index 6ecab90ab2..0d77ea5894 100644
--- a/.gitignore
+++ b/.gitignore
@@ -162,7 +162,6 @@
 /git-show-ref
 /git-stage
 /git-stash
-/git-stash--helper
 /git-status
 /git-stripspace
 /git-submodule
diff --git a/Makefile b/Makefile
index c246fc7078..8cee2731aa 100644
--- a/Makefile
+++ b/Makefile
@@ -619,7 +619,6 @@ SCRIPT_SH += git-quiltimport.sh
 SCRIPT_SH += git-legacy-rebase.sh
 SCRIPT_SH += git-remote-testgit.sh
 SCRIPT_SH += git-request-pull.sh
-SCRIPT_SH += git-stash.sh
 SCRIPT_SH += git-submodule.sh
 SCRIPT_SH += git-web--browse.sh
 
@@ -1117,7 +1116,7 @@ BUILTIN_OBJS += builtin/shortlog.o
 BUILTIN_OBJS += builtin/show-branch.o
 BUILTIN_OBJS += builtin/show-index.o
 BUILTIN_OBJS += builtin/show-ref.o
-BUILTIN_OBJS += builtin/stash--helper.o
+BUILTIN_OBJS += builtin/stash.o
 BUILTIN_OBJS += builtin/stripspace.o
 BUILTIN_OBJS += builtin/submodule--helper.o
 BUILTIN_OBJS += builtin/symbolic-ref.o
diff --git a/builtin.h b/builtin.h
index ff4460aff7..b78ab6e30b 100644
--- a/builtin.h
+++ b/builtin.h
@@ -225,7 +225,7 @@ extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_show_index(int argc, const char **argv, const char *prefix);
 extern int cmd_status(int argc, const char **argv, const char *prefix);
-extern int cmd_stash__helper(int argc, const char **argv, const char *prefix);
+extern int cmd_stash(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
diff --git a/builtin/stash--helper.c b/builtin/stash.c
similarity index 91%
rename from builtin/stash--helper.c
rename to builtin/stash.c
index 39fcff32b7..fe32ff42fd 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash.c
@@ -16,75 +16,70 @@
 
 #define INCLUDE_ALL_FILES 2
 
-static const char * const git_stash_helper_usage[] = {
-	N_("git stash--helper list [<options>]"),
-	N_("git stash--helper show [<options>] [<stash>]"),
-	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
-	N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
-	N_("git stash--helper branch <branchname> [<stash>]"),
-	N_("git stash--helper clear"),
-	N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+static const char * const git_stash_usage[] = {
+	N_("git stash list [<options>]"),
+	N_("git stash show [<options>] [<stash>]"),
+	N_("git stash drop [-q|--quiet] [<stash>]"),
+	N_("git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
+	N_("git stash branch <branchname> [<stash>]"),
+	N_("git stash clear"),
+	N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
 	   "          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
 	   "          [--] [<pathspec>...]]"),
-	N_("git stash--helper save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+	N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
 	   "          [-u|--include-untracked] [-a|--all] [<message>]"),
 	NULL
 };
 
-static const char * const git_stash_helper_list_usage[] = {
-	N_("git stash--helper list [<options>]"),
+static const char * const git_stash_list_usage[] = {
+	N_("git stash list [<options>]"),
 	NULL
 };
 
-static const char * const git_stash_helper_show_usage[] = {
-	N_("git stash--helper show [<options>] [<stash>]"),
+static const char * const git_stash_show_usage[] = {
+	N_("git stash show [<options>] [<stash>]"),
 	NULL
 };
 
-static const char * const git_stash_helper_drop_usage[] = {
-	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
+static const char * const git_stash_drop_usage[] = {
+	N_("git stash drop [-q|--quiet] [<stash>]"),
 	NULL
 };
 
-static const char * const git_stash_helper_pop_usage[] = {
-	N_("git stash--helper pop [--index] [-q|--quiet] [<stash>]"),
+static const char * const git_stash_pop_usage[] = {
+	N_("git stash pop [--index] [-q|--quiet] [<stash>]"),
 	NULL
 };
 
-static const char * const git_stash_helper_apply_usage[] = {
-	N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
+static const char * const git_stash_apply_usage[] = {
+	N_("git stash apply [--index] [-q|--quiet] [<stash>]"),
 	NULL
 };
 
-static const char * const git_stash_helper_branch_usage[] = {
-	N_("git stash--helper branch <branchname> [<stash>]"),
+static const char * const git_stash_branch_usage[] = {
+	N_("git stash branch <branchname> [<stash>]"),
 	NULL
 };
 
-static const char * const git_stash_helper_clear_usage[] = {
-	N_("git stash--helper clear"),
+static const char * const git_stash_clear_usage[] = {
+	N_("git stash clear"),
 	NULL
 };
 
-static const char * const git_stash_helper_store_usage[] = {
-	N_("git stash--helper store [-m|--message <message>] [-q|--quiet] <commit>"),
+static const char * const git_stash_store_usage[] = {
+	N_("git stash store [-m|--message <message>] [-q|--quiet] <commit>"),
 	NULL
 };
 
-static const char * const git_stash_helper_create_usage[] = {
-	N_("git stash--helper create [<message>]"),
-	NULL
-};
-
-static const char * const git_stash_helper_push_usage[] = {
-	N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+static const char * const git_stash_push_usage[] = {
+	N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
 	   "          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
 	   "          [--] [<pathspec>...]]"),
 	NULL
 };
 
-static const char * const git_stash_helper_save_usage[] = {
-	N_("git stash--helper save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+static const char * const git_stash_save_usage[] = {
+	N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
 	   "          [-u|--include-untracked] [-a|--all] [<message>]"),
 	NULL
 };
@@ -221,7 +216,7 @@ static int clear_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_clear_usage,
+			     git_stash_clear_usage,
 			     PARSE_OPT_STOP_AT_NON_OPTION);
 
 	if (argc)
@@ -526,7 +521,7 @@ static int apply_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_apply_usage, 0);
+			     git_stash_apply_usage, 0);
 
 	if (get_stash_info(&info, argc, argv))
 		return -1;
@@ -599,7 +594,7 @@ static int drop_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_drop_usage, 0);
+			     git_stash_drop_usage, 0);
 
 	if (get_stash_info(&info, argc, argv))
 		return -1;
@@ -625,7 +620,7 @@ static int pop_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_pop_usage, 0);
+			     git_stash_pop_usage, 0);
 
 	if (get_stash_info(&info, argc, argv))
 		return -1;
@@ -652,7 +647,7 @@ static int branch_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_branch_usage, 0);
+			     git_stash_branch_usage, 0);
 
 	if (!argc) {
 		fprintf_ln(stderr, _("No branch name specified"));
@@ -687,7 +682,7 @@ static int list_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_list_usage,
+			     git_stash_list_usage,
 			     PARSE_OPT_KEEP_UNKNOWN);
 
 	if (!ref_exists(ref_stash))
@@ -767,7 +762,7 @@ static int show_stash(int argc, const char **argv, const char *prefix)
 	argc = setup_revisions(argc, argv, &rev, NULL);
 	if (argc > 1) {
 		free_stash_info(&info);
-		usage_with_options(git_stash_helper_show_usage, options);
+		usage_with_options(git_stash_show_usage, options);
 	}
 
 	rev.diffopt.flags.recursive = 1;
@@ -813,7 +808,7 @@ static int store_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_store_usage,
+			     git_stash_store_usage,
 			     PARSE_OPT_KEEP_UNKNOWN);
 
 	if (argc != 1) {
@@ -1227,29 +1222,18 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 
 static int create_stash(int argc, const char **argv, const char *prefix)
 {
-	int include_untracked = 0;
 	int ret = 0;
-	const char *stash_msg = NULL;
 	struct strbuf stash_msg_buf = STRBUF_INIT;
 	struct stash_info info;
 	struct pathspec ps;
-	struct option options[] = {
-		OPT_BOOL('u', "include-untracked", &include_untracked,
-			 N_("include untracked files in stash")),
-		OPT_STRING('m', "message", &stash_msg, N_("message"),
-			 N_("stash message")),
-		OPT_END()
-	};
 
-	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_create_usage,
-			     0);
+	/* Starting with argv[1], since argv[0] is "create" */
+	strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' ');
 
 	memset(&ps, 0, sizeof(ps));
 	if (!check_changes_tracked_files(ps))
 		return 0;
 
-	strbuf_addstr(&stash_msg_buf, stash_msg);
 	if (!(ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info, NULL, 0)))
 		printf_ln("%s", oid_to_hex(&info.w_commit));
 
@@ -1479,9 +1463,10 @@ static int push_stash(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
-	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_push_usage,
-			     0);
+	if (argc)
+		argc = parse_options(argc, argv, prefix, options,
+				     git_stash_push_usage,
+				     0);
 
 	parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL, prefix, argv);
 	return do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode,
@@ -1514,7 +1499,7 @@ static int save_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_save_usage,
+			     git_stash_save_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
 	if (argc)
@@ -1528,10 +1513,12 @@ static int save_stash(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
-int cmd_stash__helper(int argc, const char **argv, const char *prefix)
+int cmd_stash(int argc, const char **argv, const char *prefix)
 {
+	int i = -1;
 	pid_t pid = getpid();
 	const char *index_file;
+	struct argv_array args = ARGV_ARRAY_INIT;
 
 	struct option options[] = {
 		OPT_END()
@@ -1539,16 +1526,16 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 
 	git_config(git_diff_basic_config, NULL);
 
-	argc = parse_options(argc, argv, prefix, options, git_stash_helper_usage,
+	argc = parse_options(argc, argv, prefix, options, git_stash_usage,
 			     PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
 
 	index_file = get_index_file();
 	strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file,
 		    (uintmax_t)pid);
 
-	if (argc < 1)
-		usage_with_options(git_stash_helper_usage, options);
-	if (!strcmp(argv[0], "apply"))
+	if (!argc)
+		return !!push_stash(0, NULL, prefix);
+	else if (!strcmp(argv[0], "apply"))
 		return !!apply_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "clear"))
 		return !!clear_stash(argc, argv, prefix);
@@ -1570,7 +1557,42 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!push_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "save"))
 		return !!save_stash(argc, argv, prefix);
+	else if (*argv[0] != '-')
+		usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
+			      git_stash_usage, options);
+
+	if (strcmp(argv[0], "-p")) {
+		while (++i < argc && strcmp(argv[i], "--")) {
+			/*
+			 * `akpqu` is a string which contains all short options,
+			 * except `-m` which is verified separately.
+			 */
+			if ((strlen(argv[i]) == 2) && *argv[i] == '-' &&
+			    strchr("akpqu", argv[i][1]))
+				continue;
+
+			if (!strcmp(argv[i], "--all") ||
+			    !strcmp(argv[i], "--keep-index") ||
+			    !strcmp(argv[i], "--no-keep-index") ||
+			    !strcmp(argv[i], "--patch") ||
+			    !strcmp(argv[i], "--quiet") ||
+			    !strcmp(argv[i], "--include-untracked"))
+				continue;
+
+			/*
+			 * `-m` and `--message=` are verified separately because
+			 * they need to be immediately followed by a string
+			 * (i.e.`-m"foobar"` or `--message="foobar"`).
+			 */
+			if (starts_with(argv[i], "-m") ||
+			    starts_with(argv[i], "--message="))
+				continue;
+
+			usage_with_options(git_stash_usage, options);
+		}
+	}
 
-	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
-		      git_stash_helper_usage, options);
+	argv_array_push(&args, "push");
+	argv_array_pushv(&args, argv);
+	return !!push_stash(args.argc, args.argv, prefix);
 }
diff --git a/git-stash.sh b/git-stash.sh
deleted file mode 100755
index 695f1feba3..0000000000
--- a/git-stash.sh
+++ /dev/null
@@ -1,153 +0,0 @@
-#!/bin/sh
-# Copyright (c) 2007, Nanako Shiraishi
-
-dashless=$(basename "$0" | sed -e 's/-/ /')
-USAGE="list [<options>]
-   or: $dashless show [<stash>]
-   or: $dashless drop [-q|--quiet] [<stash>]
-   or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
-   or: $dashless branch <branchname> [<stash>]
-   or: $dashless save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
-		      [-u|--include-untracked] [-a|--all] [<message>]
-   or: $dashless [push [--patch] [-k|--[no-]keep-index] [-q|--quiet]
-		       [-u|--include-untracked] [-a|--all] [-m <message>]
-		       [-- <pathspec>...]]
-   or: $dashless clear"
-
-SUBDIRECTORY_OK=Yes
-OPTIONS_SPEC=
-START_DIR=$(pwd)
-. git-sh-setup
-require_work_tree
-prefix=$(git rev-parse --show-prefix) || exit 1
-cd_to_toplevel
-
-TMP="$GIT_DIR/.git-stash.$$"
-TMPindex=${GIT_INDEX_FILE-"$(git rev-parse --git-path index)"}.stash.$$
-trap 'rm -f "$TMP-"* "$TMPindex"' 0
-
-ref_stash=refs/stash
-
-if git config --get-colorbool color.interactive; then
-       help_color="$(git config --get-color color.interactive.help 'red bold')"
-       reset_color="$(git config --get-color '' reset)"
-else
-       help_color=
-       reset_color=
-fi
-
-#
-# Parses the remaining options looking for flags and
-# at most one revision defaulting to ${ref_stash}@{0}
-# if none found.
-#
-# Derives related tree and commit objects from the
-# revision, if one is found.
-#
-# stash records the work tree, and is a merge between the
-# base commit (first parent) and the index tree (second parent).
-#
-#   REV is set to the symbolic version of the specified stash-like commit
-#   IS_STASH_LIKE is non-blank if ${REV} looks like a stash
-#   IS_STASH_REF is non-blank if the ${REV} looks like a stash ref
-#   s is set to the SHA1 of the stash commit
-#   w_commit is set to the commit containing the working tree
-#   b_commit is set to the base commit
-#   i_commit is set to the commit containing the index tree
-#   u_commit is set to the commit containing the untracked files tree
-#   w_tree is set to the working tree
-#   b_tree is set to the base tree
-#   i_tree is set to the index tree
-#   u_tree is set to the untracked files tree
-#
-#   GIT_QUIET is set to t if -q is specified
-#   INDEX_OPTION is set to --index if --index is specified.
-#   FLAGS is set to the remaining flags (if allowed)
-#
-# dies if:
-#   * too many revisions specified
-#   * no revision is specified and there is no stash stack
-#   * a revision is specified which cannot be resolve to a SHA1
-#   * a non-existent stash reference is specified
-#   * unknown flags were set and ALLOW_UNKNOWN_FLAGS is not "t"
-#
-
-test "$1" = "-p" && set "push" "$@"
-
-PARSE_CACHE='--not-parsed'
-# The default command is "push" if nothing but options are given
-seen_non_option=
-for opt
-do
-	case "$opt" in
-	--) break ;;
-	-*) ;;
-	*) seen_non_option=t; break ;;
-	esac
-done
-
-test -n "$seen_non_option" || set "push" "$@"
-
-# Main command set
-case "$1" in
-list)
-	shift
-	git stash--helper list "$@"
-	;;
-show)
-	shift
-	git stash--helper show "$@"
-	;;
-save)
-	shift
-	cd "$START_DIR"
-	git stash--helper save "$@"
-	;;
-push)
-	shift
-	cd "$START_DIR"
-	git stash--helper push "$@"
-	;;
-apply)
-	shift
-	cd "$START_DIR"
-	git stash--helper apply "$@"
-	;;
-clear)
-	shift
-	git stash--helper clear "$@"
-	;;
-create)
-	shift
-	git stash--helper create --message "$*"
-	;;
-store)
-	shift
-	git stash--helper store "$@"
-	;;
-drop)
-	shift
-	git stash--helper drop "$@"
-	;;
-pop)
-	shift
-	cd "$START_DIR"
-	git stash--helper pop "$@"
-	;;
-branch)
-	shift
-	cd "$START_DIR"
-	git stash--helper branch "$@"
-	;;
-*)
-	case $# in
-	0)
-		cd "$START_DIR"
-		git stash--helper push &&
-		say "$(gettext "(To restore them type \"git stash apply\")")"
-		;;
-	*)
-		usage
-	esac
-	;;
-esac
diff --git a/git.c b/git.c
index de9c774573..8a20909eae 100644
--- a/git.c
+++ b/git.c
@@ -554,7 +554,7 @@ static struct cmd_struct commands[] = {
 	{ "show-index", cmd_show_index },
 	{ "show-ref", cmd_show_ref, RUN_SETUP },
 	{ "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
-	{ "stash--helper", cmd_stash__helper, RUN_SETUP | NEED_WORK_TREE },
+	{ "stash", cmd_stash, RUN_SETUP | NEED_WORK_TREE },
 	{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 	{ "stripspace", cmd_stripspace },
 	{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 24/26] stash: add back the original, scripted `git stash`
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (22 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 23/26] stash: convert `stash--helper.c` into `stash.c` Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2018-12-20 19:44   ` [PATCH v12 25/26] stash: optionally use the scripted version again Paul-Sebastian Ungureanu
                     ` (2 subsequent siblings)
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This simply copies the version as of sd/stash-wo-user-name verbatim. As
of now, it is not hooked up.

The next commit will change the builtin `stash` to hand off to the
scripted `git stash` when `stash.useBuiltin=false`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 git-stash.sh | 769 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 769 insertions(+)
 create mode 100755 git-stash.sh

diff --git a/git-stash.sh b/git-stash.sh
new file mode 100755
index 0000000000..789ce2f41d
--- /dev/null
+++ b/git-stash.sh
@@ -0,0 +1,769 @@
+#!/bin/sh
+# Copyright (c) 2007, Nanako Shiraishi
+
+dashless=$(basename "$0" | sed -e 's/-/ /')
+USAGE="list [<options>]
+   or: $dashless show [<stash>]
+   or: $dashless drop [-q|--quiet] [<stash>]
+   or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
+   or: $dashless branch <branchname> [<stash>]
+   or: $dashless save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
+		      [-u|--include-untracked] [-a|--all] [<message>]
+   or: $dashless [push [--patch] [-k|--[no-]keep-index] [-q|--quiet]
+		       [-u|--include-untracked] [-a|--all] [-m <message>]
+		       [-- <pathspec>...]]
+   or: $dashless clear"
+
+SUBDIRECTORY_OK=Yes
+OPTIONS_SPEC=
+START_DIR=$(pwd)
+. git-sh-setup
+require_work_tree
+prefix=$(git rev-parse --show-prefix) || exit 1
+cd_to_toplevel
+
+TMP="$GIT_DIR/.git-stash.$$"
+TMPindex=${GIT_INDEX_FILE-"$(git rev-parse --git-path index)"}.stash.$$
+trap 'rm -f "$TMP-"* "$TMPindex"' 0
+
+ref_stash=refs/stash
+
+if git config --get-colorbool color.interactive; then
+       help_color="$(git config --get-color color.interactive.help 'red bold')"
+       reset_color="$(git config --get-color '' reset)"
+else
+       help_color=
+       reset_color=
+fi
+
+no_changes () {
+	git diff-index --quiet --cached HEAD --ignore-submodules -- "$@" &&
+	git diff-files --quiet --ignore-submodules -- "$@" &&
+	(test -z "$untracked" || test -z "$(untracked_files "$@")")
+}
+
+untracked_files () {
+	if test "$1" = "-z"
+	then
+		shift
+		z=-z
+	else
+		z=
+	fi
+	excl_opt=--exclude-standard
+	test "$untracked" = "all" && excl_opt=
+	git ls-files -o $z $excl_opt -- "$@"
+}
+
+prepare_fallback_ident () {
+	if ! git -c user.useconfigonly=yes var GIT_COMMITTER_IDENT >/dev/null 2>&1
+	then
+		GIT_AUTHOR_NAME="git stash"
+		GIT_AUTHOR_EMAIL=git@stash
+		GIT_COMMITTER_NAME="git stash"
+		GIT_COMMITTER_EMAIL=git@stash
+		export GIT_AUTHOR_NAME
+		export GIT_AUTHOR_EMAIL
+		export GIT_COMMITTER_NAME
+		export GIT_COMMITTER_EMAIL
+	fi
+}
+
+clear_stash () {
+	if test $# != 0
+	then
+		die "$(gettext "git stash clear with parameters is unimplemented")"
+	fi
+	if current=$(git rev-parse --verify --quiet $ref_stash)
+	then
+		git update-ref -d $ref_stash $current
+	fi
+}
+
+create_stash () {
+
+	prepare_fallback_ident
+
+	stash_msg=
+	untracked=
+	while test $# != 0
+	do
+		case "$1" in
+		-m|--message)
+			shift
+			stash_msg=${1?"BUG: create_stash () -m requires an argument"}
+			;;
+		-m*)
+			stash_msg=${1#-m}
+			;;
+		--message=*)
+			stash_msg=${1#--message=}
+			;;
+		-u|--include-untracked)
+			shift
+			untracked=${1?"BUG: create_stash () -u requires an argument"}
+			;;
+		--)
+			shift
+			break
+			;;
+		esac
+		shift
+	done
+
+	git update-index -q --refresh
+	if no_changes "$@"
+	then
+		exit 0
+	fi
+
+	# state of the base commit
+	if b_commit=$(git rev-parse --verify HEAD)
+	then
+		head=$(git rev-list --oneline -n 1 HEAD --)
+	else
+		die "$(gettext "You do not have the initial commit yet")"
+	fi
+
+	if branch=$(git symbolic-ref -q HEAD)
+	then
+		branch=${branch#refs/heads/}
+	else
+		branch='(no branch)'
+	fi
+	msg=$(printf '%s: %s' "$branch" "$head")
+
+	# state of the index
+	i_tree=$(git write-tree) &&
+	i_commit=$(printf 'index on %s\n' "$msg" |
+		git commit-tree $i_tree -p $b_commit) ||
+		die "$(gettext "Cannot save the current index state")"
+
+	if test -n "$untracked"
+	then
+		# Untracked files are stored by themselves in a parentless commit, for
+		# ease of unpacking later.
+		u_commit=$(
+			untracked_files -z "$@" | (
+				GIT_INDEX_FILE="$TMPindex" &&
+				export GIT_INDEX_FILE &&
+				rm -f "$TMPindex" &&
+				git update-index -z --add --remove --stdin &&
+				u_tree=$(git write-tree) &&
+				printf 'untracked files on %s\n' "$msg" | git commit-tree $u_tree  &&
+				rm -f "$TMPindex"
+		) ) || die "$(gettext "Cannot save the untracked files")"
+
+		untracked_commit_option="-p $u_commit";
+	else
+		untracked_commit_option=
+	fi
+
+	if test -z "$patch_mode"
+	then
+
+		# state of the working tree
+		w_tree=$( (
+			git read-tree --index-output="$TMPindex" -m $i_tree &&
+			GIT_INDEX_FILE="$TMPindex" &&
+			export GIT_INDEX_FILE &&
+			git diff-index --name-only -z HEAD -- "$@" >"$TMP-stagenames" &&
+			git update-index -z --add --remove --stdin <"$TMP-stagenames" &&
+			git write-tree &&
+			rm -f "$TMPindex"
+		) ) ||
+			die "$(gettext "Cannot save the current worktree state")"
+
+	else
+
+		rm -f "$TMP-index" &&
+		GIT_INDEX_FILE="$TMP-index" git read-tree HEAD &&
+
+		# find out what the user wants
+		GIT_INDEX_FILE="$TMP-index" \
+			git add--interactive --patch=stash -- "$@" &&
+
+		# state of the working tree
+		w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
+		die "$(gettext "Cannot save the current worktree state")"
+
+		git diff-tree -p HEAD $w_tree -- >"$TMP-patch" &&
+		test -s "$TMP-patch" ||
+		die "$(gettext "No changes selected")"
+
+		rm -f "$TMP-index" ||
+		die "$(gettext "Cannot remove temporary index (can't happen)")"
+
+	fi
+
+	# create the stash
+	if test -z "$stash_msg"
+	then
+		stash_msg=$(printf 'WIP on %s' "$msg")
+	else
+		stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
+	fi
+	w_commit=$(printf '%s\n' "$stash_msg" |
+	git commit-tree $w_tree -p $b_commit -p $i_commit $untracked_commit_option) ||
+	die "$(gettext "Cannot record working tree state")"
+}
+
+store_stash () {
+	while test $# != 0
+	do
+		case "$1" in
+		-m|--message)
+			shift
+			stash_msg="$1"
+			;;
+		-m*)
+			stash_msg=${1#-m}
+			;;
+		--message=*)
+			stash_msg=${1#--message=}
+			;;
+		-q|--quiet)
+			quiet=t
+			;;
+		*)
+			break
+			;;
+		esac
+		shift
+	done
+	test $# = 1 ||
+	die "$(eval_gettext "\"$dashless store\" requires one <commit> argument")"
+
+	w_commit="$1"
+	if test -z "$stash_msg"
+	then
+		stash_msg="Created via \"git stash store\"."
+	fi
+
+	git update-ref --create-reflog -m "$stash_msg" $ref_stash $w_commit
+	ret=$?
+	test $ret != 0 && test -z "$quiet" &&
+	die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")"
+	return $ret
+}
+
+push_stash () {
+	keep_index=
+	patch_mode=
+	untracked=
+	stash_msg=
+	while test $# != 0
+	do
+		case "$1" in
+		-k|--keep-index)
+			keep_index=t
+			;;
+		--no-keep-index)
+			keep_index=n
+			;;
+		-p|--patch)
+			patch_mode=t
+			# only default to keep if we don't already have an override
+			test -z "$keep_index" && keep_index=t
+			;;
+		-q|--quiet)
+			GIT_QUIET=t
+			;;
+		-u|--include-untracked)
+			untracked=untracked
+			;;
+		-a|--all)
+			untracked=all
+			;;
+		-m|--message)
+			shift
+			test -z ${1+x} && usage
+			stash_msg=$1
+			;;
+		-m*)
+			stash_msg=${1#-m}
+			;;
+		--message=*)
+			stash_msg=${1#--message=}
+			;;
+		--help)
+			show_help
+			;;
+		--)
+			shift
+			break
+			;;
+		-*)
+			option="$1"
+			eval_gettextln "error: unknown option for 'stash push': \$option"
+			usage
+			;;
+		*)
+			break
+			;;
+		esac
+		shift
+	done
+
+	eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")"
+
+	if test -n "$patch_mode" && test -n "$untracked"
+	then
+		die "$(gettext "Can't use --patch and --include-untracked or --all at the same time")"
+	fi
+
+	test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1
+
+	git update-index -q --refresh
+	if no_changes "$@"
+	then
+		say "$(gettext "No local changes to save")"
+		exit 0
+	fi
+
+	git reflog exists $ref_stash ||
+		clear_stash || die "$(gettext "Cannot initialize stash")"
+
+	create_stash -m "$stash_msg" -u "$untracked" -- "$@"
+	store_stash -m "$stash_msg" -q $w_commit ||
+	die "$(gettext "Cannot save the current status")"
+	say "$(eval_gettext "Saved working directory and index state \$stash_msg")"
+
+	if test -z "$patch_mode"
+	then
+		test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
+		if test -n "$untracked" && test $# = 0
+		then
+			git clean --force --quiet -d $CLEAN_X_OPTION
+		fi
+
+		if test $# != 0
+		then
+			test -z "$untracked" && UPDATE_OPTION="-u" || UPDATE_OPTION=
+			test "$untracked" = "all" && FORCE_OPTION="--force" || FORCE_OPTION=
+			git add $UPDATE_OPTION $FORCE_OPTION -- "$@"
+			git diff-index -p --cached --binary HEAD -- "$@" |
+			git apply --index -R
+		else
+			git reset --hard -q
+		fi
+
+		if test "$keep_index" = "t" && test -n "$i_tree"
+		then
+			git read-tree --reset $i_tree
+			git ls-files -z --modified -- "$@" |
+			git checkout-index -z --force --stdin
+		fi
+	else
+		git apply -R < "$TMP-patch" ||
+		die "$(gettext "Cannot remove worktree changes")"
+
+		if test "$keep_index" != "t"
+		then
+			git reset -q -- "$@"
+		fi
+	fi
+}
+
+save_stash () {
+	push_options=
+	while test $# != 0
+	do
+		case "$1" in
+		--)
+			shift
+			break
+			;;
+		-*)
+			# pass all options through to push_stash
+			push_options="$push_options $1"
+			;;
+		*)
+			break
+			;;
+		esac
+		shift
+	done
+
+	stash_msg="$*"
+
+	if test -z "$stash_msg"
+	then
+		push_stash $push_options
+	else
+		push_stash $push_options -m "$stash_msg"
+	fi
+}
+
+have_stash () {
+	git rev-parse --verify --quiet $ref_stash >/dev/null
+}
+
+list_stash () {
+	have_stash || return 0
+	git log --format="%gd: %gs" -g --first-parent -m "$@" $ref_stash --
+}
+
+show_stash () {
+	ALLOW_UNKNOWN_FLAGS=t
+	assert_stash_like "$@"
+
+	if test -z "$FLAGS"
+	then
+		if test "$(git config --bool stash.showStat || echo true)" = "true"
+		then
+			FLAGS=--stat
+		fi
+
+		if test "$(git config --bool stash.showPatch || echo false)" = "true"
+		then
+			FLAGS=${FLAGS}${FLAGS:+ }-p
+		fi
+
+		if test -z "$FLAGS"
+		then
+			return 0
+		fi
+	fi
+
+	git diff ${FLAGS} $b_commit $w_commit
+}
+
+show_help () {
+	exec git help stash
+	exit 1
+}
+
+#
+# Parses the remaining options looking for flags and
+# at most one revision defaulting to ${ref_stash}@{0}
+# if none found.
+#
+# Derives related tree and commit objects from the
+# revision, if one is found.
+#
+# stash records the work tree, and is a merge between the
+# base commit (first parent) and the index tree (second parent).
+#
+#   REV is set to the symbolic version of the specified stash-like commit
+#   IS_STASH_LIKE is non-blank if ${REV} looks like a stash
+#   IS_STASH_REF is non-blank if the ${REV} looks like a stash ref
+#   s is set to the SHA1 of the stash commit
+#   w_commit is set to the commit containing the working tree
+#   b_commit is set to the base commit
+#   i_commit is set to the commit containing the index tree
+#   u_commit is set to the commit containing the untracked files tree
+#   w_tree is set to the working tree
+#   b_tree is set to the base tree
+#   i_tree is set to the index tree
+#   u_tree is set to the untracked files tree
+#
+#   GIT_QUIET is set to t if -q is specified
+#   INDEX_OPTION is set to --index if --index is specified.
+#   FLAGS is set to the remaining flags (if allowed)
+#
+# dies if:
+#   * too many revisions specified
+#   * no revision is specified and there is no stash stack
+#   * a revision is specified which cannot be resolve to a SHA1
+#   * a non-existent stash reference is specified
+#   * unknown flags were set and ALLOW_UNKNOWN_FLAGS is not "t"
+#
+
+parse_flags_and_rev()
+{
+	test "$PARSE_CACHE" = "$*" && return 0 # optimisation
+	PARSE_CACHE="$*"
+
+	IS_STASH_LIKE=
+	IS_STASH_REF=
+	INDEX_OPTION=
+	s=
+	w_commit=
+	b_commit=
+	i_commit=
+	u_commit=
+	w_tree=
+	b_tree=
+	i_tree=
+	u_tree=
+
+	FLAGS=
+	REV=
+	for opt
+	do
+		case "$opt" in
+			-q|--quiet)
+				GIT_QUIET=-t
+			;;
+			--index)
+				INDEX_OPTION=--index
+			;;
+			--help)
+				show_help
+			;;
+			-*)
+				test "$ALLOW_UNKNOWN_FLAGS" = t ||
+					die "$(eval_gettext "unknown option: \$opt")"
+				FLAGS="${FLAGS}${FLAGS:+ }$opt"
+			;;
+			*)
+				REV="${REV}${REV:+ }'$opt'"
+			;;
+		esac
+	done
+
+	eval set -- $REV
+
+	case $# in
+		0)
+			have_stash || die "$(gettext "No stash entries found.")"
+			set -- ${ref_stash}@{0}
+		;;
+		1)
+			:
+		;;
+		*)
+			die "$(eval_gettext "Too many revisions specified: \$REV")"
+		;;
+	esac
+
+	case "$1" in
+		*[!0-9]*)
+			:
+		;;
+		*)
+			set -- "${ref_stash}@{$1}"
+		;;
+	esac
+
+	REV=$(git rev-parse --symbolic --verify --quiet "$1") || {
+		reference="$1"
+		die "$(eval_gettext "\$reference is not a valid reference")"
+	}
+
+	i_commit=$(git rev-parse --verify --quiet "$REV^2") &&
+	set -- $(git rev-parse "$REV" "$REV^1" "$REV:" "$REV^1:" "$REV^2:" 2>/dev/null) &&
+	s=$1 &&
+	w_commit=$1 &&
+	b_commit=$2 &&
+	w_tree=$3 &&
+	b_tree=$4 &&
+	i_tree=$5 &&
+	IS_STASH_LIKE=t &&
+	test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
+	IS_STASH_REF=t
+
+	u_commit=$(git rev-parse --verify --quiet "$REV^3") &&
+	u_tree=$(git rev-parse "$REV^3:" 2>/dev/null)
+}
+
+is_stash_like()
+{
+	parse_flags_and_rev "$@"
+	test -n "$IS_STASH_LIKE"
+}
+
+assert_stash_like() {
+	is_stash_like "$@" || {
+		args="$*"
+		die "$(eval_gettext "'\$args' is not a stash-like commit")"
+	}
+}
+
+is_stash_ref() {
+	is_stash_like "$@" && test -n "$IS_STASH_REF"
+}
+
+assert_stash_ref() {
+	is_stash_ref "$@" || {
+		args="$*"
+		die "$(eval_gettext "'\$args' is not a stash reference")"
+	}
+}
+
+apply_stash () {
+
+	assert_stash_like "$@"
+
+	git update-index -q --refresh || die "$(gettext "unable to refresh index")"
+
+	# current index state
+	c_tree=$(git write-tree) ||
+		die "$(gettext "Cannot apply a stash in the middle of a merge")"
+
+	unstashed_index_tree=
+	if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" &&
+			test "$c_tree" != "$i_tree"
+	then
+		git diff-tree --binary $s^2^..$s^2 | git apply --cached
+		test $? -ne 0 &&
+			die "$(gettext "Conflicts in index. Try without --index.")"
+		unstashed_index_tree=$(git write-tree) ||
+			die "$(gettext "Could not save index tree")"
+		git reset
+	fi
+
+	if test -n "$u_tree"
+	then
+		GIT_INDEX_FILE="$TMPindex" git read-tree "$u_tree" &&
+		GIT_INDEX_FILE="$TMPindex" git checkout-index --all &&
+		rm -f "$TMPindex" ||
+		die "$(gettext "Could not restore untracked files from stash entry")"
+	fi
+
+	eval "
+		GITHEAD_$w_tree='Stashed changes' &&
+		GITHEAD_$c_tree='Updated upstream' &&
+		GITHEAD_$b_tree='Version stash was based on' &&
+		export GITHEAD_$w_tree GITHEAD_$c_tree GITHEAD_$b_tree
+	"
+
+	if test -n "$GIT_QUIET"
+	then
+		GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY
+	fi
+	if git merge-recursive $b_tree -- $c_tree $w_tree
+	then
+		# No conflict
+		if test -n "$unstashed_index_tree"
+		then
+			git read-tree "$unstashed_index_tree"
+		else
+			a="$TMP-added" &&
+			git diff-index --cached --name-only --diff-filter=A $c_tree >"$a" &&
+			git read-tree --reset $c_tree &&
+			git update-index --add --stdin <"$a" ||
+				die "$(gettext "Cannot unstage modified files")"
+			rm -f "$a"
+		fi
+		squelch=
+		if test -n "$GIT_QUIET"
+		then
+			squelch='>/dev/null 2>&1'
+		fi
+		(cd "$START_DIR" && eval "git status $squelch") || :
+	else
+		# Merge conflict; keep the exit status from merge-recursive
+		status=$?
+		git rerere
+		if test -n "$INDEX_OPTION"
+		then
+			gettextln "Index was not unstashed." >&2
+		fi
+		exit $status
+	fi
+}
+
+pop_stash() {
+	assert_stash_ref "$@"
+
+	if apply_stash "$@"
+	then
+		drop_stash "$@"
+	else
+		status=$?
+		say "$(gettext "The stash entry is kept in case you need it again.")"
+		exit $status
+	fi
+}
+
+drop_stash () {
+	assert_stash_ref "$@"
+
+	git reflog delete --updateref --rewrite "${REV}" &&
+		say "$(eval_gettext "Dropped \${REV} (\$s)")" ||
+		die "$(eval_gettext "\${REV}: Could not drop stash entry")"
+
+	# clear_stash if we just dropped the last stash entry
+	git rev-parse --verify --quiet "$ref_stash@{0}" >/dev/null ||
+	clear_stash
+}
+
+apply_to_branch () {
+	test -n "$1" || die "$(gettext "No branch name specified")"
+	branch=$1
+	shift 1
+
+	set -- --index "$@"
+	assert_stash_like "$@"
+
+	git checkout -b $branch $REV^ &&
+	apply_stash "$@" && {
+		test -z "$IS_STASH_REF" || drop_stash "$@"
+	}
+}
+
+test "$1" = "-p" && set "push" "$@"
+
+PARSE_CACHE='--not-parsed'
+# The default command is "push" if nothing but options are given
+seen_non_option=
+for opt
+do
+	case "$opt" in
+	--) break ;;
+	-*) ;;
+	*) seen_non_option=t; break ;;
+	esac
+done
+
+test -n "$seen_non_option" || set "push" "$@"
+
+# Main command set
+case "$1" in
+list)
+	shift
+	list_stash "$@"
+	;;
+show)
+	shift
+	show_stash "$@"
+	;;
+save)
+	shift
+	save_stash "$@"
+	;;
+push)
+	shift
+	push_stash "$@"
+	;;
+apply)
+	shift
+	apply_stash "$@"
+	;;
+clear)
+	shift
+	clear_stash "$@"
+	;;
+create)
+	shift
+	create_stash -m "$*" && echo "$w_commit"
+	;;
+store)
+	shift
+	store_stash "$@"
+	;;
+drop)
+	shift
+	drop_stash "$@"
+	;;
+pop)
+	shift
+	pop_stash "$@"
+	;;
+branch)
+	shift
+	apply_to_branch "$@"
+	;;
+*)
+	case $# in
+	0)
+		push_stash &&
+		say "$(gettext "(To restore them type \"git stash apply\")")"
+		;;
+	*)
+		usage
+	esac
+	;;
+esac
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 25/26] stash: optionally use the scripted version again
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (23 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 24/26] stash: add back the original, scripted `git stash` Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2019-01-06 22:59     ` Thomas Gummerer
  2018-12-20 19:44   ` [PATCH v12 26/26] tests: add a special setup where stash.useBuiltin is off Paul-Sebastian Ungureanu
  2019-01-03 23:39   ` [PATCH v12 00/26] Convert "git stash" to C builtin Junio C Hamano
  26 siblings, 1 reply; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

We recently converted the `git stash` command from Unix shell scripts
to builtins.

Let's end users a way out when they discover a bug in the
builtin command: `stash.useBuiltin`.

As the file name `git-stash` is already in use, let's rename the
scripted backend to `git-legacy-stash`.

To make the test suite pass with `stash.useBuiltin=false`, this commit
also backports rudimentary support for `-q` (but only *just* enough
to appease the test suite), and adds a super-ugly hack to force exit
code 129 for `git stash -h`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore                          |  1 +
 Makefile                            |  1 +
 builtin/stash.c                     | 35 +++++++++++++++++++++++++++++
 git-stash.sh => git-legacy-stash.sh | 34 +++++++++++++++++++++++++---
 git-sh-setup.sh                     |  1 +
 git.c                               |  7 +++++-
 6 files changed, 75 insertions(+), 4 deletions(-)
 rename git-stash.sh => git-legacy-stash.sh (97%)

diff --git a/.gitignore b/.gitignore
index 0d77ea5894..7b0164675e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -82,6 +82,7 @@
 /git-interpret-trailers
 /git-instaweb
 /git-legacy-rebase
+/git-legacy-stash
 /git-log
 /git-ls-files
 /git-ls-remote
diff --git a/Makefile b/Makefile
index 8cee2731aa..810231a0b5 100644
--- a/Makefile
+++ b/Makefile
@@ -617,6 +617,7 @@ SCRIPT_SH += git-merge-resolve.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-quiltimport.sh
 SCRIPT_SH += git-legacy-rebase.sh
+SCRIPT_SH += git-legacy-stash.sh
 SCRIPT_SH += git-remote-testgit.sh
 SCRIPT_SH += git-request-pull.sh
 SCRIPT_SH += git-submodule.sh
diff --git a/builtin/stash.c b/builtin/stash.c
index fe32ff42fd..346c9d2bb1 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -13,6 +13,7 @@
 #include "revision.h"
 #include "log-tree.h"
 #include "diffcore.h"
+#include "exec-cmd.h"
 
 #define INCLUDE_ALL_FILES 2
 
@@ -1513,6 +1514,26 @@ static int save_stash(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
+static int use_builtin_stash(void)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	struct strbuf out = STRBUF_INIT;
+	int ret;
+
+	argv_array_pushl(&cp.args,
+			 "config", "--bool", "stash.usebuiltin", NULL);
+	cp.git_cmd = 1;
+	if (capture_command(&cp, &out, 6)) {
+		strbuf_release(&out);
+		return 1;
+	}
+
+	strbuf_trim(&out);
+	ret = !strcmp("true", out.buf);
+	strbuf_release(&out);
+	return ret;
+}
+
 int cmd_stash(int argc, const char **argv, const char *prefix)
 {
 	int i = -1;
@@ -1524,6 +1545,20 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
+	if (!use_builtin_stash()) {
+		const char *path = mkpath("%s/git-legacy-stash",
+					  git_exec_path());
+
+		if (sane_execvp(path, (char **)argv) < 0)
+			die_errno(_("could not exec %s"), path);
+		else
+			BUG("sane_execvp() returned???");
+	}
+
+	prefix = setup_git_directory();
+	trace_repo_setup(prefix);
+	setup_work_tree();
+
 	git_config(git_diff_basic_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, options, git_stash_usage,
diff --git a/git-stash.sh b/git-legacy-stash.sh
similarity index 97%
rename from git-stash.sh
rename to git-legacy-stash.sh
index 789ce2f41d..8a8c4a9270 100755
--- a/git-stash.sh
+++ b/git-legacy-stash.sh
@@ -80,6 +80,28 @@ clear_stash () {
 	fi
 }
 
+maybe_quiet () {
+	case "$1" in
+	--keep-stdout)
+		shift
+		if test -n "$GIT_QUIET"
+		then
+			eval "$@" 2>/dev/null
+		else
+			eval "$@"
+		fi
+		;;
+	*)
+		if test -n "$GIT_QUIET"
+		then
+			eval "$@" >/dev/null 2>&1
+		else
+			eval "$@"
+		fi
+		;;
+	esac
+}
+
 create_stash () {
 
 	prepare_fallback_ident
@@ -112,15 +134,18 @@ create_stash () {
 	done
 
 	git update-index -q --refresh
-	if no_changes "$@"
+	if maybe_quiet no_changes "$@"
 	then
 		exit 0
 	fi
 
 	# state of the base commit
-	if b_commit=$(git rev-parse --verify HEAD)
+	if b_commit=$(maybe_quiet --keep-stdout git rev-parse --verify HEAD)
 	then
 		head=$(git rev-list --oneline -n 1 HEAD --)
+	elif test -n "$GIT_QUIET"
+	then
+		exit 1
 	else
 		die "$(gettext "You do not have the initial commit yet")"
 	fi
@@ -315,7 +340,7 @@ push_stash () {
 	test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1
 
 	git update-index -q --refresh
-	if no_changes "$@"
+	if maybe_quiet no_changes "$@"
 	then
 		say "$(gettext "No local changes to save")"
 		exit 0
@@ -370,6 +395,9 @@ save_stash () {
 	while test $# != 0
 	do
 		case "$1" in
+		-q|--quiet)
+			GIT_QUIET=t
+			;;
 		--)
 			shift
 			break
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index 378928518b..10d9764185 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -101,6 +101,7 @@ $LONG_USAGE")"
 	case "$1" in
 		-h)
 		echo "$LONG_USAGE"
+		case "$0" in *git-legacy-stash) exit 129;; esac
 		exit
 	esac
 fi
diff --git a/git.c b/git.c
index 8a20909eae..591ebe9409 100644
--- a/git.c
+++ b/git.c
@@ -554,7 +554,12 @@ static struct cmd_struct commands[] = {
 	{ "show-index", cmd_show_index },
 	{ "show-ref", cmd_show_ref, RUN_SETUP },
 	{ "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
-	{ "stash", cmd_stash, RUN_SETUP | NEED_WORK_TREE },
+	/*
+	 * NEEDSWORK: Until the builtin stash is thoroughly robust and no
+	 * longer needs redirection to the stash shell script this is kept as
+	 * is, then should be changed to RUN_SETUP | NEED_WORK_TREE
+	 */
+	{ "stash", cmd_stash },
 	{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 	{ "stripspace", cmd_stripspace },
 	{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
-- 
2.20.1.441.g764a526393


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

* [PATCH v12 26/26] tests: add a special setup where stash.useBuiltin is off
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (24 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 25/26] stash: optionally use the scripted version again Paul-Sebastian Ungureanu
@ 2018-12-20 19:44   ` Paul-Sebastian Ungureanu
  2019-01-03 23:39   ` [PATCH v12 00/26] Convert "git stash" to C builtin Junio C Hamano
  26 siblings, 0 replies; 106+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-12-20 19:44 UTC (permalink / raw)
  To: git; +Cc: t.gummerer, Johannes.Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Add a GIT_TEST_STASH_USE_BUILTIN=false test mode which is equivalent
to running with stash.useBuiltin=false. This is needed to spot that
we're not introducing any regressions in the legacy stash version
while we're carrying both it and the new built-in version.

This imitates the equivalent treatment for the built-in rebase in
62c23938fae5 (tests: add a special setup where rebase.useBuiltin is off,
2018-11-14).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/stash.c | 5 ++++-
 t/README        | 4 ++++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/builtin/stash.c b/builtin/stash.c
index 346c9d2bb1..3ee8a41cda 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -1518,7 +1518,10 @@ static int use_builtin_stash(void)
 {
 	struct child_process cp = CHILD_PROCESS_INIT;
 	struct strbuf out = STRBUF_INIT;
-	int ret;
+	int ret, env = git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1);
+
+	if (env != -1)
+		return env;
 
 	argv_array_pushl(&cp.args,
 			 "config", "--bool", "stash.usebuiltin", NULL);
diff --git a/t/README b/t/README
index 28711cc508..9187eeea8e 100644
--- a/t/README
+++ b/t/README
@@ -349,6 +349,10 @@ GIT_TEST_REBASE_USE_BUILTIN=<boolean>, when false, disables the
 builtin version of git-rebase. See 'rebase.useBuiltin' in
 git-config(1).
 
+GIT_TEST_STASH_USE_BUILTIN=<boolean>, when false, disables the
+built-in version of git-stash. See 'stash.useBuiltin' in
+git-config(1).
+
 GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading
 of the index for the whole test suite by bypassing the default number of
 cache entries and thread minimums. Setting this to 1 will make the
-- 
2.20.1.441.g764a526393


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

* Re: [PATCH v12 04/26] ident: add the ability to provide a "fallback identity"
  2018-12-20 19:44   ` [PATCH v12 04/26] ident: add the ability to provide a "fallback identity" Paul-Sebastian Ungureanu
@ 2018-12-26 21:21     ` Junio C Hamano
  2018-12-27 21:24       ` Johannes Schindelin
  0 siblings, 1 reply; 106+ messages in thread
From: Junio C Hamano @ 2018-12-26 21:21 UTC (permalink / raw)
  To: Paul-Sebastian Ungureanu; +Cc: git, t.gummerer, Johannes.Schindelin

Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com> writes:

> +static void set_env_if(const char *key, const char *value, int *given, int bit)
> +{
> +	if ((*given & bit) || getenv(key))
> +		return; /* nothing to do */
> +	setenv(key, value, 0);
> +	*given |= bit;
> +}

We call setenv(3) with overwrite=0 but we protect the call with a
check for existing value with getenv(3), which feels a bit like an
anti-pattern.  Wouldn't the following be simpler to follow, I wonder?

	if (!(*given & bit)) {
		setenv(key, value, 1);
		*given |= bit;
	}

The only case these two may behave differently is when '*given' does
not have the 'bit' set but the environment 'key' already exists.
The proposed patch will leave 'bit' in '*given' unset, so when a
later code says "let's see if author_ident is explicitly given, and
complain otherwise", such a check will trigger and cause complaint.

On the other hand, the simplified version does not allow the
"explicitly-given" bits to be left unset, so it won't cause
complaint.

Isn't it a BUG() if *given lacks 'bit' when the corresponding
environment variable 'key' is missing?  IOW, I would understand
an implementation that is more elaborate than the simplified one I
just gave above were something like

	if (!(*given & bit)) {
		if (getenv(key))
			BUG("why does %s exist and no %x bit set???", key, bit);
		setenv(key, value, 0);
		*given |= bit;
	}

but I do not quite understand the reasoning behind the "check either
the bit, or the environment variable" in the proposed patch.

> +void prepare_fallback_ident(const char *name, const char *email)
> +{
> +	set_env_if("GIT_AUTHOR_NAME", name,
> +		   &author_ident_explicitly_given, IDENT_NAME_GIVEN);
> +	set_env_if("GIT_AUTHOR_EMAIL", email,
> +		   &author_ident_explicitly_given, IDENT_MAIL_GIVEN);
> +	set_env_if("GIT_COMMITTER_NAME", name,
> +		   &committer_ident_explicitly_given, IDENT_NAME_GIVEN);
> +	set_env_if("GIT_COMMITTER_EMAIL", email,
> +		   &committer_ident_explicitly_given, IDENT_MAIL_GIVEN);
> +}

Introducing this function alone without a caller and without
function doc is a bit unfriendly to future callers, who must be
careful when to call it, I think.  For example, they must know that
it will be a disaster if they call this before they call
git_ident_config(), right?

> +
>  static int buf_cmp(const char *a_begin, const char *a_end,
>  		   const char *b_begin, const char *b_end)
>  {



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

* Re: [PATCH v12 04/26] ident: add the ability to provide a "fallback identity"
  2018-12-26 21:21     ` Junio C Hamano
@ 2018-12-27 21:24       ` Johannes Schindelin
  2018-12-28 19:40         ` Junio C Hamano
  0 siblings, 1 reply; 106+ messages in thread
From: Johannes Schindelin @ 2018-12-27 21:24 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Paul-Sebastian Ungureanu, git, t.gummerer

Hi Junio,

On Wed, 26 Dec 2018, Junio C Hamano wrote:

> Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com> writes:
> 
> > +static void set_env_if(const char *key, const char *value, int *given, int bit)
> > +{
> > +	if ((*given & bit) || getenv(key))
> > +		return; /* nothing to do */
> > +	setenv(key, value, 0);
> > +	*given |= bit;
> > +}
> 
> We call setenv(3) with overwrite=0 but we protect the call with a
> check for existing value with getenv(3), which feels a bit like an
> anti-pattern.  Wouldn't the following be simpler to follow, I wonder?
> 
> 	if (!(*given & bit)) {
> 		setenv(key, value, 1);
> 		*given |= bit;
> 	}
> 
> The only case these two may behave differently is when '*given' does
> not have the 'bit' set but the environment 'key' already exists.

Indeed, this is the case where your version would actually do the wrong
thing. Imagine that GIT_AUTHOR_NAME is set already. Your code would
*override* it. But that is not what we want to do here. We want to *fall
back* if there is no already-configured value.

And of course we won't set the `given` bit if we don't fall back here;
that should be done somewhere else, where that environment variable (that
we *refuse* to overwrite) is *actually* used.

Ciao,
Dscho

> The proposed patch will leave 'bit' in '*given' unset, so when a
> later code says "let's see if author_ident is explicitly given, and
> complain otherwise", such a check will trigger and cause complaint.
> 
> On the other hand, the simplified version does not allow the
> "explicitly-given" bits to be left unset, so it won't cause
> complaint.
> 
> Isn't it a BUG() if *given lacks 'bit' when the corresponding
> environment variable 'key' is missing?  IOW, I would understand
> an implementation that is more elaborate than the simplified one I
> just gave above were something like
> 
> 	if (!(*given & bit)) {
> 		if (getenv(key))
> 			BUG("why does %s exist and no %x bit set???", key, bit);
> 		setenv(key, value, 0);
> 		*given |= bit;
> 	}
> 
> but I do not quite understand the reasoning behind the "check either
> the bit, or the environment variable" in the proposed patch.
> 
> > +void prepare_fallback_ident(const char *name, const char *email)
> > +{
> > +	set_env_if("GIT_AUTHOR_NAME", name,
> > +		   &author_ident_explicitly_given, IDENT_NAME_GIVEN);
> > +	set_env_if("GIT_AUTHOR_EMAIL", email,
> > +		   &author_ident_explicitly_given, IDENT_MAIL_GIVEN);
> > +	set_env_if("GIT_COMMITTER_NAME", name,
> > +		   &committer_ident_explicitly_given, IDENT_NAME_GIVEN);
> > +	set_env_if("GIT_COMMITTER_EMAIL", email,
> > +		   &committer_ident_explicitly_given, IDENT_MAIL_GIVEN);
> > +}
> 
> Introducing this function alone without a caller and without
> function doc is a bit unfriendly to future callers, who must be
> careful when to call it, I think.  For example, they must know that
> it will be a disaster if they call this before they call
> git_ident_config(), right?
> 
> > +
> >  static int buf_cmp(const char *a_begin, const char *a_end,
> >  		   const char *b_begin, const char *b_end)
> >  {
> 
> 
> 

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

* Re: [PATCH v12 04/26] ident: add the ability to provide a "fallback identity"
  2018-12-27 21:24       ` Johannes Schindelin
@ 2018-12-28 19:40         ` Junio C Hamano
  0 siblings, 0 replies; 106+ messages in thread
From: Junio C Hamano @ 2018-12-28 19:40 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Paul-Sebastian Ungureanu, git, t.gummerer

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> Hi Junio,
>
> On Wed, 26 Dec 2018, Junio C Hamano wrote:
>
>> Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com> writes:
>> 
>> > +static void set_env_if(const char *key, const char *value, int *given, int bit)
>> > +{
>> > +	if ((*given & bit) || getenv(key))
>> > +		return; /* nothing to do */
>> > +	setenv(key, value, 0);
>> > +	*given |= bit;
>> > +}
>> 
>> We call setenv(3) with overwrite=0 but we protect the call with a
>> check for existing value with getenv(3), which feels a bit like an
>> anti-pattern.  Wouldn't the following be simpler to follow, I wonder?
>> 
>> 	if (!(*given & bit)) {
>> 		setenv(key, value, 1);
>> 		*given |= bit;
>> 	}
>> 
>> The only case these two may behave differently is when '*given' does
>> not have the 'bit' set but the environment 'key' already exists.
>
> Indeed, this is the case where your version would actually do the wrong
> thing. Imagine that GIT_AUTHOR_NAME is set already. Your code would
> *override* it. But that is not what we want to do here. We want to *fall
> back* if there is no already-configured value.
>
> And of course we won't set the `given` bit if we don't fall back here;
> that should be done somewhere else, where that environment variable (that
> we *refuse* to overwrite) is *actually* used.

OK, so the designed calling sequence of the new "prepare fallback
ident" is that any process that wants to use fallback ident must
call the "prepare" function _before_ making a call to any other
functions (IOW, it is a BUG() if things like git_committer_info() is
called before prepare_fallback_ident() gets called).  Under that
condition, you are absolutely right that the two implementation
behaves differently.

That indeed indicates that this is unfriendly to future callers,
which was the main issue I had with the patch.  A comment before the
prepare_fallback_ident() function to explain that would have helped.

Also the first condition checking bit in *given does not help---it
is quite misleading.  It would have helped if it were

	static void set_env_if( ... )
	{
		if (*given & bit)
			BUG("%s was checked before prepare_fallback got called", key);
		if (getenv(key))
			return; /* nothing to do */
 		setenv(key, value, 1);
		*given |= bit;
	}

Because (author|committer)_ident_explicitly_given would never say
"yes, already" if the setting of fallback MUST be done before using
other API functions in ident.c, it we were to have a check for that
condition, it would be testing for a BUG().  And I wouldn't have
been confused by the code while reviewing.

>> > +void prepare_fallback_ident(const char *name, const char *email)
>> > +{
>> > +	set_env_if("GIT_AUTHOR_NAME", name,
>> > +		   &author_ident_explicitly_given, IDENT_NAME_GIVEN);
>> > +	set_env_if("GIT_AUTHOR_EMAIL", email,
>> > +		   &author_ident_explicitly_given, IDENT_MAIL_GIVEN);
>> > +	set_env_if("GIT_COMMITTER_NAME", name,
>> > +		   &committer_ident_explicitly_given, IDENT_NAME_GIVEN);
>> > +	set_env_if("GIT_COMMITTER_EMAIL", email,
>> > +		   &committer_ident_explicitly_given, IDENT_MAIL_GIVEN);
>> > +}
>> 
>> Introducing this function alone without a caller and without
>> function doc is a bit unfriendly to future callers, who must be
>> careful when to call it, I think.  For example, they must know that
>> it will be a disaster if they call this before they call
>> git_ident_config(), right?

As you can see from this "For example,...", the review comment (and
the "simplified but does the wrong thing" version) was written under
an assumption different from the expected calling sequence the
posted version makes.  What future writers of callers that use the
fallback ident feature must know is (unlike the above) that they
must call the "prepare" before they call git_ident_config() or
anything in ident.c.  A comment before this function that explain
how and when in the program flow this is designed to be used is
needed.

Thanks for a clarification.


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

* Re: [PATCH v12 00/26] Convert "git stash" to C builtin
  2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
                     ` (25 preceding siblings ...)
  2018-12-20 19:44   ` [PATCH v12 26/26] tests: add a special setup where stash.useBuiltin is off Paul-Sebastian Ungureanu
@ 2019-01-03 23:39   ` Junio C Hamano
  2019-01-18 12:06     ` Johannes Schindelin
  26 siblings, 1 reply; 106+ messages in thread
From: Junio C Hamano @ 2019-01-03 23:39 UTC (permalink / raw)
  To: Paul-Sebastian Ungureanu; +Cc: git, t.gummerer, Johannes.Schindelin

Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com> writes:

> This is a new iteration of git-stash which also takes
> sd/stash-wo-user-name into account. I cherry-picked
> some of dscho's commits (from [1]) to keep the scripted
> version of `git stash` as `git-legacy-stash`.

I took a brief look and left a comment on 04/26 last year.  I had
some time blocked for this topic today to take another look at the
whole series again.  Thanks for working on this.

It seems that the last three or so steps are new, relative to the
previous round.  I made sure that what is added back at step 24
exactly matches the result of merging sd/stash-wo-user-name into the
current 'master', but such a manual validation is error prone.  Is
it possible to avoid "remove the scripted one prematurely at step
23, and then add it back as 'oops, that was wrong' fix at step 24"?
That would have been much more robust approach.

Thanks.

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

* Re: [PATCH v12 21/26] stash: optimize `get_untracked_files()` and `check_changes()`
  2018-12-20 19:44   ` [PATCH v12 21/26] stash: optimize `get_untracked_files()` and `check_changes()` Paul-Sebastian Ungureanu
@ 2019-01-06 22:47     ` Thomas Gummerer
  0 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-01-06 22:47 UTC (permalink / raw)
  To: Paul-Sebastian Ungureanu; +Cc: git, Johannes.Schindelin

On 12/20, Paul-Sebastian Ungureanu wrote:
> This commits introduces a optimization by avoiding calling the
> same functions again. For example, `git stash push -u`
> would call at some points the following functions:
> 
>  * `check_changes()` (inside `do_push_stash()`)
>  * `do_create_stash()`, which calls: `check_changes()` and
> `get_untracked_files()`
> 
> Note that `check_changes()` also calls `get_untracked_files()`.
> So, `check_changes()` is called 2 times and `get_untracked_files()`
> 3 times.
> 
> The old function `check_changes()` now consists of two functions:
> `get_untracked_files()` and `check_changes_tracked_files()`.
> 
> These are the call chains for `push` and `create`:
> 
>  * `push_stash()` -> `do_push_stash()` -> `do_create_stash()`
> 
>  * `create_stash()` -> `do_create_stash()`
> 
> To prevent calling the same functions over and over again,
> `check_changes()` inside `do_create_stash()` is now placed
> in the caller functions (`create_stash()` and `do_push_stash()`).
> This way `check_changes()` and `get_untracked files()` are called
> only one time.
> 
> https://public-inbox.org/git/20180818223329.GJ11326@hank.intra.tgummerer.com/

I missed this the other time, but having this link in the commit
message is unnecessary, and it may go stale (though in this case it
includes the Message-ID, so it is still useful as long as some archive
exists.  The commit message explains well enough what this change is
doing, even without the link to the review.

Sorry I missed these things in the earlier rounds somehow.

> Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
> ---
>  builtin/stash--helper.c | 53 +++++++++++++++++++++++------------------
>  1 file changed, 30 insertions(+), 23 deletions(-)
> 
> diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
> index 19ead63c46..4b63352927 100644
> --- a/builtin/stash--helper.c
> +++ b/builtin/stash--helper.c
> @@ -884,18 +884,18 @@ static int get_untracked_files(struct pathspec ps, int include_untracked,
>  }
>  
>  /*
> - * The return value of `check_changes()` can be:
> + * The return value of `check_changes_tracked_files()` can be:
>   *
>   * < 0 if there was an error
>   * = 0 if there are no changes.
>   * > 0 if there are changes.
>   */
> -static int check_changes(struct pathspec ps, int include_untracked)
> +

Unnecessary blank line after the comment here.

> +static int check_changes_tracked_files(struct pathspec ps)
>  {
>  	int result;
>  	struct rev_info rev;
>  	struct object_id dummy;
> -	struct strbuf out = STRBUF_INIT;
>  
>  	/* No initial commit. */
>  	if (get_oid("HEAD", &dummy))
> @@ -923,14 +923,26 @@ static int check_changes(struct pathspec ps, int include_untracked)
>  	if (diff_result_code(&rev.diffopt, result))
>  		return 1;
>  
> +	return 0;
> +}
> +
> +/*
> + * The function will fill `untracked_files` with the names of untracked files
> + * It will return 1 if there were any changes and 0 if there were not.
> + */
> +

Unnecessary blank line.

> +static int check_changes(struct pathspec ps, int include_untracked,
> +			 struct strbuf *untracked_files)
> +{
> +	int ret = 0;
> +	if (check_changes_tracked_files(ps))
> +		ret = 1;
> +
>  	if (include_untracked && get_untracked_files(ps, include_untracked,
> -						     &out)) {
> -		strbuf_release(&out);
> -		return 1;
> -	}
> +						     untracked_files))
> +		ret = 1;
>  
> -	strbuf_release(&out);
> -	return 0;
> +	return ret;
>  }
>  
>  static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
> @@ -1141,7 +1153,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
>  		head_commit = lookup_commit(the_repository, &info->b_commit);
>  	}
>  
> -	if (!check_changes(ps, include_untracked)) {
> +	if (!check_changes(ps, include_untracked, &untracked_files)) {
>  		ret = 1;
>  		goto done;
>  	}
> @@ -1166,8 +1178,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
>  		goto done;
>  	}
>  
> -	if (include_untracked && get_untracked_files(ps, include_untracked,
> -						     &untracked_files)) {
> +	if (include_untracked) {
>  		if (save_untracked_files(info, &msg, untracked_files)) {
>  			if (!quiet)
>  				fprintf_ln(stderr, _("Cannot save "
> @@ -1252,20 +1263,15 @@ static int create_stash(int argc, const char **argv, const char *prefix)
>  			     0);
>  
>  	memset(&ps, 0, sizeof(ps));
> -	strbuf_addstr(&stash_msg_buf, stash_msg);
> -	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
> -			      NULL, 0);
> +	if (!check_changes_tracked_files(ps))
> +		return 0;
>  
> -	if (!ret)
> +	strbuf_addstr(&stash_msg_buf, stash_msg);
> +	if (!(ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info, NULL, 0)))

It's a bit odd to have do_create_stash moved inside the if here, even
though it was introduced in this patch series.  It makes the patch a
bit more noisy and harder to see what was changed here.

>  		printf_ln("%s", oid_to_hex(&info.w_commit));
>  
>  	strbuf_release(&stash_msg_buf);
> -
> -	/*
> -	 * ret can be 1 if there were no changes. In this case, we should
> -	 * not error out.
> -	 */
> -	return ret < 0;
> +	return ret;
>  }
>  
>  static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
> @@ -1275,6 +1281,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
>  	struct stash_info info;
>  	struct strbuf patch = STRBUF_INIT;
>  	struct strbuf stash_msg_buf = STRBUF_INIT;
> +	struct strbuf untracked_files = STRBUF_INIT;

Does this strbuf also need to be released at the end of the function?

>  
>  	if (patch_mode && keep_index == -1)
>  		keep_index = 1;
> @@ -1309,7 +1316,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
>  		goto done;
>  	}
>  
> -	if (!check_changes(ps, include_untracked)) {
> +	if (!check_changes(ps, include_untracked, &untracked_files)) {
>  		if (!quiet)
>  			printf_ln(_("No local changes to save"));
>  		goto done;
> -- 
> 2.20.1.441.g764a526393
> 

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

* Re: [PATCH v12 25/26] stash: optionally use the scripted version again
  2018-12-20 19:44   ` [PATCH v12 25/26] stash: optionally use the scripted version again Paul-Sebastian Ungureanu
@ 2019-01-06 22:59     ` Thomas Gummerer
  0 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-01-06 22:59 UTC (permalink / raw)
  To: Paul-Sebastian Ungureanu; +Cc: git, Johannes.Schindelin

On 12/20, Paul-Sebastian Ungureanu wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
> 
> We recently converted the `git stash` command from Unix shell scripts
> to builtins.
> 
> Let's end users a way out when they discover a bug in the

s/Let's/& give/ maybe?

The rest of the patch looks good to me.

> builtin command: `stash.useBuiltin`.
> 
> As the file name `git-stash` is already in use, let's rename the
> scripted backend to `git-legacy-stash`.
> 
> To make the test suite pass with `stash.useBuiltin=false`, this commit
> also backports rudimentary support for `-q` (but only *just* enough
> to appease the test suite), and adds a super-ugly hack to force exit
> code 129 for `git stash -h`.
> 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  .gitignore                          |  1 +
>  Makefile                            |  1 +
>  builtin/stash.c                     | 35 +++++++++++++++++++++++++++++
>  git-stash.sh => git-legacy-stash.sh | 34 +++++++++++++++++++++++++---
>  git-sh-setup.sh                     |  1 +
>  git.c                               |  7 +++++-
>  6 files changed, 75 insertions(+), 4 deletions(-)
>  rename git-stash.sh => git-legacy-stash.sh (97%)
> 
> diff --git a/.gitignore b/.gitignore
> index 0d77ea5894..7b0164675e 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -82,6 +82,7 @@
>  /git-interpret-trailers
>  /git-instaweb
>  /git-legacy-rebase
> +/git-legacy-stash
>  /git-log
>  /git-ls-files
>  /git-ls-remote
> diff --git a/Makefile b/Makefile
> index 8cee2731aa..810231a0b5 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -617,6 +617,7 @@ SCRIPT_SH += git-merge-resolve.sh
>  SCRIPT_SH += git-mergetool.sh
>  SCRIPT_SH += git-quiltimport.sh
>  SCRIPT_SH += git-legacy-rebase.sh
> +SCRIPT_SH += git-legacy-stash.sh
>  SCRIPT_SH += git-remote-testgit.sh
>  SCRIPT_SH += git-request-pull.sh
>  SCRIPT_SH += git-submodule.sh
> diff --git a/builtin/stash.c b/builtin/stash.c
> index fe32ff42fd..346c9d2bb1 100644
> --- a/builtin/stash.c
> +++ b/builtin/stash.c
> @@ -13,6 +13,7 @@
>  #include "revision.h"
>  #include "log-tree.h"
>  #include "diffcore.h"
> +#include "exec-cmd.h"
>  
>  #define INCLUDE_ALL_FILES 2
>  
> @@ -1513,6 +1514,26 @@ static int save_stash(int argc, const char **argv, const char *prefix)
>  	return ret;
>  }
>  
> +static int use_builtin_stash(void)
> +{
> +	struct child_process cp = CHILD_PROCESS_INIT;
> +	struct strbuf out = STRBUF_INIT;
> +	int ret;
> +
> +	argv_array_pushl(&cp.args,
> +			 "config", "--bool", "stash.usebuiltin", NULL);
> +	cp.git_cmd = 1;
> +	if (capture_command(&cp, &out, 6)) {
> +		strbuf_release(&out);
> +		return 1;
> +	}
> +
> +	strbuf_trim(&out);
> +	ret = !strcmp("true", out.buf);
> +	strbuf_release(&out);
> +	return ret;
> +}
> +
>  int cmd_stash(int argc, const char **argv, const char *prefix)
>  {
>  	int i = -1;
> @@ -1524,6 +1545,20 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
>  		OPT_END()
>  	};
>  
> +	if (!use_builtin_stash()) {
> +		const char *path = mkpath("%s/git-legacy-stash",
> +					  git_exec_path());
> +
> +		if (sane_execvp(path, (char **)argv) < 0)
> +			die_errno(_("could not exec %s"), path);
> +		else
> +			BUG("sane_execvp() returned???");
> +	}
> +
> +	prefix = setup_git_directory();
> +	trace_repo_setup(prefix);
> +	setup_work_tree();
> +
>  	git_config(git_diff_basic_config, NULL);
>  
>  	argc = parse_options(argc, argv, prefix, options, git_stash_usage,
> diff --git a/git-stash.sh b/git-legacy-stash.sh
> similarity index 97%
> rename from git-stash.sh
> rename to git-legacy-stash.sh
> index 789ce2f41d..8a8c4a9270 100755
> --- a/git-stash.sh
> +++ b/git-legacy-stash.sh
> @@ -80,6 +80,28 @@ clear_stash () {
>  	fi
>  }
>  
> +maybe_quiet () {
> +	case "$1" in
> +	--keep-stdout)
> +		shift
> +		if test -n "$GIT_QUIET"
> +		then
> +			eval "$@" 2>/dev/null
> +		else
> +			eval "$@"
> +		fi
> +		;;
> +	*)
> +		if test -n "$GIT_QUIET"
> +		then
> +			eval "$@" >/dev/null 2>&1
> +		else
> +			eval "$@"
> +		fi
> +		;;
> +	esac
> +}
> +
>  create_stash () {
>  
>  	prepare_fallback_ident
> @@ -112,15 +134,18 @@ create_stash () {
>  	done
>  
>  	git update-index -q --refresh
> -	if no_changes "$@"
> +	if maybe_quiet no_changes "$@"
>  	then
>  		exit 0
>  	fi
>  
>  	# state of the base commit
> -	if b_commit=$(git rev-parse --verify HEAD)
> +	if b_commit=$(maybe_quiet --keep-stdout git rev-parse --verify HEAD)
>  	then
>  		head=$(git rev-list --oneline -n 1 HEAD --)
> +	elif test -n "$GIT_QUIET"
> +	then
> +		exit 1
>  	else
>  		die "$(gettext "You do not have the initial commit yet")"
>  	fi
> @@ -315,7 +340,7 @@ push_stash () {
>  	test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1
>  
>  	git update-index -q --refresh
> -	if no_changes "$@"
> +	if maybe_quiet no_changes "$@"
>  	then
>  		say "$(gettext "No local changes to save")"
>  		exit 0
> @@ -370,6 +395,9 @@ save_stash () {
>  	while test $# != 0
>  	do
>  		case "$1" in
> +		-q|--quiet)
> +			GIT_QUIET=t
> +			;;
>  		--)
>  			shift
>  			break
> diff --git a/git-sh-setup.sh b/git-sh-setup.sh
> index 378928518b..10d9764185 100644
> --- a/git-sh-setup.sh
> +++ b/git-sh-setup.sh
> @@ -101,6 +101,7 @@ $LONG_USAGE")"
>  	case "$1" in
>  		-h)
>  		echo "$LONG_USAGE"
> +		case "$0" in *git-legacy-stash) exit 129;; esac
>  		exit
>  	esac
>  fi
> diff --git a/git.c b/git.c
> index 8a20909eae..591ebe9409 100644
> --- a/git.c
> +++ b/git.c
> @@ -554,7 +554,12 @@ static struct cmd_struct commands[] = {
>  	{ "show-index", cmd_show_index },
>  	{ "show-ref", cmd_show_ref, RUN_SETUP },
>  	{ "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
> -	{ "stash", cmd_stash, RUN_SETUP | NEED_WORK_TREE },
> +	/*
> +	 * NEEDSWORK: Until the builtin stash is thoroughly robust and no
> +	 * longer needs redirection to the stash shell script this is kept as
> +	 * is, then should be changed to RUN_SETUP | NEED_WORK_TREE
> +	 */
> +	{ "stash", cmd_stash },
>  	{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
>  	{ "stripspace", cmd_stripspace },
>  	{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
> -- 
> 2.20.1.441.g764a526393
> 

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

* Re: [PATCH v12 00/26] Convert "git stash" to C builtin
  2019-01-03 23:39   ` [PATCH v12 00/26] Convert "git stash" to C builtin Junio C Hamano
@ 2019-01-18 12:06     ` Johannes Schindelin
  2019-01-18 17:49       ` Junio C Hamano
  0 siblings, 1 reply; 106+ messages in thread
From: Johannes Schindelin @ 2019-01-18 12:06 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Paul-Sebastian Ungureanu, git, t.gummerer

Hi Junio,

On Thu, 3 Jan 2019, Junio C Hamano wrote:

> Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com> writes:
> 
> > This is a new iteration of git-stash which also takes
> > sd/stash-wo-user-name into account. I cherry-picked
> > some of dscho's commits (from [1]) to keep the scripted
> > version of `git stash` as `git-legacy-stash`.
> 
> I took a brief look and left a comment on 04/26 last year.  I had
> some time blocked for this topic today to take another look at the
> whole series again.  Thanks for working on this.
> 
> It seems that the last three or so steps are new, relative to the
> previous round.  I made sure that what is added back at step 24
> exactly matches the result of merging sd/stash-wo-user-name into the
> current 'master', but such a manual validation is error prone.  Is
> it possible to avoid "remove the scripted one prematurely at step
> 23, and then add it back as 'oops, that was wrong' fix at step 24"?
> That would have been much more robust approach.

Sorry, I should have thought of that. My mistake.

As it is, Thomas verified that they are identical, so should we go forward
with ps/stash-in-c as-is? I'd prefer that...

Ciao,
Dscho

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

* Re: [PATCH v12 00/26] Convert "git stash" to C builtin
  2019-01-18 12:06     ` Johannes Schindelin
@ 2019-01-18 17:49       ` Junio C Hamano
  0 siblings, 0 replies; 106+ messages in thread
From: Junio C Hamano @ 2019-01-18 17:49 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Paul-Sebastian Ungureanu, git, t.gummerer

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> Hi Junio,
>
> On Thu, 3 Jan 2019, Junio C Hamano wrote:
>
>> Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com> writes:
>> 
>> > This is a new iteration of git-stash which also takes
>> > sd/stash-wo-user-name into account. I cherry-picked
>> > some of dscho's commits (from [1]) to keep the scripted
>> > version of `git stash` as `git-legacy-stash`.
>> 
>> I took a brief look and left a comment on 04/26 last year.  I had
>> some time blocked for this topic today to take another look at the
>> whole series again.  Thanks for working on this.
>> 
>> It seems that the last three or so steps are new, relative to the
>> previous round.  I made sure that what is added back at step 24
>> exactly matches the result of merging sd/stash-wo-user-name into the
>> current 'master', but such a manual validation is error prone.  Is
>> it possible to avoid "remove the scripted one prematurely at step
>> 23, and then add it back as 'oops, that was wrong' fix at step 24"?
>> That would have been much more robust approach.
>
> Sorry, I should have thought of that. My mistake.
>
> As it is, Thomas verified that they are identical, so should we go forward
> with ps/stash-in-c as-is? I'd prefer that...

Yes, before sending the message you are responding to, I made sure
the scripted version added back is identical to the current one, and
also there is no in-flight updates/fixes to the scripted one.

The benefit that would come from a possible reroll to start the
series from the last three patches would be fairly limited.

Such a reorganized series would have allowed investigation of
regressions and bugs during the development comparing the original
and rewritten implementations slightly easier, but experience from
seeing the evolution of these "reimplement in C" topics tells us
that we see major part of the regression fallouts after the series
is declared "feature complete" anyway, so in the long run, the
less-than-ideal organization of the topic does not matter much in
practice.

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

* Re: [PATCH v12 18/26] stash: convert push to builtin
  2018-12-20 19:44   ` [PATCH v12 18/26] stash: convert push " Paul-Sebastian Ungureanu
@ 2019-02-08 11:30     ` SZEDER Gábor
  2019-02-10 22:17       ` Thomas Gummerer
  0 siblings, 1 reply; 106+ messages in thread
From: SZEDER Gábor @ 2019-02-08 11:30 UTC (permalink / raw)
  To: Paul-Sebastian Ungureanu; +Cc: git, t.gummerer, Johannes.Schindelin

On Thu, Dec 20, 2018 at 09:44:34PM +0200, Paul-Sebastian Ungureanu wrote:
> Add stash push to the helper.
> 
> Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

This patch causes rare failures in 't3903-stash.sh', I've seen it
break for the first time today in a Travis CI build:

  +echo bar3
  +echo bar4
  +git add file2
  +git stash -k
  Saved working directory and index state WIP on stashbranch: d3a23d9 alternate second
  +cat file
  +cat file2
  +test bar,bar4 = bar,bar2
  error: last command exited with $?=1
  not ok 20 - stash -k

Steps to reproduce:

  $ git checkout -f fa38428f76
  HEAD is now at fa38428f76 stash: convert push to builtin
  
  # fb7d1e3ac8 (test-lib: add the '--stress' option to run a test
  # repeatedly under load, 2019-01-05)
  $ git merge --no-commit fb7d1e3ac8
  Automatic merge went well; stopped before committing as requested
  $ make && cd t
  <snip>
  $ ./t3903-stash.sh --stress -r 1,13,14,20
  # wait, it tends to fail in <30 repetitions

I run stress testing on its parent for over 800 repetitions, no sign
of failure yet.

> ---
>  builtin/stash--helper.c | 245 +++++++++++++++++++++++++++++++++++++++-
>  git-stash.sh            |   6 +-
>  2 files changed, 245 insertions(+), 6 deletions(-)
> 
> diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
> index 080c2f7aa6..c77f62c895 100644
> --- a/builtin/stash--helper.c
> +++ b/builtin/stash--helper.c
> @@ -23,6 +23,9 @@ static const char * const git_stash_helper_usage[] = {
>  	N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
>  	N_("git stash--helper branch <branchname> [<stash>]"),
>  	N_("git stash--helper clear"),
> +	N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
> +	   "          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
> +	   "          [--] [<pathspec>...]]"),
>  	NULL
>  };
>  
> @@ -71,6 +74,13 @@ static const char * const git_stash_helper_create_usage[] = {
>  	NULL
>  };
>  
> +static const char * const git_stash_helper_push_usage[] = {
> +	N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
> +	   "          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
> +	   "          [--] [<pathspec>...]]"),
> +	NULL
> +};
> +
>  static const char *ref_stash = "refs/stash";
>  static struct strbuf stash_index_path = STRBUF_INIT;
>  
> @@ -1092,7 +1102,7 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
>  
>  static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
>  			   int include_untracked, int patch_mode,
> -			   struct stash_info *info)
> +			   struct stash_info *info, struct strbuf *patch)
>  {
>  	int ret = 0;
>  	int flags = 0;
> @@ -1105,7 +1115,6 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
>  	struct strbuf msg = STRBUF_INIT;
>  	struct strbuf commit_tree_label = STRBUF_INIT;
>  	struct strbuf untracked_files = STRBUF_INIT;
> -	struct strbuf patch = STRBUF_INIT;
>  
>  	prepare_fallback_ident("git stash", "git@stash");
>  
> @@ -1154,7 +1163,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
>  		untracked_commit_option = 1;
>  	}
>  	if (patch_mode) {
> -		ret = stash_patch(info, ps, &patch);
> +		ret = stash_patch(info, ps, patch);
>  		if (ret < 0) {
>  			fprintf_ln(stderr, _("Cannot save the current "
>  					     "worktree state"));
> @@ -1225,7 +1234,8 @@ static int create_stash(int argc, const char **argv, const char *prefix)
>  
>  	memset(&ps, 0, sizeof(ps));
>  	strbuf_addstr(&stash_msg_buf, stash_msg);
> -	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info);
> +	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
> +			      NULL);
>  
>  	if (!ret)
>  		printf_ln("%s", oid_to_hex(&info.w_commit));
> @@ -1239,6 +1249,231 @@ static int create_stash(int argc, const char **argv, const char *prefix)
>  	return ret < 0;
>  }
>  
> +static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
> +			 int keep_index, int patch_mode, int include_untracked)
> +{
> +	int ret = 0;
> +	struct stash_info info;
> +	struct strbuf patch = STRBUF_INIT;
> +	struct strbuf stash_msg_buf = STRBUF_INIT;
> +
> +	if (patch_mode && keep_index == -1)
> +		keep_index = 1;
> +
> +	if (patch_mode && include_untracked) {
> +		fprintf_ln(stderr, _("Can't use --patch and --include-untracked"
> +				     " or --all at the same time"));
> +		ret = -1;
> +		goto done;
> +	}
> +
> +	read_cache_preload(NULL);
> +	if (!include_untracked && ps.nr) {
> +		int i;
> +		char *ps_matched = xcalloc(ps.nr, 1);
> +
> +		for (i = 0; i < active_nr; i++)
> +			ce_path_match(&the_index, active_cache[i], &ps,
> +				      ps_matched);
> +
> +		if (report_path_error(ps_matched, &ps, NULL)) {
> +			fprintf_ln(stderr, _("Did you forget to 'git add'?"));
> +			ret = -1;
> +			free(ps_matched);
> +			goto done;
> +		}
> +		free(ps_matched);
> +	}
> +
> +	if (refresh_cache(REFRESH_QUIET)) {
> +		ret = -1;
> +		goto done;
> +	}
> +
> +	if (!check_changes(ps, include_untracked)) {
> +		if (!quiet)
> +			printf_ln(_("No local changes to save"));
> +		goto done;
> +	}
> +
> +	if (!reflog_exists(ref_stash) && do_clear_stash()) {
> +		ret = -1;
> +		fprintf_ln(stderr, _("Cannot initialize stash"));
> +		goto done;
> +	}
> +
> +	if (stash_msg)
> +		strbuf_addstr(&stash_msg_buf, stash_msg);
> +	if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,
> +			    &info, &patch)) {
> +		ret = -1;
> +		goto done;
> +	}
> +
> +	if (do_store_stash(&info.w_commit, stash_msg_buf.buf, 1)) {
> +		ret = -1;
> +		fprintf_ln(stderr, _("Cannot save the current status"));
> +		goto done;
> +	}
> +
> +	printf_ln(_("Saved working directory and index state %s"),
> +		  stash_msg_buf.buf);
> +
> +	if (!patch_mode) {
> +		if (include_untracked && !ps.nr) {
> +			struct child_process cp = CHILD_PROCESS_INIT;
> +
> +			cp.git_cmd = 1;
> +			argv_array_pushl(&cp.args, "clean", "--force",
> +					 "--quiet", "-d", NULL);
> +			if (include_untracked == INCLUDE_ALL_FILES)
> +				argv_array_push(&cp.args, "-x");
> +			if (run_command(&cp)) {
> +				ret = -1;
> +				goto done;
> +			}
> +		}
> +		if (ps.nr) {
> +			struct child_process cp_add = CHILD_PROCESS_INIT;
> +			struct child_process cp_diff = CHILD_PROCESS_INIT;
> +			struct child_process cp_apply = CHILD_PROCESS_INIT;
> +			struct strbuf out = STRBUF_INIT;
> +
> +			cp_add.git_cmd = 1;
> +			argv_array_push(&cp_add.args, "add");
> +			if (!include_untracked)
> +				argv_array_push(&cp_add.args, "-u");
> +			if (include_untracked == INCLUDE_ALL_FILES)
> +				argv_array_push(&cp_add.args, "--force");
> +			argv_array_push(&cp_add.args, "--");
> +			add_pathspecs(&cp_add.args, ps);
> +			if (run_command(&cp_add)) {
> +				ret = -1;
> +				goto done;
> +			}
> +
> +			cp_diff.git_cmd = 1;
> +			argv_array_pushl(&cp_diff.args, "diff-index", "-p",
> +					 "--cached", "--binary", "HEAD", "--",
> +					 NULL);
> +			add_pathspecs(&cp_diff.args, ps);
> +			if (pipe_command(&cp_diff, NULL, 0, &out, 0, NULL, 0)) {
> +				ret = -1;
> +				goto done;
> +			}
> +
> +			cp_apply.git_cmd = 1;
> +			argv_array_pushl(&cp_apply.args, "apply", "--index",
> +					 "-R", NULL);
> +			if (pipe_command(&cp_apply, out.buf, out.len, NULL, 0,
> +					 NULL, 0)) {
> +				ret = -1;
> +				goto done;
> +			}
> +		} else {
> +			struct child_process cp = CHILD_PROCESS_INIT;
> +			cp.git_cmd = 1;
> +			argv_array_pushl(&cp.args, "reset", "--hard", "-q",
> +					 NULL);
> +			if (run_command(&cp)) {
> +				ret = -1;
> +				goto done;
> +			}
> +		}
> +
> +		if (keep_index == 1 && !is_null_oid(&info.i_tree)) {
> +			struct child_process cp_ls = CHILD_PROCESS_INIT;
> +			struct child_process cp_checkout = CHILD_PROCESS_INIT;
> +			struct strbuf out = STRBUF_INIT;
> +
> +			if (reset_tree(&info.i_tree, 0, 1)) {
> +				ret = -1;
> +				goto done;
> +			}
> +
> +			cp_ls.git_cmd = 1;
> +			argv_array_pushl(&cp_ls.args, "ls-files", "-z",
> +					 "--modified", "--", NULL);
> +
> +			add_pathspecs(&cp_ls.args, ps);
> +			if (pipe_command(&cp_ls, NULL, 0, &out, 0, NULL, 0)) {
> +				ret = -1;
> +				goto done;
> +			}
> +
> +			cp_checkout.git_cmd = 1;
> +			argv_array_pushl(&cp_checkout.args, "checkout-index",
> +					 "-z", "--force", "--stdin", NULL);
> +			if (pipe_command(&cp_checkout, out.buf, out.len, NULL,
> +					 0, NULL, 0)) {
> +				ret = -1;
> +				goto done;
> +			}
> +		}
> +		goto done;
> +	} else {
> +		struct child_process cp = CHILD_PROCESS_INIT;
> +
> +		cp.git_cmd = 1;
> +		argv_array_pushl(&cp.args, "apply", "-R", NULL);
> +
> +		if (pipe_command(&cp, patch.buf, patch.len, NULL, 0, NULL, 0)) {
> +			fprintf_ln(stderr, _("Cannot remove worktree changes"));
> +			ret = -1;
> +			goto done;
> +		}
> +
> +		if (keep_index < 1) {
> +			struct child_process cp = CHILD_PROCESS_INIT;
> +
> +			cp.git_cmd = 1;
> +			argv_array_pushl(&cp.args, "reset", "-q", "--", NULL);
> +			add_pathspecs(&cp.args, ps);
> +			if (run_command(&cp)) {
> +				ret = -1;
> +				goto done;
> +			}
> +		}
> +		goto done;
> +	}
> +
> +done:
> +	strbuf_release(&stash_msg_buf);
> +	return ret;
> +}
> +
> +static int push_stash(int argc, const char **argv, const char *prefix)
> +{
> +	int keep_index = -1;
> +	int patch_mode = 0;
> +	int include_untracked = 0;
> +	int quiet = 0;
> +	const char *stash_msg = NULL;
> +	struct pathspec ps;
> +	struct option options[] = {
> +		OPT_BOOL('k', "keep-index", &keep_index,
> +			 N_("keep index")),
> +		OPT_BOOL('p', "patch", &patch_mode,
> +			 N_("stash in patch mode")),
> +		OPT__QUIET(&quiet, N_("quiet mode")),
> +		OPT_BOOL('u', "include-untracked", &include_untracked,
> +			 N_("include untracked files in stash")),
> +		OPT_SET_INT('a', "all", &include_untracked,
> +			    N_("include ignore files"), 2),
> +		OPT_STRING('m', "message", &stash_msg, N_("message"),
> +			   N_("stash message")),
> +		OPT_END()
> +	};
> +
> +	argc = parse_options(argc, argv, prefix, options,
> +			     git_stash_helper_push_usage,
> +			     0);
> +
> +	parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL, prefix, argv);
> +	return do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode,
> +			     include_untracked);
> +}
> +
>  int cmd_stash__helper(int argc, const char **argv, const char *prefix)
>  {
>  	pid_t pid = getpid();
> @@ -1277,6 +1512,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
>  		return !!store_stash(argc, argv, prefix);
>  	else if (!strcmp(argv[0], "create"))
>  		return !!create_stash(argc, argv, prefix);
> +	else if (!strcmp(argv[0], "push"))
> +		return !!push_stash(argc, argv, prefix);
>  
>  	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
>  		      git_stash_helper_usage, options);
> diff --git a/git-stash.sh b/git-stash.sh
> index a9b3064ff0..51d7a06601 100755
> --- a/git-stash.sh
> +++ b/git-stash.sh
> @@ -429,7 +429,8 @@ save)
>  	;;
>  push)
>  	shift
> -	push_stash "$@"
> +	cd "$START_DIR"
> +	git stash--helper push "$@"
>  	;;
>  apply)
>  	shift
> @@ -465,7 +466,8 @@ branch)
>  *)
>  	case $# in
>  	0)
> -		push_stash &&
> +		cd "$START_DIR"
> +		git stash--helper push &&
>  		say "$(gettext "(To restore them type \"git stash apply\")")"
>  		;;
>  	*)
> -- 
> 2.20.1.441.g764a526393
> 

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

* Re: [PATCH v12 18/26] stash: convert push to builtin
  2019-02-08 11:30     ` SZEDER Gábor
@ 2019-02-10 22:17       ` Thomas Gummerer
  2019-02-11  1:13         ` SZEDER Gábor
  0 siblings, 1 reply; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-10 22:17 UTC (permalink / raw)
  To: SZEDER Gábor; +Cc: Paul-Sebastian Ungureanu, git, Johannes.Schindelin

On 02/08, SZEDER Gábor wrote:
> On Thu, Dec 20, 2018 at 09:44:34PM +0200, Paul-Sebastian Ungureanu wrote:
> > Add stash push to the helper.
> > 
> > Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
> 
> This patch causes rare failures in 't3903-stash.sh', I've seen it
> break for the first time today in a Travis CI build:

Thanks for reporting this.  I was going to take a look at it, but
unfortunately I can't seem to reproduce the issue even with --stress
(I let it run for ~1000 repetitions and then aborted it).

Which platform did you see/test this on, and which compile options did
you use?  I went through a failures on
https://travis-ci.org/git/git/builds, but couldn't find this
particular one.  Could you point me at the failed run?

>   +echo bar3
>   +echo bar4
>   +git add file2
>   +git stash -k
>   Saved working directory and index state WIP on stashbranch: d3a23d9 alternate second
>   +cat file
>   +cat file2
>   +test bar,bar4 = bar,bar2
>   error: last command exited with $?=1
>   not ok 20 - stash -k
> 
> Steps to reproduce:
> 
>   $ git checkout -f fa38428f76
>   HEAD is now at fa38428f76 stash: convert push to builtin
>   
>   # fb7d1e3ac8 (test-lib: add the '--stress' option to run a test
>   # repeatedly under load, 2019-01-05)
>   $ git merge --no-commit fb7d1e3ac8
>   Automatic merge went well; stopped before committing as requested
>   $ make && cd t
>   <snip>
>   $ ./t3903-stash.sh --stress -r 1,13,14,20
>   # wait, it tends to fail in <30 repetitions
> 
> I run stress testing on its parent for over 800 repetitions, no sign
> of failure yet.

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

* Re: [PATCH v12 18/26] stash: convert push to builtin
  2019-02-10 22:17       ` Thomas Gummerer
@ 2019-02-11  1:13         ` SZEDER Gábor
  2019-02-12 23:18           ` Thomas Gummerer
  0 siblings, 1 reply; 106+ messages in thread
From: SZEDER Gábor @ 2019-02-11  1:13 UTC (permalink / raw)
  To: Thomas Gummerer; +Cc: Paul-Sebastian Ungureanu, git, Johannes.Schindelin

On Sun, Feb 10, 2019 at 10:17:12PM +0000, Thomas Gummerer wrote:
> On 02/08, SZEDER Gábor wrote:
> > On Thu, Dec 20, 2018 at 09:44:34PM +0200, Paul-Sebastian Ungureanu wrote:
> > > Add stash push to the helper.
> > > 
> > > Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
> > 
> > This patch causes rare failures in 't3903-stash.sh', I've seen it
> > break for the first time today in a Travis CI build:
> 
> Thanks for reporting this.  I was going to take a look at it, but
> unfortunately I can't seem to reproduce the issue even with --stress
> (I let it run for ~1000 repetitions and then aborted it).
> 
> Which platform did you see/test this on, and which compile options did
> you use?  I went through a failures on
> https://travis-ci.org/git/git/builds, but couldn't find this
> particular one.  Could you point me at the failed run?

It was in a Linux build job in one of my custom CI builds [1]:

  https://travis-ci.org/szeder/git-cooking-topics-for-travis-ci/jobs/490420713#L3401

and I tested it locally on Linux as well.  I don't think there are any
unusual compiler options.  I run it with --stress on Travis CI's macOS
as well, but couldn't trigger a failure there.


[1] That build log doesn't include the trash dir of the failed test
    in the log, most likely because of a faulty merge conflict
    resolution on my part, but you can find a failed trash dir
    embedded here:

      https://travis-ci.org/szeder/git/jobs/491401882#L2309


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

* Re: [PATCH v12 18/26] stash: convert push to builtin
  2019-02-11  1:13         ` SZEDER Gábor
@ 2019-02-12 23:18           ` Thomas Gummerer
  2019-02-19  0:23             ` SZEDER Gábor
  0 siblings, 1 reply; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-12 23:18 UTC (permalink / raw)
  To: SZEDER Gábor; +Cc: Paul-Sebastian Ungureanu, git, Johannes.Schindelin

On 02/11, SZEDER Gábor wrote:
> On Sun, Feb 10, 2019 at 10:17:12PM +0000, Thomas Gummerer wrote:
> > On 02/08, SZEDER Gábor wrote:
> > > On Thu, Dec 20, 2018 at 09:44:34PM +0200, Paul-Sebastian Ungureanu wrote:
> > > > Add stash push to the helper.
> > > > 
> > > > Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
> > > 
> > > This patch causes rare failures in 't3903-stash.sh', I've seen it
> > > break for the first time today in a Travis CI build:
> > 
> > Thanks for reporting this.  I was going to take a look at it, but
> > unfortunately I can't seem to reproduce the issue even with --stress
> > (I let it run for ~1000 repetitions and then aborted it).
> > 
> > Which platform did you see/test this on, and which compile options did
> > you use?  I went through a failures on
> > https://travis-ci.org/git/git/builds, but couldn't find this
> > particular one.  Could you point me at the failed run?
> 
> It was in a Linux build job in one of my custom CI builds [1]:
> 
>   https://travis-ci.org/szeder/git-cooking-topics-for-travis-ci/jobs/490420713#L3401
> 
> and I tested it locally on Linux as well.  I don't think there are any
> unusual compiler options.  I run it with --stress on Travis CI's macOS
> as well, but couldn't trigger a failure there.

Thanks.  I still didn't manage to reproduce it locally, but I was now
able to test it on Travis CI.

The diff below fixes the issue, but I still need to spend some time to
better understand why it does.  I'll hopefully be in a position to
send a patch with a proper log message why this is the right fix in
the next couple of days.

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index c77f62c895..3dab488bd6 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -231,6 +231,7 @@ static int reset_tree(struct object_id *i_tree, int update, int reset)
 	struct tree *tree;
 	struct lock_file lock_file = LOCK_INIT;
 
+	discard_cache();
 	read_cache_preload(NULL);
 	if (refresh_cache(REFRESH_QUIET))
 		return -1;


> [1] That build log doesn't include the trash dir of the failed test
>     in the log, most likely because of a faulty merge conflict
>     resolution on my part, but you can find a failed trash dir
>     embedded here:
> 
>       https://travis-ci.org/szeder/git/jobs/491401882#L2309
> 

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

* Re: [PATCH v12 18/26] stash: convert push to builtin
  2019-02-12 23:18           ` Thomas Gummerer
@ 2019-02-19  0:23             ` SZEDER Gábor
  2019-02-19 10:47               ` Johannes Schindelin
  0 siblings, 1 reply; 106+ messages in thread
From: SZEDER Gábor @ 2019-02-19  0:23 UTC (permalink / raw)
  To: Thomas Gummerer; +Cc: Paul-Sebastian Ungureanu, git, Johannes.Schindelin

On Tue, Feb 12, 2019 at 11:18:37PM +0000, Thomas Gummerer wrote:
> Thanks.  I still didn't manage to reproduce it locally, but I was now
> able to test it on Travis CI.
> 
> The diff below fixes the issue, but I still need to spend some time to
> better understand why it does.

There is nothing like a fix that works, but you have no idea why :)

FWIW, I'm at a couple of thousands of '--stress' repetitions with your
patch below, and not a single failure yet.


> I'll hopefully be in a position to
> send a patch with a proper log message why this is the right fix in
> the next couple of days.
> 
> diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
> index c77f62c895..3dab488bd6 100644
> --- a/builtin/stash--helper.c
> +++ b/builtin/stash--helper.c
> @@ -231,6 +231,7 @@ static int reset_tree(struct object_id *i_tree, int update, int reset)
>  	struct tree *tree;
>  	struct lock_file lock_file = LOCK_INIT;
>  
> +	discard_cache();
>  	read_cache_preload(NULL);
>  	if (refresh_cache(REFRESH_QUIET))
>  		return -1;
> 
> 

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

* Re: [PATCH v12 18/26] stash: convert push to builtin
  2019-02-19  0:23             ` SZEDER Gábor
@ 2019-02-19 10:47               ` Johannes Schindelin
  2019-02-19 19:59                 ` Junio C Hamano
                                   ` (2 more replies)
  0 siblings, 3 replies; 106+ messages in thread
From: Johannes Schindelin @ 2019-02-19 10:47 UTC (permalink / raw)
  To: SZEDER Gábor; +Cc: Thomas Gummerer, Paul-Sebastian Ungureanu, git

[-- Attachment #1: Type: text/plain, Size: 4622 bytes --]

Hi Gábor & Thomas,

On Tue, 19 Feb 2019, SZEDER Gábor wrote:

> On Tue, Feb 12, 2019 at 11:18:37PM +0000, Thomas Gummerer wrote:
> > Thanks.  I still didn't manage to reproduce it locally, but I was now
> > able to test it on Travis CI.
> > 
> > The diff below fixes the issue, but I still need to spend some time to
> > better understand why it does.
> 
> There is nothing like a fix that works, but you have no idea why :)

I know why. Now. See below for the analysis.

> FWIW, I'm at a couple of thousands of '--stress' repetitions with your
> patch below, and not a single failure yet.

Good, and yes, there is a problem.

> > I'll hopefully be in a position to
> > send a patch with a proper log message why this is the right fix in
> > the next couple of days.
> > 
> > diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
> > index c77f62c895..3dab488bd6 100644
> > --- a/builtin/stash--helper.c
> > +++ b/builtin/stash--helper.c
> > @@ -231,6 +231,7 @@ static int reset_tree(struct object_id *i_tree, int update, int reset)
> >  	struct tree *tree;
> >  	struct lock_file lock_file = LOCK_INIT;
> >  
> > +	discard_cache();
> >  	read_cache_preload(NULL);
> >  	if (refresh_cache(REFRESH_QUIET))
> >  		return -1;
> > 

So this is working, but it is not the correct spot for that
`discard_cache()`, as it forces unnecessary cycles on code paths calling
`reset_tree()` (which corresponds to `git read-tree`, admittedly a bit
confusing) with a fully up to date index.

The real fix, I believe, is this:

-- snip --
diff --git a/builtin/stash.c b/builtin/stash.c
index 2d6dfce883..516dee0fa4 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -1372,6 +1372,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
 			}
 		} else {
 			struct child_process cp = CHILD_PROCESS_INIT;
+			discard_cache();
 			cp.git_cmd = 1;
 			argv_array_pushl(&cp.args, "reset", "--hard", "-q",
 					 NULL);
-- snap --

And the reason this is needed: we spawn a `git reset --hard` here, which
will change the index, but outside of the current process. So the
in-process copy is stale. And when the index' mtime does not help us
detect that, we run into that test breakage.

Now, I seriously believe that we missed the best time to move
ps/stash-in-c into `next` for cooking. The best time would have been just
after Paul submitted the latest patch series: we know for a fact that he
is too busy to really take care of this patch series, so keeping it in
`pu` puts everybody into that awkward spot where nobody wants to step on
Paul's toes messing with his patch series, but where Paul also lacks the
time to push it further, so everything is stuck in a limbo and is *so very
much* not cooking at all. You might say that it has turned bad because we
failed to stoke the fire appropriately.

Since it is now way too late in the v2.21.0 process, this problem is only
exacerbated, because it won't even enter `next` "better late than never".

To address this unfortunate situation, my current plan is to take over
from Paul (we had been chatting about this privately in the past, and he
is okay with this because of University eating all his time).

I will open the whole bag again, most likely squashing the late fixups
into the patches that introduced the problems, re-review with a much finer
comb than the patch series has enjoyed on the Git mailing list (even just
a quick look at `do_apply_stash()` revealed an unnecessary `reset_tree()`
call that *no* reviewer spotted, even I myself, but then, I am hardly
solely responsible for that review), and most likely I'll even take my
sweet little time changing the code to avoid more spawned Git processes.

It will take a long time, and the `stash` project that has been discussed
recently to be given to GSoC students is no longer available, as I will
take care of it before GSoC even starts, and I won't spend much time
reviewing other people's code in the meantime.

I will start that only after v2.21.0 final is out, obviously.

Once I submit a new iteration, it will look quite a bit different from
before, and reviewers will have to re-review *everything*, wasting
everybody's time even more. It will have to be re-reviewed in its entirety
anyway because it has been *such* a long time since the latest review, and
that's just the price we all have to pay for missing the right moment to
advance this to `next`. Thomas, I will ask you to review, and Gábor, I
will expect you to review that iteration, too, as you are now a bit
familiar with the code, and I will really need your help here.

Anyway, that's my plan for now.

Ciao,
Dscho

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

* Re: [PATCH v12 18/26] stash: convert push to builtin
  2019-02-19 10:47               ` Johannes Schindelin
@ 2019-02-19 19:59                 ` Junio C Hamano
  2019-02-20 21:01                   ` Johannes Schindelin
  2019-02-19 23:59                 ` Thomas Gummerer
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
  2 siblings, 1 reply; 106+ messages in thread
From: Junio C Hamano @ 2019-02-19 19:59 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: SZEDER Gábor, Thomas Gummerer, Paul-Sebastian Ungureanu, git

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

>> > diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
>> > index c77f62c895..3dab488bd6 100644
>> > --- a/builtin/stash--helper.c
>> > +++ b/builtin/stash--helper.c
>> > @@ -231,6 +231,7 @@ static int reset_tree(struct object_id *i_tree, int update, int reset)
>> >  	struct tree *tree;
>> >  	struct lock_file lock_file = LOCK_INIT;
>> >  
>> > +	discard_cache();
>> >  	read_cache_preload(NULL);
>> >  	if (refresh_cache(REFRESH_QUIET))
>> >  		return -1;
>> > 
>
> So this is working, but it is not the correct spot for that
> `discard_cache()`, as it forces unnecessary cycles on code paths calling
> `reset_tree()` (which corresponds to `git read-tree`, admittedly a bit
> confusing) with a fully up to date index.
>
> The real fix, I believe, is this:
>
> -- snip --
> diff --git a/builtin/stash.c b/builtin/stash.c
> index 2d6dfce883..516dee0fa4 100644
> --- a/builtin/stash.c
> +++ b/builtin/stash.c
> @@ -1372,6 +1372,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
>  			}
>  		} else {
>  			struct child_process cp = CHILD_PROCESS_INIT;
> +			discard_cache();
>  			cp.git_cmd = 1;
>  			argv_array_pushl(&cp.args, "reset", "--hard", "-q",
>  					 NULL);
> -- snap --
>
> And the reason this is needed: we spawn a `git reset --hard` here, which
> will change the index, but outside of the current process. So the
> in-process copy is stale. And when the index' mtime does not help us
> detect that, we run into that test breakage.

In non-patch mode with pathspec, there is an invocation of "apply
--index -R" of a patch that takes the contents of the HEAD to what
is in the index, updating the on-disk index and making our in-core
copy stale.  Wouldn't we need to do the same?  Otherwise, the same
"reset_tree()" you are tryhing to protect with this discard_cache()
will call read_cache_preload(), no?

Among the calls to reset_tree() in this file, I think the one that
follows the "reset --hard" (your fix above) and "apply --index -R"
(the other side of the same if/else) is the only one that wants to
read from the result of an external command we just spawned from the
on-disk index, so perhaps moving discard_cache() to just before that
call may be a better fix.




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

* Re: [PATCH v12 18/26] stash: convert push to builtin
  2019-02-19 10:47               ` Johannes Schindelin
  2019-02-19 19:59                 ` Junio C Hamano
@ 2019-02-19 23:59                 ` Thomas Gummerer
  2019-02-20  4:37                   ` Junio C Hamano
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
  2 siblings, 1 reply; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-19 23:59 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: SZEDER Gábor, Paul-Sebastian Ungureanu, git

On 02/19, Johannes Schindelin wrote:
> Hi Gábor & Thomas,
> 
> On Tue, 19 Feb 2019, SZEDER Gábor wrote:
> 
> > > I'll hopefully be in a position to
> > > send a patch with a proper log message why this is the right fix in
> > > the next couple of days.
> > > 
> > > diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
> > > index c77f62c895..3dab488bd6 100644
> > > --- a/builtin/stash--helper.c
> > > +++ b/builtin/stash--helper.c
> > > @@ -231,6 +231,7 @@ static int reset_tree(struct object_id *i_tree, int update, int reset)
> > >  	struct tree *tree;
> > >  	struct lock_file lock_file = LOCK_INIT;
> > >  
> > > +	discard_cache();
> > >  	read_cache_preload(NULL);
> > >  	if (refresh_cache(REFRESH_QUIET))
> > >  		return -1;
> > > 
> 
> So this is working, but it is not the correct spot for that
> `discard_cache()`, as it forces unnecessary cycles on code paths calling
> `reset_tree()` (which corresponds to `git read-tree`, admittedly a bit
> confusing) with a fully up to date index.
> 
> The real fix, I believe, is this:
> 
> -- snip --
> diff --git a/builtin/stash.c b/builtin/stash.c
> index 2d6dfce883..516dee0fa4 100644
> --- a/builtin/stash.c
> +++ b/builtin/stash.c
> @@ -1372,6 +1372,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
>  			}
>  		} else {
>  			struct child_process cp = CHILD_PROCESS_INIT;
> +			discard_cache();
>  			cp.git_cmd = 1;
>  			argv_array_pushl(&cp.args, "reset", "--hard", "-q",
>  					 NULL);
> -- snap --
> 
> And the reason this is needed: we spawn a `git reset --hard` here, which
> will change the index, but outside of the current process. So the
> in-process copy is stale. And when the index' mtime does not help us
> detect that, we run into that test breakage.

Right, I agree with this assessment, and also agree that this is a
better place to discard the cache, rather than doing it in
'reset_tree()'.

> Now, I seriously believe that we missed the best time to move
> ps/stash-in-c into `next` for cooking. The best time would have been just
> after Paul submitted the latest patch series: we know for a fact that he
> is too busy to really take care of this patch series, so keeping it in
> `pu` puts everybody into that awkward spot where nobody wants to step on
> Paul's toes messing with his patch series, but where Paul also lacks the
> time to push it further, so everything is stuck in a limbo and is *so very
> much* not cooking at all. You might say that it has turned bad because we
> failed to stoke the fire appropriately.
> 
> Since it is now way too late in the v2.21.0 process, this problem is only
> exacerbated, because it won't even enter `next` "better late than never".
>
> To address this unfortunate situation, my current plan is to take over
> from Paul (we had been chatting about this privately in the past, and he
> is okay with this because of University eating all his time).
> 
> I will open the whole bag again, most likely squashing the late fixups
> into the patches that introduced the problems, re-review with a much finer
> comb than the patch series has enjoyed on the Git mailing list (even just
> a quick look at `do_apply_stash()` revealed an unnecessary `reset_tree()`
> call that *no* reviewer spotted, even I myself, but then, I am hardly
> solely responsible for that review), and most likely I'll even take my
> sweet little time changing the code to avoid more spawned Git processes.
> 
> It will take a long time, and the `stash` project that has been discussed
> recently to be given to GSoC students is no longer available, as I will
> take care of it before GSoC even starts, and I won't spend much time
> reviewing other people's code in the meantime.
>
> I will start that only after v2.21.0 final is out, obviously.
> 
> Once I submit a new iteration, it will look quite a bit different from
> before, and reviewers will have to re-review *everything*, wasting
> everybody's time even more. It will have to be re-reviewed in its entirety
> anyway because it has been *such* a long time since the latest review, and
> that's just the price we all have to pay for missing the right moment to
> advance this to `next`. Thomas, I will ask you to review, and Gábor, I
> will expect you to review that iteration, too, as you are now a bit
> familiar with the code, and I will really need your help here.
> 
> Anyway, that's my plan for now.

I must say I am not very happy about this plan.  The series has been
marked as "Will merge to 'next'" in previous iterations, but then we
found some issues that prevented that.  However I thought we were fine
fixing those on top at this point, rather than starting with a new
iteration again.

I was always under the impression that once the problem that was
discovered here was fixed we'd advance the series to 'next' with the
patch that comes out of this discussion on top.  Whether it's in next
shortly before 2.21 or not doesn't seem to make much of a difference
to me, as this wasn't going to make the 2.21 release anyway.  My hope
was that we could get it into 'next' shortly after 2.21 is released to
get the series some further exposure (which may well turn up some
other issues that we are not aware of yet, but such is the life of
software).

Sure there are some things that no reviewer spotted, but I'd think so
will there be next time and the time after that.  I don't believe that
code review can eliminate all issues, but I think we all did our best
to avoid a good chunk of them and unfortunately missed some in the
process.

Reviewing this series again in a slightly new form is not something I
am personally looking forward to.  I'd be more than happy to review
patches on top of this series, but after reading it many times over,
in slightly different versions, it gets harder and harder to review
and to find the motivation to review this.  Now if we really think
this series has to be completely overhauled, so be it, but I don't
think we would end up with a better end result than if we were to
continue to work on top of this.

> Ciao,
> Dscho


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

* Re: [PATCH v12 18/26] stash: convert push to builtin
  2019-02-19 23:59                 ` Thomas Gummerer
@ 2019-02-20  4:37                   ` Junio C Hamano
  2019-02-20 21:10                     ` Johannes Schindelin
  2019-02-20 22:30                     ` Thomas Gummerer
  0 siblings, 2 replies; 106+ messages in thread
From: Junio C Hamano @ 2019-02-20  4:37 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: Johannes Schindelin, SZEDER Gábor, Paul-Sebastian Ungureanu, git

Thomas Gummerer <t.gummerer@gmail.com> writes:

>> Now, I seriously believe that we missed the best time to move
>> ps/stash-in-c into `next` for cooking. The best time would have been just
>> ...
>> Anyway, that's my plan for now.
>
> I must say I am not very happy about this plan.  The series has been
> marked as "Will merge to 'next'" in previous iterations, but then we
> found some issues that prevented that.  However I thought we were fine
> fixing those on top at this point, rather than starting with a new
> iteration again.

First before going into anything else, let me thank, and let me
invite readers of this thread to join me thanking, Paul (Sebi) for
sticking with this topic for this long.  It is above and beyond what
GSoC calls for.

Having said that.

I too was somehow led to believe that the topic was in a good enough
shape, with some room for clean-up by reordering the patches to make
them into a more logical progression and squashing an existing and
recently figured out "oops, that was wrong" fixes into the patches
where the breakages originate.

And that was where the "Will merge to" originally came from.  Thanks
to tools like range-diff, a topic that goes through such reordering
and squashing of patches should not have to "waste" a lot of review
cycles out of those who have seen the previous round.

It however is a totally different matter if the topic was so
unsalvageable that it needs a total rewrite---that would need
another round of careful review, of course, and it would be
irresponsive to merge a topic in such a messy state to 'next'.  But
my impression was that the topic was not _that_ bad, so Dscho's
message and the plan were something that was totally unexpected to
me, too..

> I was always under the impression that once the problem that was
> discovered here was fixed we'd advance the series to 'next' with the
> patch that comes out of this discussion on top.  Whether it's in next
> shortly before 2.21 or not doesn't seem to make much of a difference
> to me, as this wasn't going to make the 2.21 release anyway.  My hope
> was that we could get it into 'next' shortly after 2.21 is released to
> get the series some further exposure (which may well turn up some
> other issues that we are not aware of yet, but such is the life of
> software).

I was hoping similar, but also was hoping that people would use the
time wisely while waiting for the next cycle to polish the topic with
reordering and squashing, so that it can hit 'next' early once the
tree opens.

Anyway.

I actually have a different issue with this topic, though.  It is
wonderful to see a GSoC student's continued involvement in the
project, but it is not healthy that we need so much work on top of
what was marked "done" at the end of the GSoC period.  Especially
the impression I am getting for the post GSoC work of this topic is
not "we are already done converting to built-in during GSoC, and now
we are extending the command", but "we ran out of time during GSoC;
here is what we would have seen at the end of GSoC in an ideal
world."

I wonder if this is an unfortunate indication that our expectation
is unrealistically high when we accept students' applications.
Being overly ambitious is *not* students' fault, but those of us on
the list, especially those who mentor, have far deeper experience
with how our code and project is structured than any students do.
We should be able to, and should not hesitate to, say things like
"that's overly ambitious---for such and such, you'd need to even
invent an internal API---can we reduce the scope and still produce a
useful end result?"

One suggestion I have is to have success criteria (e.g. "gets merged
to 'master' before GSoC ends" [*1*]) clearly spelled out in the
application.  Something like that would help managing the
expectation and biting way too much for a summer, I'd hope.

    Side note *1*.  Of course, depending on the alignment of the
    stars ^W our ~10-12 week development cycle and the end of GSoC,
    getting merged to 'master' might become impossible if it
    coincides with the pre-release freeze period.  But we on the
    list and the mentors know how the project works, and can help
    stating a more realistic success criterion if the development
    cycle and other quirks specific to this project gets in the way.

Thanks.

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

* Re: [PATCH v12 18/26] stash: convert push to builtin
  2019-02-19 19:59                 ` Junio C Hamano
@ 2019-02-20 21:01                   ` Johannes Schindelin
  0 siblings, 0 replies; 106+ messages in thread
From: Johannes Schindelin @ 2019-02-20 21:01 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: SZEDER Gábor, Thomas Gummerer, Paul-Sebastian Ungureanu, git

Hi Junio,

On Tue, 19 Feb 2019, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> >> > diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
> >> > index c77f62c895..3dab488bd6 100644
> >> > --- a/builtin/stash--helper.c
> >> > +++ b/builtin/stash--helper.c
> >> > @@ -231,6 +231,7 @@ static int reset_tree(struct object_id *i_tree, int update, int reset)
> >> >  	struct tree *tree;
> >> >  	struct lock_file lock_file = LOCK_INIT;
> >> >  
> >> > +	discard_cache();
> >> >  	read_cache_preload(NULL);
> >> >  	if (refresh_cache(REFRESH_QUIET))
> >> >  		return -1;
> >> > 
> >
> > So this is working, but it is not the correct spot for that
> > `discard_cache()`, as it forces unnecessary cycles on code paths calling
> > `reset_tree()` (which corresponds to `git read-tree`, admittedly a bit
> > confusing) with a fully up to date index.
> >
> > The real fix, I believe, is this:
> >
> > -- snip --
> > diff --git a/builtin/stash.c b/builtin/stash.c
> > index 2d6dfce883..516dee0fa4 100644
> > --- a/builtin/stash.c
> > +++ b/builtin/stash.c
> > @@ -1372,6 +1372,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
> >  			}
> >  		} else {
> >  			struct child_process cp = CHILD_PROCESS_INIT;
> > +			discard_cache();
> >  			cp.git_cmd = 1;
> >  			argv_array_pushl(&cp.args, "reset", "--hard", "-q",
> >  					 NULL);
> > -- snap --
> >
> > And the reason this is needed: we spawn a `git reset --hard` here, which
> > will change the index, but outside of the current process. So the
> > in-process copy is stale. And when the index' mtime does not help us
> > detect that, we run into that test breakage.
> 
> In non-patch mode with pathspec, there is an invocation of "apply
> --index -R" of a patch that takes the contents of the HEAD to what
> is in the index, updating the on-disk index and making our in-core
> copy stale.  Wouldn't we need to do the same?  Otherwise, the same
> "reset_tree()" you are tryhing to protect with this discard_cache()
> will call read_cache_preload(), no?
> 
> Among the calls to reset_tree() in this file, I think the one that
> follows the "reset --hard" (your fix above) and "apply --index -R"
> (the other side of the same if/else) is the only one that wants to
> read from the result of an external command we just spawned from the
> on-disk index, so perhaps moving discard_cache() to just before that
> call may be a better fix.

Good catch!
Dscho

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

* Re: [PATCH v12 18/26] stash: convert push to builtin
  2019-02-20  4:37                   ` Junio C Hamano
@ 2019-02-20 21:10                     ` Johannes Schindelin
  2019-02-20 22:30                     ` Thomas Gummerer
  1 sibling, 0 replies; 106+ messages in thread
From: Johannes Schindelin @ 2019-02-20 21:10 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Thomas Gummerer, SZEDER Gábor, Paul-Sebastian Ungureanu, git

Hi Junio,

On Tue, 19 Feb 2019, Junio C Hamano wrote:

> Thomas Gummerer <t.gummerer@gmail.com> writes:
> 
> >> Now, I seriously believe that we missed the best time to move
> >> ps/stash-in-c into `next` for cooking. The best time would have been just
> >> ...
> >> Anyway, that's my plan for now.
> >
> > I must say I am not very happy about this plan.  The series has been
> > marked as "Will merge to 'next'" in previous iterations, but then we
> > found some issues that prevented that.  However I thought we were fine
> > fixing those on top at this point, rather than starting with a new
> > iteration again.
> 
> First before going into anything else, let me thank, and let me
> invite readers of this thread to join me thanking, Paul (Sebi) for
> sticking with this topic for this long.  It is above and beyond what
> GSoC calls for.

Indeed. He put in quite a few dozen hours of work *after* GSoC.

> Having said that.
> 
> I too was somehow led to believe that the topic was in a good enough
> shape, with some room for clean-up by reordering the patches to make
> them into a more logical progression and squashing an existing and
> recently figured out "oops, that was wrong" fixes into the patches
> where the breakages originate.
> 
> And that was where the "Will merge to" originally came from.  Thanks
> to tools like range-diff, a topic that goes through such reordering
> and squashing of patches should not have to "waste" a lot of review
> cycles out of those who have seen the previous round.
> 
> It however is a totally different matter if the topic was so
> unsalvageable that it needs a total rewrite---that would need
> another round of careful review, of course, and it would be
> irresponsive to merge a topic in such a messy state to 'next'.  But
> my impression was that the topic was not _that_ bad, so Dscho's
> message and the plan were something that was totally unexpected to
> me, too..

My throwing hands into the air and giving up on advancing it in the
current form to `next` was based on its rotting away in `pu`...

If it had been in `next` while Hannes, others and I found and fixed bugs,
then it would truly be cooking, but in `pu`? It kind of stopped pretty
much all the development around it.

And that's the only reason why I came up with that plan (that will require
at least 50 hours from my side, I know that): because I thought that your
not advancing the patches to `next` meant that this extra work was exactly
what you were expecting.

> > I was always under the impression that once the problem that was
> > discovered here was fixed we'd advance the series to 'next' with the
> > patch that comes out of this discussion on top.  Whether it's in next
> > shortly before 2.21 or not doesn't seem to make much of a difference
> > to me, as this wasn't going to make the 2.21 release anyway.  My hope
> > was that we could get it into 'next' shortly after 2.21 is released to
> > get the series some further exposure (which may well turn up some
> > other issues that we are not aware of yet, but such is the life of
> > software).
> 
> I was hoping similar, but also was hoping that people would use the
> time wisely while waiting for the next cycle to polish the topic with
> reordering and squashing, so that it can hit 'next' early once the
> tree opens.
> 
> Anyway.
> 
> I actually have a different issue with this topic, though.  It is
> wonderful to see a GSoC student's continued involvement in the
> project, but it is not healthy that we need so much work on top of
> what was marked "done" at the end of the GSoC period.  Especially
> the impression I am getting for the post GSoC work of this topic is
> not "we are already done converting to built-in during GSoC, and now
> we are extending the command", but "we ran out of time during GSoC;
> here is what we would have seen at the end of GSoC in an ideal
> world."
> 
> I wonder if this is an unfortunate indication that our expectation
> is unrealistically high when we accept students' applications.

Yes, you are right. This is on me. Guilty as charged.

And I don't mean this flippantly. I thought long and hard about this, and
I've come to the conclusion that I will not offer to mentor this round of
GSoC as a consequence.

Ciao,
Dscho

> Being overly ambitious is *not* students' fault, but those of us on
> the list, especially those who mentor, have far deeper experience
> with how our code and project is structured than any students do.
> We should be able to, and should not hesitate to, say things like
> "that's overly ambitious---for such and such, you'd need to even
> invent an internal API---can we reduce the scope and still produce a
> useful end result?"
> 
> One suggestion I have is to have success criteria (e.g. "gets merged
> to 'master' before GSoC ends" [*1*]) clearly spelled out in the
> application.  Something like that would help managing the
> expectation and biting way too much for a summer, I'd hope.
> 
>     Side note *1*.  Of course, depending on the alignment of the
>     stars ^W our ~10-12 week development cycle and the end of GSoC,
>     getting merged to 'master' might become impossible if it
>     coincides with the pre-release freeze period.  But we on the
>     list and the mentors know how the project works, and can help
>     stating a more realistic success criterion if the development
>     cycle and other quirks specific to this project gets in the way.
> 
> Thanks.
> 

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

* Re: [PATCH v12 18/26] stash: convert push to builtin
  2019-02-20  4:37                   ` Junio C Hamano
  2019-02-20 21:10                     ` Johannes Schindelin
@ 2019-02-20 22:30                     ` Thomas Gummerer
  1 sibling, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-20 22:30 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin, SZEDER Gábor, Paul-Sebastian Ungureanu,
	git, Olga Telezhnaya, Christian Couder

On 02/19, Junio C Hamano wrote:
> Thomas Gummerer <t.gummerer@gmail.com> writes:
> 
> >> Now, I seriously believe that we missed the best time to move
> >> ps/stash-in-c into `next` for cooking. The best time would have been just
> >> ...
> >> Anyway, that's my plan for now.
> >
> > I must say I am not very happy about this plan.  The series has been
> > marked as "Will merge to 'next'" in previous iterations, but then we
> > found some issues that prevented that.  However I thought we were fine
> > fixing those on top at this point, rather than starting with a new
> > iteration again.
> 
> First before going into anything else, let me thank, and let me
> invite readers of this thread to join me thanking, Paul (Sebi) for
> sticking with this topic for this long.  It is above and beyond what
> GSoC calls for.

Indeed, thanks for all your work on this Paul-Sebastian!

> Having said that.
> 
> I too was somehow led to believe that the topic was in a good enough
> shape, with some room for clean-up by reordering the patches to make
> them into a more logical progression and squashing an existing and
> recently figured out "oops, that was wrong" fixes into the patches
> where the breakages originate.
> 
> And that was where the "Will merge to" originally came from.  Thanks
> to tools like range-diff, a topic that goes through such reordering
> and squashing of patches should not have to "waste" a lot of review
> cycles out of those who have seen the previous round.

Right, I had the impression that we were okay with doing the cleanups
on top of what is already in 'pu'.  Especially since the topic with
Johannes Sixt's patch on top was marked as "Will merge to 'next'" at
one point if I remember correctly.  I didn't think that it being in
'pu' vs. it being in 'next' would make too much of a difference there,
and that it's just a by-product of where in the development cycle we
are rather than an indication of which way we are taking the branch.

> It however is a totally different matter if the topic was so
> unsalvageable that it needs a total rewrite---that would need
> another round of careful review, of course, and it would be
> irresponsive to merge a topic in such a messy state to 'next'.  But
> my impression was that the topic was not _that_ bad, so Dscho's
> message and the plan were something that was totally unexpected to
> me, too..

Indeed, the topic did not get any worse over the time it was in 'pu',
indeed it got a couple of fixes on top.  And my impression was that
Dscho still would have wanted to get the topic merged to next much
earlier, so I don't quite understand what changed since then, other
than getting a few fixes on top.

> > I was always under the impression that once the problem that was
> > discovered here was fixed we'd advance the series to 'next' with the
> > patch that comes out of this discussion on top.  Whether it's in next
> > shortly before 2.21 or not doesn't seem to make much of a difference
> > to me, as this wasn't going to make the 2.21 release anyway.  My hope
> > was that we could get it into 'next' shortly after 2.21 is released to
> > get the series some further exposure (which may well turn up some
> > other issues that we are not aware of yet, but such is the life of
> > software).
> 
> I was hoping similar, but also was hoping that people would use the
> time wisely while waiting for the next cycle to polish the topic with
> reordering and squashing, so that it can hit 'next' early once the
> tree opens.

I'd be happy to do this myself, but as mentioned above I thought we
were okay with just having the patches on top, and leave the work
Paul-Sebastian sent until now as it was.  If that impression was wrong
I'm happy to put in some work to help with the cleanup.

> Anyway.
> 
> I actually have a different issue with this topic, though.  It is
> wonderful to see a GSoC student's continued involvement in the
> project, but it is not healthy that we need so much work on top of
> what was marked "done" at the end of the GSoC period.  Especially
> the impression I am getting for the post GSoC work of this topic is
> not "we are already done converting to built-in during GSoC, and now
> we are extending the command", but "we ran out of time during GSoC;
> here is what we would have seen at the end of GSoC in an ideal
> world."
> 
> I wonder if this is an unfortunate indication that our expectation
> is unrealistically high when we accept students' applications.
> Being overly ambitious is *not* students' fault, but those of us on
> the list, especially those who mentor, have far deeper experience
> with how our code and project is structured than any students do.
> We should be able to, and should not hesitate to, say things like
> "that's overly ambitious---for such and such, you'd need to even
> invent an internal API---can we reduce the scope and still produce a
> useful end result?"
> 
> One suggestion I have is to have success criteria (e.g. "gets merged
> to 'master' before GSoC ends" [*1*]) clearly spelled out in the
> application.  Something like that would help managing the
> expectation and biting way too much for a summer, I'd hope.

[Adding Christian and Olga to cc here as this discussion should be
interesting to them as well as GSoC mentors]

I think one thing we underestimated at least here is how long it takes
from "everything that we intended to do is done" to "this is reviewed
and ready to merge into 'next' and 'master'".  This is partly my fault
as well because it took me quite a while to review the series on the
list, which certainly didn't help in moving things along.

While having set criteria is a good idea, I think we should still give
some leeway to the mentors in terms of the actual success/fail rating
in the program.  Not getting things merged is definitely not a good
experience, but it would be much worse to not get it merged, and also
fail GSoC and not getting paid for the efforts over the summer.  We
shouldn't punish students for the failure of the mentors to estimate
projects correctly.

One thing we should do I think is to say the project should be
"complete" at least a month before the end of GSoC, and that the last
month should only be dedicated to polishing the patches.  I don't know
how to make sure the students still have enough work to do during that
last period while they are just waiting for reviewers.

An other alternative (just thinking out loud here) is to make sure the
project has a few deliverables that can and will be merged
individually, even if they later build on top of eachother.  This
would mean getting students to send multiple series, when the patches
may normally go better in a single series, but I think it would help
moving the review cycle along faster.

Dunno, maybe there are other alternatives that I'm missing as well.

>     Side note *1*.  Of course, depending on the alignment of the
>     stars ^W our ~10-12 week development cycle and the end of GSoC,
>     getting merged to 'master' might become impossible if it
>     coincides with the pre-release freeze period.  But we on the
>     list and the mentors know how the project works, and can help
>     stating a more realistic success criterion if the development
>     cycle and other quirks specific to this project gets in the way.
> 
> Thanks.

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

* [PATCH v13 00/27] Convert "git stash" to C builtin
  2019-02-19 10:47               ` Johannes Schindelin
  2019-02-19 19:59                 ` Junio C Hamano
  2019-02-19 23:59                 ` Thomas Gummerer
@ 2019-02-25 23:16                 ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 01/27] sha1-name.c: add `get_oidf()` which acts like `get_oid()` Thomas Gummerer
                                     ` (30 more replies)
  2 siblings, 31 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Thomas Gummerer

As I was advocating for this series to go into 'next' without a large
refactor of this series, I'll put my money were my mouth is and try to
make the cleanups and fixes required, though without trying to avoid
further external process calls, or changing the series around too
much.

One thing to consider here is that we have a GSoC project planned
based on 'git stash'.  If we can't get this to 'next' soon, I'd vote
for taking that project out of this years GSoC, and maybe try again
next year, if nobody implemented the feature in the meantime.

One thing that came up in the latest reviews, was to keep the stash
script intact throughout the series, and to not re-introduce it after
deleting it.  I did however not do that, as that would make the
range-diff quite a bit harder to read.  In addition removing the
script bit by bit also allowed us to find the precise commit in which
the missing 'discard_cache()' bug was introduced, which made it a bit
easier to pinpoint where the bug comes from imo.

Additionally now that we established that we re-introduce the same
shell script in a previous review, it's now easy to see that we're
still doing the right thing in this patch series.

So what did change in this round?
- Squashed Johannes Sixt patch into the relevant patch
- Pulled out the test from Matthew Kraai into a separate patch, and
  squashed the rest of the patch into the relevant commit
(The two changes above were credited by adding Helped-by trailers)
- Fixed the missing discard_cache before reset --hard and apply
  --index -R.  t3903-stash.sh --stress now passed more than 250
  iterations without error.
- Addressed my and Junios review comments on the previous round
- Some small fixups

range-diff below:

 1:  0c1599c33c !  1:  baa5d369e4 strbuf.c: add `strbuf_join_argv()`
    @@ -6,7 +6,7 @@
         into a strbuf.
     
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/strbuf.c b/strbuf.c
      --- a/strbuf.c
 2:  bfc3fe33f6 !  2:  08bb77502c strbuf.c: add `strbuf_insertf()` and `strbuf_vinsertf()`
    @@ -7,7 +7,8 @@
     
         Original-idea-by: Johannes Schindelin <johannes.schindelin@gmx.de>
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Helped-by: Johannes Sixt <j6t@kdbg.org>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/strbuf.c b/strbuf.c
      --- a/strbuf.c
    @@ -37,7 +38,7 @@
     +	memmove(sb->buf + pos + len, sb->buf + pos, sb->len - pos);
     +	/* vsnprintf() will append a NUL, overwriting one of our characters */
     +	save = sb->buf[pos + len];
    -+	len2 = vsnprintf(sb->buf + pos, sb->alloc - sb->len, fmt, ap);
    ++	len2 = vsnprintf(sb->buf + pos, len + 1, fmt, ap);
     +	sb->buf[pos + len] = save;
     +	if (len2 != len)
     +		BUG("your vsnprintf is broken (returns inconsistent lengths)");
 3:  97f56073ce !  3:  2956b2bd27 ident: add the ability to provide a "fallback identity"
    @@ -12,7 +12,9 @@
         function.
     
         Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    [tg: add docs; make it a bug to call the function before other
    +    functions in the ident API]
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/cache.h b/cache.h
      --- a/cache.h
    @@ -21,6 +23,10 @@
      extern const char *git_pager(int stdout_is_tty);
      extern int is_terminal_dumb(void);
      extern int git_ident_config(const char *, const char *, void *);
    ++/*
    ++ * Prepare an ident to fall back on if the user didn't configure it.
    ++ * Must be called before any other function from the ident API.
    ++ */
     +void prepare_fallback_ident(const char *name, const char *email);
      extern void reset_ident_date(void);
      
    @@ -35,7 +41,9 @@
      
     +static void set_env_if(const char *key, const char *value, int *given, int bit)
     +{
    -+	if ((*given & bit) || getenv(key))
    ++	if (*given & bit)
    ++		BUG("%s was checked before prepare_fallback got called", key);
    ++	if (getenv(key))
     +		return; /* nothing to do */
     +	setenv(key, value, 0);
     +	*given |= bit;
 4:  0654fe70a8 !  4:  55db527e68 stash: improve option parsing test coverage
    @@ -8,7 +8,7 @@
     
         Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
      --- a/t/t3903-stash.sh
 5:  caf7bc3cc6 !  5:  7a5eb3a9e8 t3903: modernize style
    @@ -6,7 +6,7 @@
         long lines.
     
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
      --- a/t/t3903-stash.sh
26:  ed5d77f7d3 !  6:  453543916d stash: fix segmentation fault when files were added with intent
    @@ -1,45 +1,17 @@
     Author: Matthew Kraai <mkraai@its.jnj.com>
     
    -    stash: fix segmentation fault when files were added with intent
    +    t3903: add test for --intent-to-add file
     
    -    After `git add -N <file>`, the index is in a special state. A state for
    -    which the built-in stash was not prepared, as it failed to initialize
    -    the `rev` structure in that case before using `&rev.pending`.  If
    -    `reset_tree()` returns a non-zero value, `stash_working_tree()`
    -    calls `object_array_clear()` with `&rev.pending`.  If `rev` is not
    -    initialized, this causes a segmentation fault.
    +    Add a test showing the 'git stash' behaviour with a file that has been
    +    added with 'git add --intent-to-add'.  Stash fails to stash the file,
    +    so the purpose of this test is mainly to make sure git doesn't crash,
    +    but exits normally in this situation.
     
    -    Prevent this by initializing `rev` before calling `reset_tree()`.
    -
    -    This fixes https://github.com/git-for-windows/git/issues/2006.
    -
    -    [jes: modified the commit message in preparation for sending this patch
    -    to the Git mailing list.]
    +    [tg: pulled the test out into a separate commit]
     
         Signed-off-by: Matthew Kraai <mkraai@its.jnj.com>
         Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    -
    - diff --git a/builtin/stash.c b/builtin/stash.c
    - --- a/builtin/stash.c
    - +++ b/builtin/stash.c
    -@@
    - 	struct strbuf diff_output = STRBUF_INIT;
    - 	struct index_state istate = { NULL };
    - 
    -+	init_revisions(&rev, NULL);
    -+
    - 	set_alternate_index_output(stash_index_path.buf);
    - 	if (reset_tree(&info->i_tree, 0, 0)) {
    - 		ret = -1;
    -@@
    - 	}
    - 	set_alternate_index_output(NULL);
    - 
    --	init_revisions(&rev, NULL);
    - 	rev.prune_data = ps;
    - 	rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
    - 	rev.diffopt.format_callback = add_diff_to_buf;
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
      --- a/t/t3903-stash.sh
 6:  ee77c6a603 !  7:  587d3e8e49 stash: rename test cases to be more descriptive
    @@ -6,7 +6,7 @@
         characters per line.
     
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
      --- a/t/t3903-stash.sh
 7:  cb2152ebce !  8:  b03c17e744 stash: add tests for `git stash show` config
    @@ -7,7 +7,7 @@
         and `stash.showPatch` are unset or set to true / false.
     
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/t/t3907-stash-show-config.sh b/t/t3907-stash-show-config.sh
      new file mode 100755
 8:  983084d9ec !  9:  010c5f4ce2 stash: mention options in `show` synopsis
    @@ -6,7 +6,7 @@
         option known to `git diff`.
     
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
      --- a/Documentation/git-stash.txt
 9:  f6bbd78127 ! 10:  45670448e8 stash: convert apply to builtin
    @@ -17,7 +17,7 @@
     
         Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/.gitignore b/.gitignore
      --- a/.gitignore
    @@ -96,7 +96,6 @@
     + * i_tree is set to the index tree
     + * u_tree is set to the untracked files tree
     + */
    -+
     +struct stash_info {
     +	struct object_id w_commit;
     +	struct object_id b_commit;
    @@ -357,7 +356,7 @@
     +	if (refresh_cache(REFRESH_QUIET))
     +		return -1;
     +
    -+	if (write_cache_as_tree(&c_tree, 0, NULL) || reset_tree(&c_tree, 0, 0))
    ++	if (write_cache_as_tree(&c_tree, 0, NULL))
     +		return error(_("cannot apply a stash in the middle of a merge"));
     +
     +	if (index) {
10:  cdca49bc4c ! 11:  cea038dd3c stash: convert drop and clear to builtin
    @@ -13,7 +13,7 @@
     
         Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
      --- a/builtin/stash--helper.c
    @@ -128,9 +128,9 @@
     +static void assert_stash_ref(struct stash_info *info)
     +{
     +	if (!info->is_stash_ref) {
    -+		free_stash_info(info);
     +		error(_("'%s' is not a stash reference"), info->revision.buf);
    -+		exit(128);
    ++		free_stash_info(info);
    ++		exit(1);
     +	}
     +}
     +
11:  f596f3366c ! 12:  deb4f6cee9 stash: convert branch to builtin
    @@ -11,7 +11,7 @@
     
         Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
      --- a/builtin/stash--helper.c
12:  e1d01876a4 ! 13:  75f9431abf stash: convert pop to builtin
    @@ -8,7 +8,7 @@
     
         Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
      --- a/builtin/stash--helper.c
13:  9b77b07ba4 ! 14:  f6814704ee stash: convert list to builtin
    @@ -6,7 +6,7 @@
         from the shell script.
     
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
      --- a/builtin/stash--helper.c
14:  b4493f269e ! 15:  d1098f2c8e stash: convert show to builtin
    @@ -11,7 +11,7 @@
         further to `git diff`.
     
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
      --- a/builtin/stash--helper.c
15:  847eb0b0a8 ! 16:  9706cab487 stash: convert store to builtin
    @@ -6,7 +6,7 @@
         from the shell script.
     
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
      --- a/builtin/stash--helper.c
16:  1f5a011d90 ! 17:  a5d6a3bd14 stash: convert create to builtin
    @@ -5,7 +5,8 @@
         Add stash create to the helper.
     
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Helped-by: Matthew Kraai <mkraai@its.jnj.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
      --- a/builtin/stash--helper.c
    @@ -45,11 +46,7 @@
     +	for (i = 0; i < q->nr; i++) {
     +		strbuf_addstr(data, q->queue[i]->one->path);
     +
    -+		/*
    -+		 * The reason we add "0" at the end of this strbuf
    -+		 * is because we will pass the output further to
    -+		 * "git update-index -z ...".
    -+		 */
    ++		/* NUL-terminate: will be fed to update-index -z */
     +		strbuf_addch(data, '\0');
     +	}
     +}
    @@ -98,7 +95,7 @@
     +			found++;
     +			strbuf_addstr(untracked_files, ent->name);
     +			/* NUL-terminate: will be fed to update-index -z */
    -+			strbuf_addch(untracked_files, 0);
    ++			strbuf_addch(untracked_files, '\0');
     +		}
     +		free(ent);
     +	}
    @@ -278,6 +275,8 @@
     +	struct strbuf out = STRBUF_INIT;
     +	struct strbuf diff_output = STRBUF_INIT;
     +
    ++	init_revisions(&rev, NULL);
    ++
     +	set_alternate_index_output(stash_index_path.buf);
     +	if (reset_tree(&info->i_tree, 0, 0)) {
     +		ret = -1;
    @@ -285,7 +284,6 @@
     +	}
     +	set_alternate_index_output(NULL);
     +
    -+	init_revisions(&rev, NULL);
     +	rev.prune_data = ps;
     +	rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
     +	rev.diffopt.format_callback = add_diff_to_buf;
    @@ -471,7 +469,6 @@
     +	memset(&ps, 0, sizeof(ps));
     +	strbuf_addstr(&stash_msg_buf, stash_msg);
     +	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info);
    -+
     +	if (!ret)
     +		printf_ln("%s", oid_to_hex(&info.w_commit));
     +
17:  fa38428f76 ! 18:  3065f08c65 stash: convert push to builtin
    @@ -5,7 +5,7 @@
         Add stash push to the helper.
     
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
      --- a/builtin/stash--helper.c
    @@ -67,9 +67,9 @@
     -	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info);
     +	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
     +			      NULL);
    - 
      	if (!ret)
      		printf_ln("%s", oid_to_hex(&info.w_commit));
    + 
     @@
      	return ret < 0;
      }
    @@ -158,6 +158,7 @@
     +				goto done;
     +			}
     +		}
    ++		discard_cache();
     +		if (ps.nr) {
     +			struct child_process cp_add = CHILD_PROCESS_INIT;
     +			struct child_process cp_diff = CHILD_PROCESS_INIT;
18:  9a95010a11 ! 19:  31dd0edc0c stash: make push -q quiet
    @@ -8,7 +8,7 @@
         `--quiet` or `-q` is specified. Add tests for `--quiet`.
     
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
      --- a/builtin/stash--helper.c
    @@ -118,9 +118,9 @@
      	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
     -			      NULL);
     +			      NULL, 0);
    - 
      	if (!ret)
      		printf_ln("%s", oid_to_hex(&info.w_commit));
    + 
     @@
      
      	if (!reflog_exists(ref_stash) && do_clear_stash()) {
19:  cf5b27d699 ! 20:  f5644a4fdc stash: convert save to builtin
    @@ -8,7 +8,7 @@
         `no_changes()`).
     
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
      --- a/builtin/stash--helper.c
20:  168e6cff5e ! 21:  367511ab8f stash: optimize `get_untracked_files()` and `check_changes()`
    @@ -29,10 +29,8 @@
         This way `check_changes()` and `get_untracked files()` are called
         only one time.
     
    -    https://public-inbox.org/git/20180818223329.GJ11326@hank.intra.tgummerer.com/
    -
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
      --- a/builtin/stash--helper.c
    @@ -49,7 +47,6 @@
       * > 0 if there are changes.
       */
     -static int check_changes(struct pathspec ps, int include_untracked)
    -+
     +static int check_changes_tracked_files(struct pathspec ps)
      {
      	int result;
    @@ -70,7 +67,6 @@
     + * The function will fill `untracked_files` with the names of untracked files
     + * It will return 1 if there were any changes and 0 if there were not.
     + */
    -+
     +static int check_changes(struct pathspec ps, int include_untracked,
     +			 struct strbuf *untracked_files)
     +{
    @@ -115,15 +111,13 @@
      			     0);
      
      	memset(&ps, 0, sizeof(ps));
    --	strbuf_addstr(&stash_msg_buf, stash_msg);
    --	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
    --			      NULL, 0);
     +	if (!check_changes_tracked_files(ps))
     +		return 0;
    - 
    --	if (!ret)
    -+	strbuf_addstr(&stash_msg_buf, stash_msg);
    -+	if (!(ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info, NULL, 0)))
    ++
    + 	strbuf_addstr(&stash_msg_buf, stash_msg);
    + 	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
    + 			      NULL, 0);
    +@@
      		printf_ln("%s", oid_to_hex(&info.w_commit));
      
      	strbuf_release(&stash_msg_buf);
21:  559edead8f ! 22:  376bb4adc9 stash: replace all `write-tree` child processes with API calls
    @@ -2,10 +2,11 @@
     
         stash: replace all `write-tree` child processes with API calls
     
    -    This commit replaces spawning `git write-tree` with API calls.
    +    Avoid spawning write-tree child processes by replacing the calls with
    +    in-core API calls.
     
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
      --- a/builtin/stash--helper.c
    @@ -100,8 +101,8 @@
      	struct strbuf diff_output = STRBUF_INIT;
     +	struct index_state istate = { NULL };
      
    - 	set_alternate_index_output(stash_index_path.buf);
    - 	if (reset_tree(&info->i_tree, 0, 0)) {
    + 	init_revisions(&rev, NULL);
    + 
     @@
      		goto done;
      	}
22:  51809c70ca ! 23:  56a5ce2aeb stash: convert `stash--helper.c` into `stash.c`
    @@ -13,7 +13,7 @@
         called directly and not by a shell script.
     
         Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/.gitignore b/.gitignore
      --- a/.gitignore
    @@ -273,9 +273,11 @@
      		return 0;
      
     -	strbuf_addstr(&stash_msg_buf, stash_msg);
    - 	if (!(ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info, NULL, 0)))
    +-	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
    ++	ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info,
    + 			      NULL, 0);
    + 	if (!ret)
      		printf_ln("%s", oid_to_hex(&info.w_commit));
    - 
     @@
      		OPT_END()
      	};
23:  c907fe1cd6 ! 24:  830c1d6dbe stash: add back the original, scripted `git stash`
    @@ -9,7 +9,7 @@
         scripted `git stash` when `stash.useBuiltin=false`.
     
         Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/git-stash.sh b/git-stash.sh
      new file mode 100755
24:  26799a208f ! 25:  00fb753d5e stash: optionally use the scripted version again
    @@ -17,7 +17,7 @@
         code 129 for `git stash -h`.
     
         Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/.gitignore b/.gitignore
      --- a/.gitignore
25:  bec65d5b78 ! 26:  49b7f82db9 tests: add a special setup where stash.useBuiltin is off
    @@ -12,7 +12,7 @@
         2018-11-14).
     
         Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
     
      diff --git a/builtin/stash.c b/builtin/stash.c
      --- a/builtin/stash.c
27:  8187099d9c <  -:  ---------- strbuf_vinsertf: provide the correct buffer size to vsnprintf


Joel Teichroeb (5):
  stash: improve option parsing test coverage
  stash: convert apply to builtin
  stash: convert drop and clear to builtin
  stash: convert branch to builtin
  stash: convert pop to builtin

Johannes Schindelin (4):
  ident: add the ability to provide a "fallback identity"
  stash: add back the original, scripted `git stash`
  stash: optionally use the scripted version again
  tests: add a special setup where stash.useBuiltin is off

Matthew Kraai (1):
  t3903: add test for --intent-to-add file

Paul-Sebastian Ungureanu (17):
  sha1-name.c: add `get_oidf()` which acts like `get_oid()`
  strbuf.c: add `strbuf_join_argv()`
  strbuf.c: add `strbuf_insertf()` and `strbuf_vinsertf()`
  t3903: modernize style
  stash: rename test cases to be more descriptive
  stash: add tests for `git stash show` config
  stash: mention options in `show` synopsis
  stash: convert list to builtin
  stash: convert show to builtin
  stash: convert store to builtin
  stash: convert create to builtin
  stash: convert push to builtin
  stash: make push -q quiet
  stash: convert save to builtin
  stash: optimize `get_untracked_files()` and `check_changes()`
  stash: replace all `write-tree` child processes with API calls
  stash: convert `stash--helper.c` into `stash.c`

 .gitignore                          |    1 +
 Documentation/git-stash.txt         |    4 +-
 Makefile                            |    3 +-
 builtin.h                           |    1 +
 builtin/stash.c                     | 1633 +++++++++++++++++++++++++++
 cache.h                             |    6 +
 git-stash.sh => git-legacy-stash.sh |   34 +-
 git-sh-setup.sh                     |    1 +
 git.c                               |    6 +
 ident.c                             |   22 +
 sha1-name.c                         |   19 +
 strbuf.c                            |   51 +
 strbuf.h                            |   16 +
 t/README                            |    4 +
 t/t3903-stash.sh                    |  200 ++--
 t/t3907-stash-show-config.sh        |   83 ++
 16 files changed, 2012 insertions(+), 72 deletions(-)
 create mode 100644 builtin/stash.c
 rename git-stash.sh => git-legacy-stash.sh (97%)
 create mode 100755 t/t3907-stash-show-config.sh

-- 
2.21.0.rc2.291.g17236886c5

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

* [PATCH v13 01/27] sha1-name.c: add `get_oidf()` which acts like `get_oid()`
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 02/27] strbuf.c: add `strbuf_join_argv()` Thomas Gummerer
                                     ` (29 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Johannes Schindelin

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

Compared to `get_oid()`, `get_oidf()` has as parameters
a pointer to `object_id`, a printf format string and
additional arguments. This will help simplify the code
in subsequent commits.

Original-idea-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 cache.h     |  1 +
 sha1-name.c | 19 +++++++++++++++++++
 2 files changed, 20 insertions(+)

diff --git a/cache.h b/cache.h
index ca36b44ee0..7b6b89fc4c 100644
--- a/cache.h
+++ b/cache.h
@@ -1333,6 +1333,7 @@ struct object_context {
 	GET_OID_BLOB)
 
 extern int get_oid(const char *str, struct object_id *oid);
+extern int get_oidf(struct object_id *oid, const char *fmt, ...);
 extern int get_oid_commit(const char *str, struct object_id *oid);
 extern int get_oid_committish(const char *str, struct object_id *oid);
 extern int get_oid_tree(const char *str, struct object_id *oid);
diff --git a/sha1-name.c b/sha1-name.c
index faa60f69e3..cf0e8a3f85 100644
--- a/sha1-name.c
+++ b/sha1-name.c
@@ -1542,6 +1542,25 @@ int get_oid(const char *name, struct object_id *oid)
 	return get_oid_with_context(name, 0, oid, &unused);
 }
 
+/*
+ * This returns a non-zero value if the string (built using printf
+ * format and the given arguments) is not a valid object.
+ */
+int get_oidf(struct object_id *oid, const char *fmt, ...)
+{
+	va_list ap;
+	int ret;
+	struct strbuf sb = STRBUF_INIT;
+
+	va_start(ap, fmt);
+	strbuf_vaddf(&sb, fmt, ap);
+	va_end(ap);
+
+	ret = get_oid(sb.buf, oid);
+	strbuf_release(&sb);
+
+	return ret;
+}
 
 /*
  * Many callers know that the user meant to name a commit-ish by
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 02/27] strbuf.c: add `strbuf_join_argv()`
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 01/27] sha1-name.c: add `get_oidf()` which acts like `get_oid()` Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 03/27] strbuf.c: add `strbuf_insertf()` and `strbuf_vinsertf()` Thomas Gummerer
                                     ` (28 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

Implement `strbuf_join_argv()` to join arguments
into a strbuf.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 strbuf.c | 15 +++++++++++++++
 strbuf.h |  7 +++++++
 2 files changed, 22 insertions(+)

diff --git a/strbuf.c b/strbuf.c
index f6a6cf78b9..82e90f1dfe 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -268,6 +268,21 @@ void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2)
 	strbuf_setlen(sb, sb->len + sb2->len);
 }
 
+const char *strbuf_join_argv(struct strbuf *buf,
+			     int argc, const char **argv, char delim)
+{
+	if (!argc)
+		return buf->buf;
+
+	strbuf_addstr(buf, *argv);
+	while (--argc) {
+		strbuf_addch(buf, delim);
+		strbuf_addstr(buf, *(++argv));
+	}
+
+	return buf->buf;
+}
+
 void strbuf_addchars(struct strbuf *sb, int c, size_t n)
 {
 	strbuf_grow(sb, n);
diff --git a/strbuf.h b/strbuf.h
index fc40873b65..be02150df3 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -288,6 +288,13 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s)
  */
 void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2);
 
+/**
+ * Join the arguments into a buffer. `delim` is put between every
+ * two arguments.
+ */
+const char *strbuf_join_argv(struct strbuf *buf, int argc,
+			     const char **argv, char delim);
+
 /**
  * This function can be used to expand a format string containing
  * placeholders. To that end, it parses the string and calls the specified
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 03/27] strbuf.c: add `strbuf_insertf()` and `strbuf_vinsertf()`
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 01/27] sha1-name.c: add `get_oidf()` which acts like `get_oid()` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 02/27] strbuf.c: add `strbuf_join_argv()` Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 04/27] ident: add the ability to provide a "fallback identity" Thomas Gummerer
                                     ` (27 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Johannes Schindelin, Johannes Sixt,
	Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

Implement `strbuf_insertf()` and `strbuf_vinsertf()` to
insert data using a printf format string.

Original-idea-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Helped-by: Johannes Sixt <j6t@kdbg.org>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 strbuf.c | 36 ++++++++++++++++++++++++++++++++++++
 strbuf.h |  9 +++++++++
 2 files changed, 45 insertions(+)

diff --git a/strbuf.c b/strbuf.c
index 82e90f1dfe..87ecf7f975 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -249,6 +249,42 @@ void strbuf_insert(struct strbuf *sb, size_t pos, const void *data, size_t len)
 	strbuf_splice(sb, pos, 0, data, len);
 }
 
+void strbuf_vinsertf(struct strbuf *sb, size_t pos, const char *fmt, va_list ap)
+{
+	int len, len2;
+	char save;
+	va_list cp;
+
+	if (pos > sb->len)
+		die("`pos' is too far after the end of the buffer");
+	va_copy(cp, ap);
+	len = vsnprintf(sb->buf + sb->len, 0, fmt, cp);
+	va_end(cp);
+	if (len < 0)
+		BUG("your vsnprintf is broken (returned %d)", len);
+	if (!len)
+		return; /* nothing to do */
+	if (unsigned_add_overflows(sb->len, len))
+		die("you want to use way too much memory");
+	strbuf_grow(sb, len);
+	memmove(sb->buf + pos + len, sb->buf + pos, sb->len - pos);
+	/* vsnprintf() will append a NUL, overwriting one of our characters */
+	save = sb->buf[pos + len];
+	len2 = vsnprintf(sb->buf + pos, len + 1, fmt, ap);
+	sb->buf[pos + len] = save;
+	if (len2 != len)
+		BUG("your vsnprintf is broken (returns inconsistent lengths)");
+	strbuf_setlen(sb, sb->len + len);
+}
+
+void strbuf_insertf(struct strbuf *sb, size_t pos, const char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+	strbuf_vinsertf(sb, pos, fmt, ap);
+	va_end(ap);
+}
+
 void strbuf_remove(struct strbuf *sb, size_t pos, size_t len)
 {
 	strbuf_splice(sb, pos, len, "", 0);
diff --git a/strbuf.h b/strbuf.h
index be02150df3..8f8fe01e68 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -244,6 +244,15 @@ void strbuf_addchars(struct strbuf *sb, int c, size_t n);
  */
 void strbuf_insert(struct strbuf *sb, size_t pos, const void *, size_t);
 
+/**
+ * Insert data to the given position of the buffer giving a printf format
+ * string. The contents will be shifted, not overwritten.
+ */
+void strbuf_vinsertf(struct strbuf *sb, size_t pos, const char *fmt,
+		     va_list ap);
+
+void strbuf_insertf(struct strbuf *sb, size_t pos, const char *fmt, ...);
+
 /**
  * Remove given amount of data from a given position of the buffer.
  */
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 04/27] ident: add the ability to provide a "fallback identity"
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (2 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 03/27] strbuf.c: add `strbuf_insertf()` and `strbuf_vinsertf()` Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 05/27] stash: improve option parsing test coverage Thomas Gummerer
                                     ` (26 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Johannes Schindelin, Thomas Gummerer

From: Johannes Schindelin <johannes.schindelin@gmx.de>

In 3bc2111fc2e9 (stash: tolerate missing user identity, 2018-11-18),
`git stash` learned to provide a fallback identity for the case that no
proper name/email was given (and `git stash` does not really care about
a correct identity anyway, but it does want to create a commit object).

In preparation for the same functionality in the upcoming built-in
version of `git stash`, let's offer the same functionality as an API
function.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
[tg: add docs; make it a bug to call the function before other
functions in the ident API]
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 cache.h |  5 +++++
 ident.c | 22 ++++++++++++++++++++++
 2 files changed, 27 insertions(+)

diff --git a/cache.h b/cache.h
index 7b6b89fc4c..611e554dea 100644
--- a/cache.h
+++ b/cache.h
@@ -1491,6 +1491,11 @@ extern const char *git_sequence_editor(void);
 extern const char *git_pager(int stdout_is_tty);
 extern int is_terminal_dumb(void);
 extern int git_ident_config(const char *, const char *, void *);
+/*
+ * Prepare an ident to fall back on if the user didn't configure it.
+ * Must be called before any other function from the ident API.
+ */
+void prepare_fallback_ident(const char *name, const char *email);
 extern void reset_ident_date(void);
 
 struct ident_split {
diff --git a/ident.c b/ident.c
index 33bcf40644..f30bd623f0 100644
--- a/ident.c
+++ b/ident.c
@@ -505,6 +505,28 @@ int git_ident_config(const char *var, const char *value, void *data)
 	return 0;
 }
 
+static void set_env_if(const char *key, const char *value, int *given, int bit)
+{
+	if (*given & bit)
+		BUG("%s was checked before prepare_fallback got called", key);
+	if (getenv(key))
+		return; /* nothing to do */
+	setenv(key, value, 0);
+	*given |= bit;
+}
+
+void prepare_fallback_ident(const char *name, const char *email)
+{
+	set_env_if("GIT_AUTHOR_NAME", name,
+		   &author_ident_explicitly_given, IDENT_NAME_GIVEN);
+	set_env_if("GIT_AUTHOR_EMAIL", email,
+		   &author_ident_explicitly_given, IDENT_MAIL_GIVEN);
+	set_env_if("GIT_COMMITTER_NAME", name,
+		   &committer_ident_explicitly_given, IDENT_NAME_GIVEN);
+	set_env_if("GIT_COMMITTER_EMAIL", email,
+		   &committer_ident_explicitly_given, IDENT_MAIL_GIVEN);
+}
+
 static int buf_cmp(const char *a_begin, const char *a_end,
 		   const char *b_begin, const char *b_end)
 {
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 05/27] stash: improve option parsing test coverage
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (3 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 04/27] ident: add the ability to provide a "fallback identity" Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 06/27] t3903: modernize style Thomas Gummerer
                                     ` (25 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Joel Teichroeb, Thomas Gummerer

From: Joel Teichroeb <joel@teichroeb.net>

In preparation for converting the stash command incrementally to
a builtin command, this patch improves test coverage of the option
parsing. Both for having too many parameters, or too few.

Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 t/t3903-stash.sh | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 5f8272b6f9..ac55629737 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -444,6 +444,36 @@ test_expect_failure 'stash file to directory' '
 	test foo = "$(cat file/file)"
 '
 
+test_expect_success 'giving too many ref arguments does not modify files' '
+	git stash clear &&
+	test_when_finished "git reset --hard HEAD" &&
+	echo foo >file2 &&
+	git stash &&
+	echo bar >file2 &&
+	git stash &&
+	test-tool chmtime =123456789 file2 &&
+	for type in apply pop "branch stash-branch"
+	do
+		test_must_fail git stash $type stash@{0} stash@{1} 2>err &&
+		test_i18ngrep "Too many revisions" err &&
+		test 123456789 = $(test-tool chmtime -g file2) || return 1
+	done
+'
+
+test_expect_success 'drop: too many arguments errors out (does nothing)' '
+	git stash list >expect &&
+	test_must_fail git stash drop stash@{0} stash@{1} 2>err &&
+	test_i18ngrep "Too many revisions" err &&
+	git stash list >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'show: too many arguments errors out (does nothing)' '
+	test_must_fail git stash show stash@{0} stash@{1} 2>err 1>out &&
+	test_i18ngrep "Too many revisions" err &&
+	test_must_be_empty out
+'
+
 test_expect_success 'stash create - no changes' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
@@ -479,6 +509,11 @@ test_expect_success 'stash branch - stashes on stack, stash-like argument' '
 	test $(git ls-files --modified | wc -l) -eq 1
 '
 
+test_expect_success 'stash branch complains with no arguments' '
+	test_must_fail git stash branch 2>err &&
+	test_i18ngrep "No branch name specified" err
+'
+
 test_expect_success 'stash show format defaults to --stat' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 06/27] t3903: modernize style
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (4 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 05/27] stash: improve option parsing test coverage Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 07/27] t3903: add test for --intent-to-add file Thomas Gummerer
                                     ` (24 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

Remove whitespaces after redirection operators and wrap
long lines.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 t/t3903-stash.sh | 120 ++++++++++++++++++++++++-----------------------
 1 file changed, 61 insertions(+), 59 deletions(-)

diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index ac55629737..4e83facf23 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -8,22 +8,22 @@ test_description='Test git stash'
 . ./test-lib.sh
 
 test_expect_success 'stash some dirty working directory' '
-	echo 1 > file &&
+	echo 1 >file &&
 	git add file &&
 	echo unrelated >other-file &&
 	git add other-file &&
 	test_tick &&
 	git commit -m initial &&
-	echo 2 > file &&
+	echo 2 >file &&
 	git add file &&
-	echo 3 > file &&
+	echo 3 >file &&
 	test_tick &&
 	git stash &&
 	git diff-files --quiet &&
 	git diff-index --cached --quiet HEAD
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 diff --git a/file b/file
 index 0cfbf08..00750ed 100644
 --- a/file
@@ -35,7 +35,7 @@ EOF
 
 test_expect_success 'parents of stash' '
 	test $(git rev-parse stash^) = $(git rev-parse HEAD) &&
-	git diff stash^2..stash > output &&
+	git diff stash^2..stash >output &&
 	test_cmp expect output
 '
 
@@ -74,7 +74,7 @@ test_expect_success 'apply stashed changes' '
 
 test_expect_success 'apply stashed changes (including index)' '
 	git reset --hard HEAD^ &&
-	echo 6 > other-file &&
+	echo 6 >other-file &&
 	git add other-file &&
 	test_tick &&
 	git commit -m other-file &&
@@ -99,12 +99,12 @@ test_expect_success 'stash drop complains of extra options' '
 
 test_expect_success 'drop top stash' '
 	git reset --hard &&
-	git stash list > stashlist1 &&
-	echo 7 > file &&
+	git stash list >expected &&
+	echo 7 >file &&
 	git stash &&
 	git stash drop &&
-	git stash list > stashlist2 &&
-	test_cmp stashlist1 stashlist2 &&
+	git stash list >actual &&
+	test_cmp expected actual &&
 	git stash apply &&
 	test 3 = $(cat file) &&
 	test 1 = $(git show :file) &&
@@ -113,9 +113,9 @@ test_expect_success 'drop top stash' '
 
 test_expect_success 'drop middle stash' '
 	git reset --hard &&
-	echo 8 > file &&
+	echo 8 >file &&
 	git stash &&
-	echo 9 > file &&
+	echo 9 >file &&
 	git stash &&
 	git stash drop stash@{1} &&
 	test 2 = $(git stash list | wc -l) &&
@@ -160,7 +160,7 @@ test_expect_success 'stash pop' '
 	test 0 = $(git stash list | wc -l)
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 diff --git a/file2 b/file2
 new file mode 100644
 index 0000000..1fe912c
@@ -170,7 +170,7 @@ index 0000000..1fe912c
 +bar2
 EOF
 
-cat > expect1 << EOF
+cat >expect1 <<EOF
 diff --git a/file b/file
 index 257cc56..5716ca5 100644
 --- a/file
@@ -180,7 +180,7 @@ index 257cc56..5716ca5 100644
 +bar
 EOF
 
-cat > expect2 << EOF
+cat >expect2 <<EOF
 diff --git a/file b/file
 index 7601807..5716ca5 100644
 --- a/file
@@ -198,79 +198,79 @@ index 0000000..1fe912c
 EOF
 
 test_expect_success 'stash branch' '
-	echo foo > file &&
+	echo foo >file &&
 	git commit file -m first &&
-	echo bar > file &&
-	echo bar2 > file2 &&
+	echo bar >file &&
+	echo bar2 >file2 &&
 	git add file2 &&
 	git stash &&
-	echo baz > file &&
+	echo baz >file &&
 	git commit file -m second &&
 	git stash branch stashbranch &&
 	test refs/heads/stashbranch = $(git symbolic-ref HEAD) &&
 	test $(git rev-parse HEAD) = $(git rev-parse master^) &&
-	git diff --cached > output &&
+	git diff --cached >output &&
 	test_cmp expect output &&
-	git diff > output &&
+	git diff >output &&
 	test_cmp expect1 output &&
 	git add file &&
 	git commit -m alternate\ second &&
-	git diff master..stashbranch > output &&
+	git diff master..stashbranch >output &&
 	test_cmp output expect2 &&
 	test 0 = $(git stash list | wc -l)
 '
 
 test_expect_success 'apply -q is quiet' '
-	echo foo > file &&
+	echo foo >file &&
 	git stash &&
-	git stash apply -q > output.out 2>&1 &&
+	git stash apply -q >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
 test_expect_success 'save -q is quiet' '
-	git stash save --quiet > output.out 2>&1 &&
+	git stash save --quiet >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
 test_expect_success 'pop -q is quiet' '
-	git stash pop -q > output.out 2>&1 &&
+	git stash pop -q >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
 test_expect_success 'pop -q --index works and is quiet' '
-	echo foo > file &&
+	echo foo >file &&
 	git add file &&
 	git stash save --quiet &&
-	git stash pop -q --index > output.out 2>&1 &&
+	git stash pop -q --index >output.out 2>&1 &&
 	test foo = "$(git show :file)" &&
 	test_must_be_empty output.out
 '
 
 test_expect_success 'drop -q is quiet' '
 	git stash &&
-	git stash drop -q > output.out 2>&1 &&
+	git stash drop -q >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
 test_expect_success 'stash -k' '
-	echo bar3 > file &&
-	echo bar4 > file2 &&
+	echo bar3 >file &&
+	echo bar4 >file2 &&
 	git add file2 &&
 	git stash -k &&
 	test bar,bar4 = $(cat file),$(cat file2)
 '
 
 test_expect_success 'stash --no-keep-index' '
-	echo bar33 > file &&
-	echo bar44 > file2 &&
+	echo bar33 >file &&
+	echo bar44 >file2 &&
 	git add file2 &&
 	git stash --no-keep-index &&
 	test bar,bar2 = $(cat file),$(cat file2)
 '
 
 test_expect_success 'stash --invalid-option' '
-	echo bar5 > file &&
-	echo bar6 > file2 &&
+	echo bar5 >file &&
+	echo bar6 >file2 &&
 	git add file2 &&
 	test_must_fail git stash --invalid-option &&
 	test_must_fail git stash save --invalid-option &&
@@ -486,11 +486,12 @@ test_expect_success 'stash branch - no stashes on stack, stash-like argument' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
 	git reset --hard &&
-	echo foo >> file &&
+	echo foo >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
 	git stash branch stash-branch ${STASH_ID} &&
-	test_when_finished "git reset --hard HEAD && git checkout master && git branch -D stash-branch" &&
+	test_when_finished "git reset --hard HEAD && git checkout master &&
+	git branch -D stash-branch" &&
 	test $(git ls-files --modified | wc -l) -eq 1
 '
 
@@ -498,14 +499,15 @@ test_expect_success 'stash branch - stashes on stack, stash-like argument' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
 	git reset --hard &&
-	echo foo >> file &&
+	echo foo >>file &&
 	git stash &&
 	test_when_finished "git stash drop" &&
-	echo bar >> file &&
+	echo bar >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
 	git stash branch stash-branch ${STASH_ID} &&
-	test_when_finished "git reset --hard HEAD && git checkout master && git branch -D stash-branch" &&
+	test_when_finished "git reset --hard HEAD && git checkout master &&
+	git branch -D stash-branch" &&
 	test $(git ls-files --modified | wc -l) -eq 1
 '
 
@@ -518,10 +520,10 @@ test_expect_success 'stash show format defaults to --stat' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
 	git reset --hard &&
-	echo foo >> file &&
+	echo foo >>file &&
 	git stash &&
 	test_when_finished "git stash drop" &&
-	echo bar >> file &&
+	echo bar >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
 	cat >expected <<-EOF &&
@@ -536,10 +538,10 @@ test_expect_success 'stash show - stashes on stack, stash-like argument' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
 	git reset --hard &&
-	echo foo >> file &&
+	echo foo >>file &&
 	git stash &&
 	test_when_finished "git stash drop" &&
-	echo bar >> file &&
+	echo bar >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
 	echo "1	0	file" >expected &&
@@ -551,10 +553,10 @@ test_expect_success 'stash show -p - stashes on stack, stash-like argument' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
 	git reset --hard &&
-	echo foo >> file &&
+	echo foo >>file &&
 	git stash &&
 	test_when_finished "git stash drop" &&
-	echo bar >> file &&
+	echo bar >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
 	cat >expected <<-EOF &&
@@ -574,7 +576,7 @@ test_expect_success 'stash show - no stashes on stack, stash-like argument' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
 	git reset --hard &&
-	echo foo >> file &&
+	echo foo >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
 	echo "1	0	file" >expected &&
@@ -586,7 +588,7 @@ test_expect_success 'stash show -p - no stashes on stack, stash-like argument' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
 	git reset --hard &&
-	echo foo >> file &&
+	echo foo >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
 	cat >expected <<-EOF &&
@@ -606,9 +608,9 @@ test_expect_success 'stash drop - fail early if specified stash is not a stash r
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD && git stash clear" &&
 	git reset --hard &&
-	echo foo > file &&
+	echo foo >file &&
 	git stash &&
-	echo bar > file &&
+	echo bar >file &&
 	git stash &&
 	test_must_fail git stash drop $(git rev-parse stash@{0}) &&
 	git stash pop &&
@@ -620,9 +622,9 @@ test_expect_success 'stash pop - fail early if specified stash is not a stash re
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD && git stash clear" &&
 	git reset --hard &&
-	echo foo > file &&
+	echo foo >file &&
 	git stash &&
-	echo bar > file &&
+	echo bar >file &&
 	git stash &&
 	test_must_fail git stash pop $(git rev-parse stash@{0}) &&
 	git stash pop &&
@@ -632,8 +634,8 @@ test_expect_success 'stash pop - fail early if specified stash is not a stash re
 
 test_expect_success 'ref with non-existent reflog' '
 	git stash clear &&
-	echo bar5 > file &&
-	echo bar6 > file2 &&
+	echo bar5 >file &&
+	echo bar6 >file2 &&
 	git add file2 &&
 	git stash &&
 	test_must_fail git rev-parse --quiet --verify does-not-exist &&
@@ -653,8 +655,8 @@ test_expect_success 'ref with non-existent reflog' '
 test_expect_success 'invalid ref of the form stash@{n}, n >= N' '
 	git stash clear &&
 	test_must_fail git stash drop stash@{0} &&
-	echo bar5 > file &&
-	echo bar6 > file2 &&
+	echo bar5 >file &&
+	echo bar6 >file2 &&
 	git add file2 &&
 	git stash &&
 	test_must_fail git stash drop stash@{1} &&
@@ -724,7 +726,7 @@ test_expect_success 'stash apply shows status same as git status (relative to cu
 	test_i18ncmp expect actual
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 diff --git a/HEAD b/HEAD
 new file mode 100644
 index 0000000..fe0cbee
@@ -737,14 +739,14 @@ EOF
 test_expect_success 'stash where working directory contains "HEAD" file' '
 	git stash clear &&
 	git reset --hard &&
-	echo file-not-a-ref > HEAD &&
+	echo file-not-a-ref >HEAD &&
 	git add HEAD &&
 	test_tick &&
 	git stash &&
 	git diff-files --quiet &&
 	git diff-index --cached --quiet HEAD &&
 	test "$(git rev-parse stash^)" = "$(git rev-parse HEAD)" &&
-	git diff stash^..stash > output &&
+	git diff stash^..stash >output &&
 	test_cmp expect output
 '
 
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 07/27] t3903: add test for --intent-to-add file
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (5 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 06/27] t3903: modernize style Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 08/27] stash: rename test cases to be more descriptive Thomas Gummerer
                                     ` (23 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Matthew Kraai, Johannes Schindelin,
	Thomas Gummerer

From: Matthew Kraai <mkraai@its.jnj.com>

Add a test showing the 'git stash' behaviour with a file that has been
added with 'git add --intent-to-add'.  Stash fails to stash the file,
so the purpose of this test is mainly to make sure git doesn't crash,
but exits normally in this situation.

This is in preparation for converting stash into a builtin.

[tg: pulled the test out into a separate commit]

Signed-off-by: Matthew Kraai <mkraai@its.jnj.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 t/t3903-stash.sh | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 4e83facf23..fc292f339f 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -287,6 +287,14 @@ test_expect_success 'stash an added file' '
 	test new = "$(cat file3)"
 '
 
+test_expect_success 'stash --intent-to-add file' '
+	git reset --hard &&
+	echo new >file4 &&
+	git add --intent-to-add file4 &&
+	test_when_finished "git rm -f file4" &&
+	test_must_fail git stash
+'
+
 test_expect_success 'stash rm then recreate' '
 	git reset --hard &&
 	git rm file &&
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 08/27] stash: rename test cases to be more descriptive
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (6 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 07/27] t3903: add test for --intent-to-add file Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 09/27] stash: add tests for `git stash show` config Thomas Gummerer
                                     ` (22 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

Rename some test cases' labels to be more descriptive and under 80
characters per line.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 t/t3903-stash.sh | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index fc292f339f..023421a36c 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -612,7 +612,7 @@ test_expect_success 'stash show -p - no stashes on stack, stash-like argument' '
 	test_cmp expected actual
 '
 
-test_expect_success 'stash drop - fail early if specified stash is not a stash reference' '
+test_expect_success 'drop: fail early if specified stash is not a stash ref' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD && git stash clear" &&
 	git reset --hard &&
@@ -626,7 +626,7 @@ test_expect_success 'stash drop - fail early if specified stash is not a stash r
 	git reset --hard HEAD
 '
 
-test_expect_success 'stash pop - fail early if specified stash is not a stash reference' '
+test_expect_success 'pop: fail early if specified stash is not a stash ref' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD && git stash clear" &&
 	git reset --hard &&
@@ -690,7 +690,7 @@ test_expect_success 'invalid ref of the form "n", n >= N' '
 	git stash drop
 '
 
-test_expect_success 'stash branch should not drop the stash if the branch exists' '
+test_expect_success 'branch: do not drop the stash if the branch exists' '
 	git stash clear &&
 	echo foo >file &&
 	git add file &&
@@ -701,7 +701,7 @@ test_expect_success 'stash branch should not drop the stash if the branch exists
 	git rev-parse stash@{0} --
 '
 
-test_expect_success 'stash branch should not drop the stash if the apply fails' '
+test_expect_success 'branch: should not drop the stash if the apply fails' '
 	git stash clear &&
 	git reset HEAD~1 --hard &&
 	echo foo >file &&
@@ -715,7 +715,7 @@ test_expect_success 'stash branch should not drop the stash if the apply fails'
 	git rev-parse stash@{0} --
 '
 
-test_expect_success 'stash apply shows status same as git status (relative to current directory)' '
+test_expect_success 'apply: show same status as git status (relative to ./)' '
 	git stash clear &&
 	echo 1 >subdir/subfile1 &&
 	echo 2 >subdir/subfile2 &&
@@ -1056,7 +1056,7 @@ test_expect_success 'stash push -p with pathspec shows no changes only once' '
 	test_i18ncmp expect actual
 '
 
-test_expect_success 'stash push with pathspec shows no changes when there are none' '
+test_expect_success 'push <pathspec>: show no changes when there are none' '
 	>foo &&
 	git add foo &&
 	git commit -m "tmp" &&
@@ -1066,7 +1066,7 @@ test_expect_success 'stash push with pathspec shows no changes when there are no
 	test_i18ncmp expect actual
 '
 
-test_expect_success 'stash push with pathspec not in the repository errors out' '
+test_expect_success 'push: <pathspec> not in the repository errors out' '
 	>untracked &&
 	test_must_fail git stash push untracked &&
 	test_path_is_file untracked
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 09/27] stash: add tests for `git stash show` config
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (7 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 08/27] stash: rename test cases to be more descriptive Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 10/27] stash: mention options in `show` synopsis Thomas Gummerer
                                     ` (21 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

This commit introduces tests for `git stash show`
config. It tests all the cases where `stash.showStat`
and `stash.showPatch` are unset or set to true / false.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 t/t3907-stash-show-config.sh | 83 ++++++++++++++++++++++++++++++++++++
 1 file changed, 83 insertions(+)
 create mode 100755 t/t3907-stash-show-config.sh

diff --git a/t/t3907-stash-show-config.sh b/t/t3907-stash-show-config.sh
new file mode 100755
index 0000000000..10914bba7b
--- /dev/null
+++ b/t/t3907-stash-show-config.sh
@@ -0,0 +1,83 @@
+#!/bin/sh
+
+test_description='Test git stash show configuration.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit file
+'
+
+# takes three parameters:
+# 1. the stash.showStat value (or "<unset>")
+# 2. the stash.showPatch value (or "<unset>")
+# 3. the diff options of the expected output (or nothing for no output)
+test_stat_and_patch () {
+	if test "<unset>" = "$1"
+	then
+		test_unconfig stash.showStat
+	else
+		test_config stash.showStat "$1"
+	fi &&
+
+	if test "<unset>" = "$2"
+	then
+		test_unconfig stash.showPatch
+	else
+		test_config stash.showPatch "$2"
+	fi &&
+
+	shift 2 &&
+	echo 2 >file.t &&
+	if test $# != 0
+	then
+		git diff "$@" >expect
+	fi &&
+	git stash &&
+	git stash show >actual &&
+
+	if test $# = 0
+	then
+		test_must_be_empty actual
+	else
+		test_cmp expect actual
+	fi
+}
+
+test_expect_success 'showStat unset showPatch unset' '
+	test_stat_and_patch "<unset>" "<unset>" --stat
+'
+
+test_expect_success 'showStat unset showPatch false' '
+	test_stat_and_patch "<unset>" false --stat
+'
+
+test_expect_success 'showStat unset showPatch true' '
+	test_stat_and_patch "<unset>" true --stat -p
+'
+
+test_expect_success 'showStat false showPatch unset' '
+	test_stat_and_patch false "<unset>"
+'
+
+test_expect_success 'showStat false showPatch false' '
+	test_stat_and_patch false false
+'
+
+test_expect_success 'showStat false showPatch true' '
+	test_stat_and_patch false true -p
+'
+
+test_expect_success 'showStat true showPatch unset' '
+	test_stat_and_patch true "<unset>" --stat
+'
+
+test_expect_success 'showStat true showPatch false' '
+	test_stat_and_patch true false --stat
+'
+
+test_expect_success 'showStat true showPatch true' '
+	test_stat_and_patch true true --stat -p
+'
+
+test_done
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 10/27] stash: mention options in `show` synopsis
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (8 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 09/27] stash: add tests for `git stash show` config Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 11/27] stash: convert apply to builtin Thomas Gummerer
                                     ` (20 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

Mention in the documentation, that `show` accepts any
option known to `git diff`.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 Documentation/git-stash.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 7ef8c47911..e31ea7d303 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git stash' list [<options>]
-'git stash' show [<stash>]
+'git stash' show [<options>] [<stash>]
 'git stash' drop [-q|--quiet] [<stash>]
 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
 'git stash' branch <branchname> [<stash>]
@@ -106,7 +106,7 @@ stash@{1}: On master: 9cc0589... Add git-stash
 The command takes options applicable to the 'git log'
 command to control what is shown and how. See linkgit:git-log[1].
 
-show [<stash>]::
+show [<options>] [<stash>]::
 
 	Show the changes recorded in the stash entry as a diff between the
 	stashed contents and the commit back when the stash entry was first
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 11/27] stash: convert apply to builtin
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (9 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 10/27] stash: mention options in `show` synopsis Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-03-14 13:19                     ` regression in new built-in stash + fsmonitor (was Re: [PATCH v13 11/27] stash: convert apply to builtin) Ævar Arnfjörð Bjarmason
  2019-02-25 23:16                   ` [PATCH v13 12/27] stash: convert drop and clear to builtin Thomas Gummerer
                                     ` (19 subsequent siblings)
  30 siblings, 1 reply; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Joel Teichroeb, Thomas Gummerer

From: Joel Teichroeb <joel@teichroeb.net>

Add a builtin helper for performing stash commands. Converting
all at once proved hard to review, so starting with just apply
lets conversion get started without the other commands being
finished.

The helper is being implemented as a drop in replacement for
stash so that when it is complete it can simply be renamed and
the shell script deleted.

Delete the contents of the apply_stash shell function and replace
it with a call to stash--helper apply until pop is also
converted.

Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 .gitignore              |   1 +
 Makefile                |   1 +
 builtin.h               |   1 +
 builtin/stash--helper.c | 451 ++++++++++++++++++++++++++++++++++++++++
 git-stash.sh            |  78 +------
 git.c                   |   1 +
 6 files changed, 462 insertions(+), 71 deletions(-)
 create mode 100644 builtin/stash--helper.c

diff --git a/.gitignore b/.gitignore
index 0d77ea5894..6ecab90ab2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -162,6 +162,7 @@
 /git-show-ref
 /git-stage
 /git-stash
+/git-stash--helper
 /git-status
 /git-stripspace
 /git-submodule
diff --git a/Makefile b/Makefile
index 1a44c811aa..c246fc7078 100644
--- a/Makefile
+++ b/Makefile
@@ -1117,6 +1117,7 @@ BUILTIN_OBJS += builtin/shortlog.o
 BUILTIN_OBJS += builtin/show-branch.o
 BUILTIN_OBJS += builtin/show-index.o
 BUILTIN_OBJS += builtin/show-ref.o
+BUILTIN_OBJS += builtin/stash--helper.o
 BUILTIN_OBJS += builtin/stripspace.o
 BUILTIN_OBJS += builtin/submodule--helper.o
 BUILTIN_OBJS += builtin/symbolic-ref.o
diff --git a/builtin.h b/builtin.h
index 6538932e99..ff4460aff7 100644
--- a/builtin.h
+++ b/builtin.h
@@ -225,6 +225,7 @@ extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_show_index(int argc, const char **argv, const char *prefix);
 extern int cmd_status(int argc, const char **argv, const char *prefix);
+extern int cmd_stash__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
new file mode 100644
index 0000000000..03511f466b
--- /dev/null
+++ b/builtin/stash--helper.c
@@ -0,0 +1,451 @@
+#include "builtin.h"
+#include "config.h"
+#include "parse-options.h"
+#include "refs.h"
+#include "lockfile.h"
+#include "cache-tree.h"
+#include "unpack-trees.h"
+#include "merge-recursive.h"
+#include "argv-array.h"
+#include "run-command.h"
+#include "dir.h"
+#include "rerere.h"
+
+static const char * const git_stash_helper_usage[] = {
+	N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
+	NULL
+};
+
+static const char * const git_stash_helper_apply_usage[] = {
+	N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
+	NULL
+};
+
+static const char *ref_stash = "refs/stash";
+static struct strbuf stash_index_path = STRBUF_INIT;
+
+/*
+ * w_commit is set to the commit containing the working tree
+ * b_commit is set to the base commit
+ * i_commit is set to the commit containing the index tree
+ * u_commit is set to the commit containing the untracked files tree
+ * w_tree is set to the working tree
+ * b_tree is set to the base tree
+ * i_tree is set to the index tree
+ * u_tree is set to the untracked files tree
+ */
+struct stash_info {
+	struct object_id w_commit;
+	struct object_id b_commit;
+	struct object_id i_commit;
+	struct object_id u_commit;
+	struct object_id w_tree;
+	struct object_id b_tree;
+	struct object_id i_tree;
+	struct object_id u_tree;
+	struct strbuf revision;
+	int is_stash_ref;
+	int has_u;
+};
+
+static void free_stash_info(struct stash_info *info)
+{
+	strbuf_release(&info->revision);
+}
+
+static void assert_stash_like(struct stash_info *info, const char *revision)
+{
+	if (get_oidf(&info->b_commit, "%s^1", revision) ||
+	    get_oidf(&info->w_tree, "%s:", revision) ||
+	    get_oidf(&info->b_tree, "%s^1:", revision) ||
+	    get_oidf(&info->i_tree, "%s^2:", revision))
+		die(_("'%s' is not a stash-like commit"), revision);
+}
+
+static int get_stash_info(struct stash_info *info, int argc, const char **argv)
+{
+	int ret;
+	char *end_of_rev;
+	char *expanded_ref;
+	const char *revision;
+	const char *commit = NULL;
+	struct object_id dummy;
+	struct strbuf symbolic = STRBUF_INIT;
+
+	if (argc > 1) {
+		int i;
+		struct strbuf refs_msg = STRBUF_INIT;
+
+		for (i = 0; i < argc; i++)
+			strbuf_addf(&refs_msg, " '%s'", argv[i]);
+
+		fprintf_ln(stderr, _("Too many revisions specified:%s"),
+			   refs_msg.buf);
+		strbuf_release(&refs_msg);
+
+		return -1;
+	}
+
+	if (argc == 1)
+		commit = argv[0];
+
+	strbuf_init(&info->revision, 0);
+	if (!commit) {
+		if (!ref_exists(ref_stash)) {
+			free_stash_info(info);
+			fprintf_ln(stderr, _("No stash entries found."));
+			return -1;
+		}
+
+		strbuf_addf(&info->revision, "%s@{0}", ref_stash);
+	} else if (strspn(commit, "0123456789") == strlen(commit)) {
+		strbuf_addf(&info->revision, "%s@{%s}", ref_stash, commit);
+	} else {
+		strbuf_addstr(&info->revision, commit);
+	}
+
+	revision = info->revision.buf;
+
+	if (get_oid(revision, &info->w_commit)) {
+		error(_("%s is not a valid reference"), revision);
+		free_stash_info(info);
+		return -1;
+	}
+
+	assert_stash_like(info, revision);
+
+	info->has_u = !get_oidf(&info->u_tree, "%s^3:", revision);
+
+	end_of_rev = strchrnul(revision, '@');
+	strbuf_add(&symbolic, revision, end_of_rev - revision);
+
+	ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref);
+	strbuf_release(&symbolic);
+	switch (ret) {
+	case 0: /* Not found, but valid ref */
+		info->is_stash_ref = 0;
+		break;
+	case 1:
+		info->is_stash_ref = !strcmp(expanded_ref, ref_stash);
+		break;
+	default: /* Invalid or ambiguous */
+		free_stash_info(info);
+	}
+
+	free(expanded_ref);
+	return !(ret == 0 || ret == 1);
+}
+
+static int reset_tree(struct object_id *i_tree, int update, int reset)
+{
+	int nr_trees = 1;
+	struct unpack_trees_options opts;
+	struct tree_desc t[MAX_UNPACK_TREES];
+	struct tree *tree;
+	struct lock_file lock_file = LOCK_INIT;
+
+	read_cache_preload(NULL);
+	if (refresh_cache(REFRESH_QUIET))
+		return -1;
+
+	hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
+
+	memset(&opts, 0, sizeof(opts));
+
+	tree = parse_tree_indirect(i_tree);
+	if (parse_tree(tree))
+		return -1;
+
+	init_tree_desc(t, tree->buffer, tree->size);
+
+	opts.head_idx = 1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.merge = 1;
+	opts.reset = reset;
+	opts.update = update;
+	opts.fn = oneway_merge;
+
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+
+	if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
+		return error(_("unable to write new index file"));
+
+	return 0;
+}
+
+static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	const char *w_commit_hex = oid_to_hex(w_commit);
+
+	/*
+	 * Diff-tree would not be very hard to replace with a native function,
+	 * however it should be done together with apply_cached.
+	 */
+	cp.git_cmd = 1;
+	argv_array_pushl(&cp.args, "diff-tree", "--binary", NULL);
+	argv_array_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex);
+
+	return pipe_command(&cp, NULL, 0, out, 0, NULL, 0);
+}
+
+static int apply_cached(struct strbuf *out)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+
+	/*
+	 * Apply currently only reads either from stdin or a file, thus
+	 * apply_all_patches would have to be updated to optionally take a
+	 * buffer.
+	 */
+	cp.git_cmd = 1;
+	argv_array_pushl(&cp.args, "apply", "--cached", NULL);
+	return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0);
+}
+
+static int reset_head(void)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+
+	/*
+	 * Reset is overall quite simple, however there is no current public
+	 * API for resetting.
+	 */
+	cp.git_cmd = 1;
+	argv_array_push(&cp.args, "reset");
+
+	return run_command(&cp);
+}
+
+static int get_newly_staged(struct strbuf *out, struct object_id *c_tree)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	const char *c_tree_hex = oid_to_hex(c_tree);
+
+	/*
+	 * diff-index is very similar to diff-tree above, and should be
+	 * converted together with update_index.
+	 */
+	cp.git_cmd = 1;
+	argv_array_pushl(&cp.args, "diff-index", "--cached", "--name-only",
+			 "--diff-filter=A", NULL);
+	argv_array_push(&cp.args, c_tree_hex);
+	return pipe_command(&cp, NULL, 0, out, 0, NULL, 0);
+}
+
+static int update_index(struct strbuf *out)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+
+	/*
+	 * Update-index is very complicated and may need to have a public
+	 * function exposed in order to remove this forking.
+	 */
+	cp.git_cmd = 1;
+	argv_array_pushl(&cp.args, "update-index", "--add", "--stdin", NULL);
+	return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0);
+}
+
+static int restore_untracked(struct object_id *u_tree)
+{
+	int res;
+	struct child_process cp = CHILD_PROCESS_INIT;
+
+	/*
+	 * We need to run restore files from a given index, but without
+	 * affecting the current index, so we use GIT_INDEX_FILE with
+	 * run_command to fork processes that will not interfere.
+	 */
+	cp.git_cmd = 1;
+	argv_array_push(&cp.args, "read-tree");
+	argv_array_push(&cp.args, oid_to_hex(u_tree));
+	argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+	if (run_command(&cp)) {
+		remove_path(stash_index_path.buf);
+		return -1;
+	}
+
+	child_process_init(&cp);
+	cp.git_cmd = 1;
+	argv_array_pushl(&cp.args, "checkout-index", "--all", NULL);
+	argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+
+	res = run_command(&cp);
+	remove_path(stash_index_path.buf);
+	return res;
+}
+
+static int do_apply_stash(const char *prefix, struct stash_info *info,
+			  int index, int quiet)
+{
+	int ret;
+	int has_index = index;
+	struct merge_options o;
+	struct object_id c_tree;
+	struct object_id index_tree;
+	struct commit *result;
+	const struct object_id *bases[1];
+
+	read_cache_preload(NULL);
+	if (refresh_cache(REFRESH_QUIET))
+		return -1;
+
+	if (write_cache_as_tree(&c_tree, 0, NULL))
+		return error(_("cannot apply a stash in the middle of a merge"));
+
+	if (index) {
+		if (oideq(&info->b_tree, &info->i_tree) ||
+		    oideq(&c_tree, &info->i_tree)) {
+			has_index = 0;
+		} else {
+			struct strbuf out = STRBUF_INIT;
+
+			if (diff_tree_binary(&out, &info->w_commit)) {
+				strbuf_release(&out);
+				return error(_("could not generate diff %s^!."),
+					     oid_to_hex(&info->w_commit));
+			}
+
+			ret = apply_cached(&out);
+			strbuf_release(&out);
+			if (ret)
+				return error(_("conflicts in index."
+					       "Try without --index."));
+
+			discard_cache();
+			read_cache();
+			if (write_cache_as_tree(&index_tree, 0, NULL))
+				return error(_("could not save index tree"));
+
+			reset_head();
+		}
+	}
+
+	if (info->has_u && restore_untracked(&info->u_tree))
+		return error(_("could not restore untracked files from stash"));
+
+	init_merge_options(&o);
+
+	o.branch1 = "Updated upstream";
+	o.branch2 = "Stashed changes";
+
+	if (oideq(&info->b_tree, &c_tree))
+		o.branch1 = "Version stash was based on";
+
+	if (quiet)
+		o.verbosity = 0;
+
+	if (o.verbosity >= 3)
+		printf_ln(_("Merging %s with %s"), o.branch1, o.branch2);
+
+	bases[0] = &info->b_tree;
+
+	ret = merge_recursive_generic(&o, &c_tree, &info->w_tree, 1, bases,
+				      &result);
+	if (ret) {
+		rerere(0);
+
+		if (index)
+			fprintf_ln(stderr, _("Index was not unstashed."));
+
+		return ret;
+	}
+
+	if (has_index) {
+		if (reset_tree(&index_tree, 0, 0))
+			return -1;
+	} else {
+		struct strbuf out = STRBUF_INIT;
+
+		if (get_newly_staged(&out, &c_tree)) {
+			strbuf_release(&out);
+			return -1;
+		}
+
+		if (reset_tree(&c_tree, 0, 1)) {
+			strbuf_release(&out);
+			return -1;
+		}
+
+		ret = update_index(&out);
+		strbuf_release(&out);
+		if (ret)
+			return -1;
+
+		discard_cache();
+	}
+
+	if (quiet) {
+		if (refresh_cache(REFRESH_QUIET))
+			warning("could not refresh index");
+	} else {
+		struct child_process cp = CHILD_PROCESS_INIT;
+
+		/*
+		 * Status is quite simple and could be replaced with calls to
+		 * wt_status in the future, but it adds complexities which may
+		 * require more tests.
+		 */
+		cp.git_cmd = 1;
+		cp.dir = prefix;
+		argv_array_push(&cp.args, "status");
+		run_command(&cp);
+	}
+
+	return 0;
+}
+
+static int apply_stash(int argc, const char **argv, const char *prefix)
+{
+	int ret;
+	int quiet = 0;
+	int index = 0;
+	struct stash_info info;
+	struct option options[] = {
+		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
+		OPT_BOOL(0, "index", &index,
+			 N_("attempt to recreate the index")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_apply_usage, 0);
+
+	if (get_stash_info(&info, argc, argv))
+		return -1;
+
+	ret = do_apply_stash(prefix, &info, index, quiet);
+	free_stash_info(&info);
+	return ret;
+}
+
+int cmd_stash__helper(int argc, const char **argv, const char *prefix)
+{
+	pid_t pid = getpid();
+	const char *index_file;
+
+	struct option options[] = {
+		OPT_END()
+	};
+
+	git_config(git_default_config, NULL);
+
+	argc = parse_options(argc, argv, prefix, options, git_stash_helper_usage,
+			     PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
+
+	index_file = get_index_file();
+	strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file,
+		    (uintmax_t)pid);
+
+	if (argc < 1)
+		usage_with_options(git_stash_helper_usage, options);
+	if (!strcmp(argv[0], "apply"))
+		return !!apply_stash(argc, argv, prefix);
+
+	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
+		      git_stash_helper_usage, options);
+}
diff --git a/git-stash.sh b/git-stash.sh
index 789ce2f41d..366a082853 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -583,76 +583,11 @@ assert_stash_ref() {
 }
 
 apply_stash () {
-
-	assert_stash_like "$@"
-
-	git update-index -q --refresh || die "$(gettext "unable to refresh index")"
-
-	# current index state
-	c_tree=$(git write-tree) ||
-		die "$(gettext "Cannot apply a stash in the middle of a merge")"
-
-	unstashed_index_tree=
-	if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" &&
-			test "$c_tree" != "$i_tree"
-	then
-		git diff-tree --binary $s^2^..$s^2 | git apply --cached
-		test $? -ne 0 &&
-			die "$(gettext "Conflicts in index. Try without --index.")"
-		unstashed_index_tree=$(git write-tree) ||
-			die "$(gettext "Could not save index tree")"
-		git reset
-	fi
-
-	if test -n "$u_tree"
-	then
-		GIT_INDEX_FILE="$TMPindex" git read-tree "$u_tree" &&
-		GIT_INDEX_FILE="$TMPindex" git checkout-index --all &&
-		rm -f "$TMPindex" ||
-		die "$(gettext "Could not restore untracked files from stash entry")"
-	fi
-
-	eval "
-		GITHEAD_$w_tree='Stashed changes' &&
-		GITHEAD_$c_tree='Updated upstream' &&
-		GITHEAD_$b_tree='Version stash was based on' &&
-		export GITHEAD_$w_tree GITHEAD_$c_tree GITHEAD_$b_tree
-	"
-
-	if test -n "$GIT_QUIET"
-	then
-		GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY
-	fi
-	if git merge-recursive $b_tree -- $c_tree $w_tree
-	then
-		# No conflict
-		if test -n "$unstashed_index_tree"
-		then
-			git read-tree "$unstashed_index_tree"
-		else
-			a="$TMP-added" &&
-			git diff-index --cached --name-only --diff-filter=A $c_tree >"$a" &&
-			git read-tree --reset $c_tree &&
-			git update-index --add --stdin <"$a" ||
-				die "$(gettext "Cannot unstage modified files")"
-			rm -f "$a"
-		fi
-		squelch=
-		if test -n "$GIT_QUIET"
-		then
-			squelch='>/dev/null 2>&1'
-		fi
-		(cd "$START_DIR" && eval "git status $squelch") || :
-	else
-		# Merge conflict; keep the exit status from merge-recursive
-		status=$?
-		git rerere
-		if test -n "$INDEX_OPTION"
-		then
-			gettextln "Index was not unstashed." >&2
-		fi
-		exit $status
-	fi
+	cd "$START_DIR"
+	git stash--helper apply "$@"
+	res=$?
+	cd_to_toplevel
+	return $res
 }
 
 pop_stash() {
@@ -730,7 +665,8 @@ push)
 	;;
 apply)
 	shift
-	apply_stash "$@"
+	cd "$START_DIR"
+	git stash--helper apply "$@"
 	;;
 clear)
 	shift
diff --git a/git.c b/git.c
index 2f604a41ea..76ee02802e 100644
--- a/git.c
+++ b/git.c
@@ -554,6 +554,7 @@ static struct cmd_struct commands[] = {
 	{ "show-index", cmd_show_index },
 	{ "show-ref", cmd_show_ref, RUN_SETUP },
 	{ "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
+	{ "stash--helper", cmd_stash__helper, RUN_SETUP | NEED_WORK_TREE },
 	{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 	{ "stripspace", cmd_stripspace },
 	{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 12/27] stash: convert drop and clear to builtin
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (10 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 11/27] stash: convert apply to builtin Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-03-07 19:15                     ` Jeff King
  2019-02-25 23:16                   ` [PATCH v13 13/27] stash: convert branch " Thomas Gummerer
                                     ` (18 subsequent siblings)
  30 siblings, 1 reply; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Joel Teichroeb, Thomas Gummerer

From: Joel Teichroeb <joel@teichroeb.net>

Add the drop and clear commands to the builtin helper. These two
are each simple, but are being added together as they are quite
related.

We have to unfortunately keep the drop and clear functions in the
shell script as functions are called with parameters internally
that are not valid when the commands are called externally. Once
pop is converted they can both be removed.

Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 builtin/stash--helper.c | 117 ++++++++++++++++++++++++++++++++++++++++
 git-stash.sh            |   4 +-
 2 files changed, 119 insertions(+), 2 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 03511f466b..c515f0b358 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -12,7 +12,14 @@
 #include "rerere.h"
 
 static const char * const git_stash_helper_usage[] = {
+	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
 	N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
+	N_("git stash--helper clear"),
+	NULL
+};
+
+static const char * const git_stash_helper_drop_usage[] = {
+	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
 	NULL
 };
 
@@ -21,6 +28,11 @@ static const char * const git_stash_helper_apply_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_clear_usage[] = {
+	N_("git stash--helper clear"),
+	NULL
+};
+
 static const char *ref_stash = "refs/stash";
 static struct strbuf stash_index_path = STRBUF_INIT;
 
@@ -136,6 +148,32 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)
 	return !(ret == 0 || ret == 1);
 }
 
+static int do_clear_stash(void)
+{
+	struct object_id obj;
+	if (get_oid(ref_stash, &obj))
+		return 0;
+
+	return delete_ref(NULL, ref_stash, &obj, 0);
+}
+
+static int clear_stash(int argc, const char **argv, const char *prefix)
+{
+	struct option options[] = {
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_clear_usage,
+			     PARSE_OPT_STOP_AT_NON_OPTION);
+
+	if (argc)
+		return error(_("git stash clear with parameters is "
+			       "unimplemented"));
+
+	return do_clear_stash();
+}
+
 static int reset_tree(struct object_id *i_tree, int update, int reset)
 {
 	int nr_trees = 1;
@@ -423,6 +461,81 @@ static int apply_stash(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
+static int do_drop_stash(const char *prefix, struct stash_info *info, int quiet)
+{
+	int ret;
+	struct child_process cp_reflog = CHILD_PROCESS_INIT;
+	struct child_process cp = CHILD_PROCESS_INIT;
+
+	/*
+	 * reflog does not provide a simple function for deleting refs. One will
+	 * need to be added to avoid implementing too much reflog code here
+	 */
+
+	cp_reflog.git_cmd = 1;
+	argv_array_pushl(&cp_reflog.args, "reflog", "delete", "--updateref",
+			 "--rewrite", NULL);
+	argv_array_push(&cp_reflog.args, info->revision.buf);
+	ret = run_command(&cp_reflog);
+	if (!ret) {
+		if (!quiet)
+			printf_ln(_("Dropped %s (%s)"), info->revision.buf,
+				  oid_to_hex(&info->w_commit));
+	} else {
+		return error(_("%s: Could not drop stash entry"),
+			     info->revision.buf);
+	}
+
+	/*
+	 * This could easily be replaced by get_oid, but currently it will throw
+	 * a fatal error when a reflog is empty, which we can not recover from.
+	 */
+	cp.git_cmd = 1;
+	/* Even though --quiet is specified, rev-parse still outputs the hash */
+	cp.no_stdout = 1;
+	argv_array_pushl(&cp.args, "rev-parse", "--verify", "--quiet", NULL);
+	argv_array_pushf(&cp.args, "%s@{0}", ref_stash);
+	ret = run_command(&cp);
+
+	/* do_clear_stash if we just dropped the last stash entry */
+	if (ret)
+		do_clear_stash();
+
+	return 0;
+}
+
+static void assert_stash_ref(struct stash_info *info)
+{
+	if (!info->is_stash_ref) {
+		error(_("'%s' is not a stash reference"), info->revision.buf);
+		free_stash_info(info);
+		exit(1);
+	}
+}
+
+static int drop_stash(int argc, const char **argv, const char *prefix)
+{
+	int ret;
+	int quiet = 0;
+	struct stash_info info;
+	struct option options[] = {
+		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_drop_usage, 0);
+
+	if (get_stash_info(&info, argc, argv))
+		return -1;
+
+	assert_stash_ref(&info);
+
+	ret = do_drop_stash(prefix, &info, quiet);
+	free_stash_info(&info);
+	return ret;
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -445,6 +558,10 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_stash_helper_usage, options);
 	if (!strcmp(argv[0], "apply"))
 		return !!apply_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "clear"))
+		return !!clear_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "drop"))
+		return !!drop_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index 366a082853..b8f70230f9 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -670,7 +670,7 @@ apply)
 	;;
 clear)
 	shift
-	clear_stash "$@"
+	git stash--helper clear "$@"
 	;;
 create)
 	shift
@@ -682,7 +682,7 @@ store)
 	;;
 drop)
 	shift
-	drop_stash "$@"
+	git stash--helper drop "$@"
 	;;
 pop)
 	shift
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 13/27] stash: convert branch to builtin
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (11 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 12/27] stash: convert drop and clear to builtin Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 14/27] stash: convert pop " Thomas Gummerer
                                     ` (17 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Joel Teichroeb, Thomas Gummerer

From: Joel Teichroeb <joel@teichroeb.net>

Add stash branch to the helper and delete the apply_to_branch
function from the shell script.

Checkout does not currently provide a function for checking out
a branch as cmd_checkout does a large amount of sanity checks
first that we require here.

Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 builtin/stash--helper.c | 46 +++++++++++++++++++++++++++++++++++++++++
 git-stash.sh            | 17 ++-------------
 2 files changed, 48 insertions(+), 15 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index c515f0b358..c9874e2f5d 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -14,6 +14,7 @@
 static const char * const git_stash_helper_usage[] = {
 	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
 	N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
+	N_("git stash--helper branch <branchname> [<stash>]"),
 	N_("git stash--helper clear"),
 	NULL
 };
@@ -28,6 +29,11 @@ static const char * const git_stash_helper_apply_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_branch_usage[] = {
+	N_("git stash--helper branch <branchname> [<stash>]"),
+	NULL
+};
+
 static const char * const git_stash_helper_clear_usage[] = {
 	N_("git stash--helper clear"),
 	NULL
@@ -536,6 +542,44 @@ static int drop_stash(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
+static int branch_stash(int argc, const char **argv, const char *prefix)
+{
+	int ret;
+	const char *branch = NULL;
+	struct stash_info info;
+	struct child_process cp = CHILD_PROCESS_INIT;
+	struct option options[] = {
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_branch_usage, 0);
+
+	if (!argc) {
+		fprintf_ln(stderr, _("No branch name specified"));
+		return -1;
+	}
+
+	branch = argv[0];
+
+	if (get_stash_info(&info, argc - 1, argv + 1))
+		return -1;
+
+	cp.git_cmd = 1;
+	argv_array_pushl(&cp.args, "checkout", "-b", NULL);
+	argv_array_push(&cp.args, branch);
+	argv_array_push(&cp.args, oid_to_hex(&info.b_commit));
+	ret = run_command(&cp);
+	if (!ret)
+		ret = do_apply_stash(prefix, &info, 1, 0);
+	if (!ret && info.is_stash_ref)
+		ret = do_drop_stash(prefix, &info, 0);
+
+	free_stash_info(&info);
+
+	return ret;
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -562,6 +606,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!clear_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "drop"))
 		return !!drop_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "branch"))
+		return !!branch_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index b8f70230f9..67db321a4c 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -615,20 +615,6 @@ drop_stash () {
 	clear_stash
 }
 
-apply_to_branch () {
-	test -n "$1" || die "$(gettext "No branch name specified")"
-	branch=$1
-	shift 1
-
-	set -- --index "$@"
-	assert_stash_like "$@"
-
-	git checkout -b $branch $REV^ &&
-	apply_stash "$@" && {
-		test -z "$IS_STASH_REF" || drop_stash "$@"
-	}
-}
-
 test "$1" = "-p" && set "push" "$@"
 
 PARSE_CACHE='--not-parsed'
@@ -690,7 +676,8 @@ pop)
 	;;
 branch)
 	shift
-	apply_to_branch "$@"
+	cd "$START_DIR"
+	git stash--helper branch "$@"
 	;;
 *)
 	case $# in
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 14/27] stash: convert pop to builtin
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (12 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 13/27] stash: convert branch " Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 15/27] stash: convert list " Thomas Gummerer
                                     ` (16 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Joel Teichroeb, Thomas Gummerer

From: Joel Teichroeb <joel@teichroeb.net>

Add stash pop to the helper and delete the pop_stash, drop_stash,
assert_stash_ref functions from the shell script now that they
are no longer needed.

Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 builtin/stash--helper.c | 39 +++++++++++++++++++++++++++++++++-
 git-stash.sh            | 47 ++---------------------------------------
 2 files changed, 40 insertions(+), 46 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index c9874e2f5d..63e1e84e78 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -13,7 +13,7 @@
 
 static const char * const git_stash_helper_usage[] = {
 	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
-	N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
+	N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
 	N_("git stash--helper branch <branchname> [<stash>]"),
 	N_("git stash--helper clear"),
 	NULL
@@ -24,6 +24,11 @@ static const char * const git_stash_helper_drop_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_pop_usage[] = {
+	N_("git stash--helper pop [--index] [-q|--quiet] [<stash>]"),
+	NULL
+};
+
 static const char * const git_stash_helper_apply_usage[] = {
 	N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
 	NULL
@@ -542,6 +547,36 @@ static int drop_stash(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
+static int pop_stash(int argc, const char **argv, const char *prefix)
+{
+	int ret;
+	int index = 0;
+	int quiet = 0;
+	struct stash_info info;
+	struct option options[] = {
+		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
+		OPT_BOOL(0, "index", &index,
+			 N_("attempt to recreate the index")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_pop_usage, 0);
+
+	if (get_stash_info(&info, argc, argv))
+		return -1;
+
+	assert_stash_ref(&info);
+	if ((ret = do_apply_stash(prefix, &info, index, quiet)))
+		printf_ln(_("The stash entry is kept in case "
+			    "you need it again."));
+	else
+		ret = do_drop_stash(prefix, &info, quiet);
+
+	free_stash_info(&info);
+	return ret;
+}
+
 static int branch_stash(int argc, const char **argv, const char *prefix)
 {
 	int ret;
@@ -606,6 +641,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!clear_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "drop"))
 		return !!drop_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "pop"))
+		return !!pop_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "branch"))
 		return !!branch_stash(argc, argv, prefix);
 
diff --git a/git-stash.sh b/git-stash.sh
index 67db321a4c..8a9f907aa9 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -571,50 +571,6 @@ assert_stash_like() {
 	}
 }
 
-is_stash_ref() {
-	is_stash_like "$@" && test -n "$IS_STASH_REF"
-}
-
-assert_stash_ref() {
-	is_stash_ref "$@" || {
-		args="$*"
-		die "$(eval_gettext "'\$args' is not a stash reference")"
-	}
-}
-
-apply_stash () {
-	cd "$START_DIR"
-	git stash--helper apply "$@"
-	res=$?
-	cd_to_toplevel
-	return $res
-}
-
-pop_stash() {
-	assert_stash_ref "$@"
-
-	if apply_stash "$@"
-	then
-		drop_stash "$@"
-	else
-		status=$?
-		say "$(gettext "The stash entry is kept in case you need it again.")"
-		exit $status
-	fi
-}
-
-drop_stash () {
-	assert_stash_ref "$@"
-
-	git reflog delete --updateref --rewrite "${REV}" &&
-		say "$(eval_gettext "Dropped \${REV} (\$s)")" ||
-		die "$(eval_gettext "\${REV}: Could not drop stash entry")"
-
-	# clear_stash if we just dropped the last stash entry
-	git rev-parse --verify --quiet "$ref_stash@{0}" >/dev/null ||
-	clear_stash
-}
-
 test "$1" = "-p" && set "push" "$@"
 
 PARSE_CACHE='--not-parsed'
@@ -672,7 +628,8 @@ drop)
 	;;
 pop)
 	shift
-	pop_stash "$@"
+	cd "$START_DIR"
+	git stash--helper pop "$@"
 	;;
 branch)
 	shift
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 15/27] stash: convert list to builtin
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (13 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 14/27] stash: convert pop " Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 16/27] stash: convert show " Thomas Gummerer
                                     ` (15 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

Add stash list to the helper and delete the list_stash function
from the shell script.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 builtin/stash--helper.c | 31 +++++++++++++++++++++++++++++++
 git-stash.sh            |  7 +------
 2 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 63e1e84e78..f296224577 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -12,6 +12,7 @@
 #include "rerere.h"
 
 static const char * const git_stash_helper_usage[] = {
+	N_("git stash--helper list [<options>]"),
 	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
 	N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
 	N_("git stash--helper branch <branchname> [<stash>]"),
@@ -19,6 +20,11 @@ static const char * const git_stash_helper_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_list_usage[] = {
+	N_("git stash--helper list [<options>]"),
+	NULL
+};
+
 static const char * const git_stash_helper_drop_usage[] = {
 	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
 	NULL
@@ -615,6 +621,29 @@ static int branch_stash(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
+static int list_stash(int argc, const char **argv, const char *prefix)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	struct option options[] = {
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_list_usage,
+			     PARSE_OPT_KEEP_UNKNOWN);
+
+	if (!ref_exists(ref_stash))
+		return 0;
+
+	cp.git_cmd = 1;
+	argv_array_pushl(&cp.args, "log", "--format=%gd: %gs", "-g",
+			 "--first-parent", "-m", NULL);
+	argv_array_pushv(&cp.args, argv);
+	argv_array_push(&cp.args, ref_stash);
+	argv_array_push(&cp.args, "--");
+	return run_command(&cp);
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -645,6 +674,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!pop_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "branch"))
 		return !!branch_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "list"))
+		return !!list_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index 8a9f907aa9..ab3992b59d 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -399,11 +399,6 @@ have_stash () {
 	git rev-parse --verify --quiet $ref_stash >/dev/null
 }
 
-list_stash () {
-	have_stash || return 0
-	git log --format="%gd: %gs" -g --first-parent -m "$@" $ref_stash --
-}
-
 show_stash () {
 	ALLOW_UNKNOWN_FLAGS=t
 	assert_stash_like "$@"
@@ -591,7 +586,7 @@ test -n "$seen_non_option" || set "push" "$@"
 case "$1" in
 list)
 	shift
-	list_stash "$@"
+	git stash--helper list "$@"
 	;;
 show)
 	shift
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 16/27] stash: convert show to builtin
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (14 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 15/27] stash: convert list " Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 17/27] stash: convert store " Thomas Gummerer
                                     ` (14 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

Add stash show to the helper and delete the show_stash, have_stash,
assert_stash_like, is_stash_like and parse_flags_and_rev functions
from the shell script now that they are no longer needed.

In shell version, although `git stash show` accepts `--index` and
`--quiet` options, it ignores them. In C, both options are passed
further to `git diff`.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 builtin/stash--helper.c |  87 ++++++++++++++++++++++++++
 git-stash.sh            | 132 +---------------------------------------
 2 files changed, 88 insertions(+), 131 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index f296224577..4028b64314 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -10,9 +10,12 @@
 #include "run-command.h"
 #include "dir.h"
 #include "rerere.h"
+#include "revision.h"
+#include "log-tree.h"
 
 static const char * const git_stash_helper_usage[] = {
 	N_("git stash--helper list [<options>]"),
+	N_("git stash--helper show [<options>] [<stash>]"),
 	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
 	N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
 	N_("git stash--helper branch <branchname> [<stash>]"),
@@ -25,6 +28,11 @@ static const char * const git_stash_helper_list_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_show_usage[] = {
+	N_("git stash--helper show [<options>] [<stash>]"),
+	NULL
+};
+
 static const char * const git_stash_helper_drop_usage[] = {
 	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
 	NULL
@@ -644,6 +652,83 @@ static int list_stash(int argc, const char **argv, const char *prefix)
 	return run_command(&cp);
 }
 
+static int show_stat = 1;
+static int show_patch;
+
+static int git_stash_config(const char *var, const char *value, void *cb)
+{
+	if (!strcmp(var, "stash.showstat")) {
+		show_stat = git_config_bool(var, value);
+		return 0;
+	}
+	if (!strcmp(var, "stash.showpatch")) {
+		show_patch = git_config_bool(var, value);
+		return 0;
+	}
+	return git_default_config(var, value, cb);
+}
+
+static int show_stash(int argc, const char **argv, const char *prefix)
+{
+	int i;
+	int opts = 0;
+	int ret = 0;
+	struct stash_info info;
+	struct rev_info rev;
+	struct argv_array stash_args = ARGV_ARRAY_INIT;
+	struct option options[] = {
+		OPT_END()
+	};
+
+	init_diff_ui_defaults();
+	git_config(git_diff_ui_config, NULL);
+	init_revisions(&rev, prefix);
+
+	for (i = 1; i < argc; i++) {
+		if (argv[i][0] != '-')
+			argv_array_push(&stash_args, argv[i]);
+		else
+			opts++;
+	}
+
+	ret = get_stash_info(&info, stash_args.argc, stash_args.argv);
+	argv_array_clear(&stash_args);
+	if (ret)
+		return -1;
+
+	/*
+	 * The config settings are applied only if there are not passed
+	 * any options.
+	 */
+	if (!opts) {
+		git_config(git_stash_config, NULL);
+		if (show_stat)
+			rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT;
+
+		if (show_patch)
+			rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
+
+		if (!show_stat && !show_patch) {
+			free_stash_info(&info);
+			return 0;
+		}
+	}
+
+	argc = setup_revisions(argc, argv, &rev, NULL);
+	if (argc > 1) {
+		free_stash_info(&info);
+		usage_with_options(git_stash_helper_show_usage, options);
+	}
+
+	rev.diffopt.flags.recursive = 1;
+	setup_diff_pager(&rev.diffopt);
+	diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt);
+	log_tree_diff_flush(&rev);
+
+	free_stash_info(&info);
+	return diff_result_code(&rev.diffopt, 0);
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -676,6 +761,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!branch_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "list"))
 		return !!list_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "show"))
+		return !!show_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index ab3992b59d..d0318f859e 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -395,35 +395,6 @@ save_stash () {
 	fi
 }
 
-have_stash () {
-	git rev-parse --verify --quiet $ref_stash >/dev/null
-}
-
-show_stash () {
-	ALLOW_UNKNOWN_FLAGS=t
-	assert_stash_like "$@"
-
-	if test -z "$FLAGS"
-	then
-		if test "$(git config --bool stash.showStat || echo true)" = "true"
-		then
-			FLAGS=--stat
-		fi
-
-		if test "$(git config --bool stash.showPatch || echo false)" = "true"
-		then
-			FLAGS=${FLAGS}${FLAGS:+ }-p
-		fi
-
-		if test -z "$FLAGS"
-		then
-			return 0
-		fi
-	fi
-
-	git diff ${FLAGS} $b_commit $w_commit
-}
-
 show_help () {
 	exec git help stash
 	exit 1
@@ -465,107 +436,6 @@ show_help () {
 #   * unknown flags were set and ALLOW_UNKNOWN_FLAGS is not "t"
 #
 
-parse_flags_and_rev()
-{
-	test "$PARSE_CACHE" = "$*" && return 0 # optimisation
-	PARSE_CACHE="$*"
-
-	IS_STASH_LIKE=
-	IS_STASH_REF=
-	INDEX_OPTION=
-	s=
-	w_commit=
-	b_commit=
-	i_commit=
-	u_commit=
-	w_tree=
-	b_tree=
-	i_tree=
-	u_tree=
-
-	FLAGS=
-	REV=
-	for opt
-	do
-		case "$opt" in
-			-q|--quiet)
-				GIT_QUIET=-t
-			;;
-			--index)
-				INDEX_OPTION=--index
-			;;
-			--help)
-				show_help
-			;;
-			-*)
-				test "$ALLOW_UNKNOWN_FLAGS" = t ||
-					die "$(eval_gettext "unknown option: \$opt")"
-				FLAGS="${FLAGS}${FLAGS:+ }$opt"
-			;;
-			*)
-				REV="${REV}${REV:+ }'$opt'"
-			;;
-		esac
-	done
-
-	eval set -- $REV
-
-	case $# in
-		0)
-			have_stash || die "$(gettext "No stash entries found.")"
-			set -- ${ref_stash}@{0}
-		;;
-		1)
-			:
-		;;
-		*)
-			die "$(eval_gettext "Too many revisions specified: \$REV")"
-		;;
-	esac
-
-	case "$1" in
-		*[!0-9]*)
-			:
-		;;
-		*)
-			set -- "${ref_stash}@{$1}"
-		;;
-	esac
-
-	REV=$(git rev-parse --symbolic --verify --quiet "$1") || {
-		reference="$1"
-		die "$(eval_gettext "\$reference is not a valid reference")"
-	}
-
-	i_commit=$(git rev-parse --verify --quiet "$REV^2") &&
-	set -- $(git rev-parse "$REV" "$REV^1" "$REV:" "$REV^1:" "$REV^2:" 2>/dev/null) &&
-	s=$1 &&
-	w_commit=$1 &&
-	b_commit=$2 &&
-	w_tree=$3 &&
-	b_tree=$4 &&
-	i_tree=$5 &&
-	IS_STASH_LIKE=t &&
-	test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
-	IS_STASH_REF=t
-
-	u_commit=$(git rev-parse --verify --quiet "$REV^3") &&
-	u_tree=$(git rev-parse "$REV^3:" 2>/dev/null)
-}
-
-is_stash_like()
-{
-	parse_flags_and_rev "$@"
-	test -n "$IS_STASH_LIKE"
-}
-
-assert_stash_like() {
-	is_stash_like "$@" || {
-		args="$*"
-		die "$(eval_gettext "'\$args' is not a stash-like commit")"
-	}
-}
-
 test "$1" = "-p" && set "push" "$@"
 
 PARSE_CACHE='--not-parsed'
@@ -590,7 +460,7 @@ list)
 	;;
 show)
 	shift
-	show_stash "$@"
+	git stash--helper show "$@"
 	;;
 save)
 	shift
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 17/27] stash: convert store to builtin
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (15 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 16/27] stash: convert show " Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 18/27] stash: convert create " Thomas Gummerer
                                     ` (13 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

Add stash store to the helper and delete the store_stash function
from the shell script.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 builtin/stash--helper.c | 62 +++++++++++++++++++++++++++++++++++++++++
 git-stash.sh            | 43 ++--------------------------
 2 files changed, 64 insertions(+), 41 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 4028b64314..763a5ffd25 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -58,6 +58,11 @@ static const char * const git_stash_helper_clear_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_store_usage[] = {
+	N_("git stash--helper store [-m|--message <message>] [-q|--quiet] <commit>"),
+	NULL
+};
+
 static const char *ref_stash = "refs/stash";
 static struct strbuf stash_index_path = STRBUF_INIT;
 
@@ -729,6 +734,61 @@ static int show_stash(int argc, const char **argv, const char *prefix)
 	return diff_result_code(&rev.diffopt, 0);
 }
 
+static int do_store_stash(const struct object_id *w_commit, const char *stash_msg,
+			  int quiet)
+{
+	if (!stash_msg)
+		stash_msg = "Created via \"git stash store\".";
+
+	if (update_ref(stash_msg, ref_stash, w_commit, NULL,
+		       REF_FORCE_CREATE_REFLOG,
+		       quiet ? UPDATE_REFS_QUIET_ON_ERR :
+		       UPDATE_REFS_MSG_ON_ERR)) {
+		if (!quiet) {
+			fprintf_ln(stderr, _("Cannot update %s with %s"),
+				   ref_stash, oid_to_hex(w_commit));
+		}
+		return -1;
+	}
+
+	return 0;
+}
+
+static int store_stash(int argc, const char **argv, const char *prefix)
+{
+	int quiet = 0;
+	const char *stash_msg = NULL;
+	struct object_id obj;
+	struct object_context dummy;
+	struct option options[] = {
+		OPT__QUIET(&quiet, N_("be quiet")),
+		OPT_STRING('m', "message", &stash_msg, "message",
+			   N_("stash message")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_store_usage,
+			     PARSE_OPT_KEEP_UNKNOWN);
+
+	if (argc != 1) {
+		if (!quiet)
+			fprintf_ln(stderr, _("\"git stash store\" requires one "
+					     "<commit> argument"));
+		return -1;
+	}
+
+	if (get_oid_with_context(argv[0], quiet ? GET_OID_QUIETLY : 0, &obj,
+				 &dummy)) {
+		if (!quiet)
+			fprintf_ln(stderr, _("Cannot update %s with %s"),
+					     ref_stash, argv[0]);
+		return -1;
+	}
+
+	return do_store_stash(&obj, stash_msg, quiet);
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -763,6 +823,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!list_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "show"))
 		return !!show_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "store"))
+		return !!store_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index d0318f859e..ff5556ccb0 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -208,45 +208,6 @@ create_stash () {
 	die "$(gettext "Cannot record working tree state")"
 }
 
-store_stash () {
-	while test $# != 0
-	do
-		case "$1" in
-		-m|--message)
-			shift
-			stash_msg="$1"
-			;;
-		-m*)
-			stash_msg=${1#-m}
-			;;
-		--message=*)
-			stash_msg=${1#--message=}
-			;;
-		-q|--quiet)
-			quiet=t
-			;;
-		*)
-			break
-			;;
-		esac
-		shift
-	done
-	test $# = 1 ||
-	die "$(eval_gettext "\"$dashless store\" requires one <commit> argument")"
-
-	w_commit="$1"
-	if test -z "$stash_msg"
-	then
-		stash_msg="Created via \"git stash store\"."
-	fi
-
-	git update-ref --create-reflog -m "$stash_msg" $ref_stash $w_commit
-	ret=$?
-	test $ret != 0 && test -z "$quiet" &&
-	die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")"
-	return $ret
-}
-
 push_stash () {
 	keep_index=
 	patch_mode=
@@ -325,7 +286,7 @@ push_stash () {
 		clear_stash || die "$(gettext "Cannot initialize stash")"
 
 	create_stash -m "$stash_msg" -u "$untracked" -- "$@"
-	store_stash -m "$stash_msg" -q $w_commit ||
+	git stash--helper store -m "$stash_msg" -q $w_commit ||
 	die "$(gettext "Cannot save the current status")"
 	say "$(eval_gettext "Saved working directory and index state \$stash_msg")"
 
@@ -485,7 +446,7 @@ create)
 	;;
 store)
 	shift
-	store_stash "$@"
+	git stash--helper store "$@"
 	;;
 drop)
 	shift
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 18/27] stash: convert create to builtin
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (16 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 17/27] stash: convert store " Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-03-07 19:18                     ` Jeff King
  2019-02-25 23:16                   ` [PATCH v13 19/27] stash: convert push to builtin Thomas Gummerer
                                     ` (12 subsequent siblings)
  30 siblings, 1 reply; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Matthew Kraai, Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

Add stash create to the helper.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Helped-by: Matthew Kraai <mkraai@its.jnj.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 builtin/stash--helper.c | 449 +++++++++++++++++++++++++++++++++++++++-
 git-stash.sh            |   2 +-
 2 files changed, 449 insertions(+), 2 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 763a5ffd25..56864c126b 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -12,6 +12,9 @@
 #include "rerere.h"
 #include "revision.h"
 #include "log-tree.h"
+#include "diffcore.h"
+
+#define INCLUDE_ALL_FILES 2
 
 static const char * const git_stash_helper_usage[] = {
 	N_("git stash--helper list [<options>]"),
@@ -63,6 +66,11 @@ static const char * const git_stash_helper_store_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_create_usage[] = {
+	N_("git stash--helper create [<message>]"),
+	NULL
+};
+
 static const char *ref_stash = "refs/stash";
 static struct strbuf stash_index_path = STRBUF_INIT;
 
@@ -287,6 +295,20 @@ static int reset_head(void)
 	return run_command(&cp);
 }
 
+static void add_diff_to_buf(struct diff_queue_struct *q,
+			    struct diff_options *options,
+			    void *data)
+{
+	int i;
+
+	for (i = 0; i < q->nr; i++) {
+		strbuf_addstr(data, q->queue[i]->one->path);
+
+		/* NUL-terminate: will be fed to update-index -z */
+		strbuf_addch(data, '\0');
+	}
+}
+
 static int get_newly_staged(struct strbuf *out, struct object_id *c_tree)
 {
 	struct child_process cp = CHILD_PROCESS_INIT;
@@ -789,6 +811,429 @@ static int store_stash(int argc, const char **argv, const char *prefix)
 	return do_store_stash(&obj, stash_msg, quiet);
 }
 
+static void add_pathspecs(struct argv_array *args,
+			  struct pathspec ps) {
+	int i;
+
+	for (i = 0; i < ps.nr; i++)
+		argv_array_push(args, ps.items[i].match);
+}
+
+/*
+ * `untracked_files` will be filled with the names of untracked files.
+ * The return value is:
+ *
+ * = 0 if there are not any untracked files
+ * > 0 if there are untracked files
+ */
+static int get_untracked_files(struct pathspec ps, int include_untracked,
+			       struct strbuf *untracked_files)
+{
+	int i;
+	int max_len;
+	int found = 0;
+	char *seen;
+	struct dir_struct dir;
+
+	memset(&dir, 0, sizeof(dir));
+	if (include_untracked != INCLUDE_ALL_FILES)
+		setup_standard_excludes(&dir);
+
+	seen = xcalloc(ps.nr, 1);
+
+	max_len = fill_directory(&dir, the_repository->index, &ps);
+	for (i = 0; i < dir.nr; i++) {
+		struct dir_entry *ent = dir.entries[i];
+		if (dir_path_match(&the_index, ent, &ps, max_len, seen)) {
+			found++;
+			strbuf_addstr(untracked_files, ent->name);
+			/* NUL-terminate: will be fed to update-index -z */
+			strbuf_addch(untracked_files, '\0');
+		}
+		free(ent);
+	}
+
+	free(seen);
+	free(dir.entries);
+	free(dir.ignored);
+	clear_directory(&dir);
+	return found;
+}
+
+/*
+ * The return value of `check_changes()` can be:
+ *
+ * < 0 if there was an error
+ * = 0 if there are no changes.
+ * > 0 if there are changes.
+ */
+static int check_changes(struct pathspec ps, int include_untracked)
+{
+	int result;
+	struct rev_info rev;
+	struct object_id dummy;
+	struct strbuf out = STRBUF_INIT;
+
+	/* No initial commit. */
+	if (get_oid("HEAD", &dummy))
+		return -1;
+
+	if (read_cache() < 0)
+		return -1;
+
+	init_revisions(&rev, NULL);
+	rev.prune_data = ps;
+
+	rev.diffopt.flags.quick = 1;
+	rev.diffopt.flags.ignore_submodules = 1;
+	rev.abbrev = 0;
+
+	add_head_to_pending(&rev);
+	diff_setup_done(&rev.diffopt);
+
+	result = run_diff_index(&rev, 1);
+	if (diff_result_code(&rev.diffopt, result))
+		return 1;
+
+	object_array_clear(&rev.pending);
+	result = run_diff_files(&rev, 0);
+	if (diff_result_code(&rev.diffopt, result))
+		return 1;
+
+	if (include_untracked && get_untracked_files(ps, include_untracked,
+						     &out)) {
+		strbuf_release(&out);
+		return 1;
+	}
+
+	strbuf_release(&out);
+	return 0;
+}
+
+static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
+				struct strbuf files)
+{
+	int ret = 0;
+	struct strbuf untracked_msg = STRBUF_INIT;
+	struct strbuf out = STRBUF_INIT;
+	struct child_process cp_upd_index = CHILD_PROCESS_INIT;
+	struct child_process cp_write_tree = CHILD_PROCESS_INIT;
+
+	cp_upd_index.git_cmd = 1;
+	argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add",
+			 "--remove", "--stdin", NULL);
+	argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+
+	strbuf_addf(&untracked_msg, "untracked files on %s\n", msg->buf);
+	if (pipe_command(&cp_upd_index, files.buf, files.len, NULL, 0,
+			 NULL, 0)) {
+		ret = -1;
+		goto done;
+	}
+
+	cp_write_tree.git_cmd = 1;
+	argv_array_push(&cp_write_tree.args, "write-tree");
+	argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+	if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) {
+		ret = -1;
+		goto done;
+	}
+	get_oid_hex(out.buf, &info->u_tree);
+
+	if (commit_tree(untracked_msg.buf, untracked_msg.len,
+			&info->u_tree, NULL, &info->u_commit, NULL, NULL)) {
+		ret = -1;
+		goto done;
+	}
+
+done:
+	strbuf_release(&untracked_msg);
+	strbuf_release(&out);
+	remove_path(stash_index_path.buf);
+	return ret;
+}
+
+static int stash_patch(struct stash_info *info, struct pathspec ps,
+		       struct strbuf *out_patch)
+{
+	int ret = 0;
+	struct strbuf out = STRBUF_INIT;
+	struct child_process cp_read_tree = CHILD_PROCESS_INIT;
+	struct child_process cp_add_i = CHILD_PROCESS_INIT;
+	struct child_process cp_write_tree = CHILD_PROCESS_INIT;
+	struct child_process cp_diff_tree = CHILD_PROCESS_INIT;
+
+	remove_path(stash_index_path.buf);
+
+	cp_read_tree.git_cmd = 1;
+	argv_array_pushl(&cp_read_tree.args, "read-tree", "HEAD", NULL);
+	argv_array_pushf(&cp_read_tree.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+	if (run_command(&cp_read_tree)) {
+		ret = -1;
+		goto done;
+	}
+
+	/* Find out what the user wants. */
+	cp_add_i.git_cmd = 1;
+	argv_array_pushl(&cp_add_i.args, "add--interactive", "--patch=stash",
+			 "--", NULL);
+	add_pathspecs(&cp_add_i.args, ps);
+	argv_array_pushf(&cp_add_i.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+	if (run_command(&cp_add_i)) {
+		ret = -1;
+		goto done;
+	}
+
+	/* State of the working tree. */
+	cp_write_tree.git_cmd = 1;
+	argv_array_push(&cp_write_tree.args, "write-tree");
+	argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+	if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) {
+		ret = -1;
+		goto done;
+	}
+
+	get_oid_hex(out.buf, &info->w_tree);
+
+	cp_diff_tree.git_cmd = 1;
+	argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD",
+			 oid_to_hex(&info->w_tree), "--", NULL);
+	if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {
+		ret = -1;
+		goto done;
+	}
+
+	if (!out_patch->len) {
+		fprintf_ln(stderr, _("No changes selected"));
+		ret = 1;
+	}
+
+done:
+	strbuf_release(&out);
+	remove_path(stash_index_path.buf);
+	return ret;
+}
+
+static int stash_working_tree(struct stash_info *info, struct pathspec ps)
+{
+	int ret = 0;
+	struct rev_info rev;
+	struct child_process cp_upd_index = CHILD_PROCESS_INIT;
+	struct child_process cp_write_tree = CHILD_PROCESS_INIT;
+	struct strbuf out = STRBUF_INIT;
+	struct strbuf diff_output = STRBUF_INIT;
+
+	init_revisions(&rev, NULL);
+
+	set_alternate_index_output(stash_index_path.buf);
+	if (reset_tree(&info->i_tree, 0, 0)) {
+		ret = -1;
+		goto done;
+	}
+	set_alternate_index_output(NULL);
+
+	rev.prune_data = ps;
+	rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+	rev.diffopt.format_callback = add_diff_to_buf;
+	rev.diffopt.format_callback_data = &diff_output;
+
+	if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
+		ret = -1;
+		goto done;
+	}
+
+	add_pending_object(&rev, parse_object(the_repository, &info->b_commit),
+			   "");
+	if (run_diff_index(&rev, 0)) {
+		ret = -1;
+		goto done;
+	}
+
+	cp_upd_index.git_cmd = 1;
+	argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add",
+			 "--remove", "--stdin", NULL);
+	argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+
+	if (pipe_command(&cp_upd_index, diff_output.buf, diff_output.len,
+			 NULL, 0, NULL, 0)) {
+		ret = -1;
+		goto done;
+	}
+
+	cp_write_tree.git_cmd = 1;
+	argv_array_push(&cp_write_tree.args, "write-tree");
+	argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s",
+			 stash_index_path.buf);
+	if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) {
+		ret = -1;
+		goto done;
+	}
+
+	get_oid_hex(out.buf, &info->w_tree);
+
+done:
+	UNLEAK(rev);
+	strbuf_release(&out);
+	object_array_clear(&rev.pending);
+	strbuf_release(&diff_output);
+	remove_path(stash_index_path.buf);
+	return ret;
+}
+
+static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
+			   int include_untracked, int patch_mode,
+			   struct stash_info *info)
+{
+	int ret = 0;
+	int flags = 0;
+	int untracked_commit_option = 0;
+	const char *head_short_sha1 = NULL;
+	const char *branch_ref = NULL;
+	const char *branch_name = "(no branch)";
+	struct commit *head_commit = NULL;
+	struct commit_list *parents = NULL;
+	struct strbuf msg = STRBUF_INIT;
+	struct strbuf commit_tree_label = STRBUF_INIT;
+	struct strbuf untracked_files = STRBUF_INIT;
+	struct strbuf patch = STRBUF_INIT;
+
+	prepare_fallback_ident("git stash", "git@stash");
+
+	read_cache_preload(NULL);
+	refresh_cache(REFRESH_QUIET);
+
+	if (get_oid("HEAD", &info->b_commit)) {
+		fprintf_ln(stderr, _("You do not have the initial commit yet"));
+		ret = -1;
+		goto done;
+	} else {
+		head_commit = lookup_commit(the_repository, &info->b_commit);
+	}
+
+	if (!check_changes(ps, include_untracked)) {
+		ret = 1;
+		goto done;
+	}
+
+	branch_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
+	if (flags & REF_ISSYMREF)
+		branch_name = strrchr(branch_ref, '/') + 1;
+	head_short_sha1 = find_unique_abbrev(&head_commit->object.oid,
+					     DEFAULT_ABBREV);
+	strbuf_addf(&msg, "%s: %s ", branch_name, head_short_sha1);
+	pp_commit_easy(CMIT_FMT_ONELINE, head_commit, &msg);
+
+	strbuf_addf(&commit_tree_label, "index on %s\n", msg.buf);
+	commit_list_insert(head_commit, &parents);
+	if (write_cache_as_tree(&info->i_tree, 0, NULL) ||
+	    commit_tree(commit_tree_label.buf, commit_tree_label.len,
+			&info->i_tree, parents, &info->i_commit, NULL, NULL)) {
+		fprintf_ln(stderr, _("Cannot save the current index state"));
+		ret = -1;
+		goto done;
+	}
+
+	if (include_untracked && get_untracked_files(ps, include_untracked,
+						     &untracked_files)) {
+		if (save_untracked_files(info, &msg, untracked_files)) {
+			fprintf_ln(stderr, _("Cannot save "
+					     "the untracked files"));
+			ret = -1;
+			goto done;
+		}
+		untracked_commit_option = 1;
+	}
+	if (patch_mode) {
+		ret = stash_patch(info, ps, &patch);
+		if (ret < 0) {
+			fprintf_ln(stderr, _("Cannot save the current "
+					     "worktree state"));
+			goto done;
+		} else if (ret > 0) {
+			goto done;
+		}
+	} else {
+		if (stash_working_tree(info, ps)) {
+			fprintf_ln(stderr, _("Cannot save the current "
+					     "worktree state"));
+			ret = -1;
+			goto done;
+		}
+	}
+
+	if (!stash_msg_buf->len)
+		strbuf_addf(stash_msg_buf, "WIP on %s", msg.buf);
+	else
+		strbuf_insertf(stash_msg_buf, 0, "On %s: ", branch_name);
+
+	/*
+	 * `parents` will be empty after calling `commit_tree()`, so there is
+	 * no need to call `free_commit_list()`
+	 */
+	parents = NULL;
+	if (untracked_commit_option)
+		commit_list_insert(lookup_commit(the_repository,
+						 &info->u_commit),
+				   &parents);
+	commit_list_insert(lookup_commit(the_repository, &info->i_commit),
+			   &parents);
+	commit_list_insert(head_commit, &parents);
+
+	if (commit_tree(stash_msg_buf->buf, stash_msg_buf->len, &info->w_tree,
+			parents, &info->w_commit, NULL, NULL)) {
+		fprintf_ln(stderr, _("Cannot record working tree state"));
+		ret = -1;
+		goto done;
+	}
+
+done:
+	strbuf_release(&commit_tree_label);
+	strbuf_release(&msg);
+	strbuf_release(&untracked_files);
+	return ret;
+}
+
+static int create_stash(int argc, const char **argv, const char *prefix)
+{
+	int include_untracked = 0;
+	int ret = 0;
+	const char *stash_msg = NULL;
+	struct strbuf stash_msg_buf = STRBUF_INIT;
+	struct stash_info info;
+	struct pathspec ps;
+	struct option options[] = {
+		OPT_BOOL('u', "include-untracked", &include_untracked,
+			 N_("include untracked files in stash")),
+		OPT_STRING('m', "message", &stash_msg, N_("message"),
+			 N_("stash message")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_create_usage,
+			     0);
+
+	memset(&ps, 0, sizeof(ps));
+	strbuf_addstr(&stash_msg_buf, stash_msg);
+	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info);
+	if (!ret)
+		printf_ln("%s", oid_to_hex(&info.w_commit));
+
+	strbuf_release(&stash_msg_buf);
+
+	/*
+	 * ret can be 1 if there were no changes. In this case, we should
+	 * not error out.
+	 */
+	return ret < 0;
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -798,7 +1243,7 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
-	git_config(git_default_config, NULL);
+	git_config(git_diff_basic_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, options, git_stash_helper_usage,
 			     PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
@@ -825,6 +1270,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!show_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "store"))
 		return !!store_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "create"))
+		return !!create_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index ff5556ccb0..a9b3064ff0 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -442,7 +442,7 @@ clear)
 	;;
 create)
 	shift
-	create_stash -m "$*" && echo "$w_commit"
+	git stash--helper create --message "$*"
 	;;
 store)
 	shift
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 19/27] stash: convert push to builtin
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (17 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 18/27] stash: convert create " Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 20/27] stash: make push -q quiet Thomas Gummerer
                                     ` (11 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

Add stash push to the helper.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 builtin/stash--helper.c | 246 +++++++++++++++++++++++++++++++++++++++-
 git-stash.sh            |   6 +-
 2 files changed, 246 insertions(+), 6 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 56864c126b..30c7aad76a 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -23,6 +23,9 @@ static const char * const git_stash_helper_usage[] = {
 	N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
 	N_("git stash--helper branch <branchname> [<stash>]"),
 	N_("git stash--helper clear"),
+	N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+	   "          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
+	   "          [--] [<pathspec>...]]"),
 	NULL
 };
 
@@ -71,6 +74,13 @@ static const char * const git_stash_helper_create_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_push_usage[] = {
+	N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+	   "          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
+	   "          [--] [<pathspec>...]]"),
+	NULL
+};
+
 static const char *ref_stash = "refs/stash";
 static struct strbuf stash_index_path = STRBUF_INIT;
 
@@ -1088,7 +1098,7 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
 
 static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 			   int include_untracked, int patch_mode,
-			   struct stash_info *info)
+			   struct stash_info *info, struct strbuf *patch)
 {
 	int ret = 0;
 	int flags = 0;
@@ -1101,7 +1111,6 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 	struct strbuf msg = STRBUF_INIT;
 	struct strbuf commit_tree_label = STRBUF_INIT;
 	struct strbuf untracked_files = STRBUF_INIT;
-	struct strbuf patch = STRBUF_INIT;
 
 	prepare_fallback_ident("git stash", "git@stash");
 
@@ -1150,7 +1159,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 		untracked_commit_option = 1;
 	}
 	if (patch_mode) {
-		ret = stash_patch(info, ps, &patch);
+		ret = stash_patch(info, ps, patch);
 		if (ret < 0) {
 			fprintf_ln(stderr, _("Cannot save the current "
 					     "worktree state"));
@@ -1221,7 +1230,8 @@ static int create_stash(int argc, const char **argv, const char *prefix)
 
 	memset(&ps, 0, sizeof(ps));
 	strbuf_addstr(&stash_msg_buf, stash_msg);
-	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info);
+	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
+			      NULL);
 	if (!ret)
 		printf_ln("%s", oid_to_hex(&info.w_commit));
 
@@ -1234,6 +1244,232 @@ static int create_stash(int argc, const char **argv, const char *prefix)
 	return ret < 0;
 }
 
+static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
+			 int keep_index, int patch_mode, int include_untracked)
+{
+	int ret = 0;
+	struct stash_info info;
+	struct strbuf patch = STRBUF_INIT;
+	struct strbuf stash_msg_buf = STRBUF_INIT;
+
+	if (patch_mode && keep_index == -1)
+		keep_index = 1;
+
+	if (patch_mode && include_untracked) {
+		fprintf_ln(stderr, _("Can't use --patch and --include-untracked"
+				     " or --all at the same time"));
+		ret = -1;
+		goto done;
+	}
+
+	read_cache_preload(NULL);
+	if (!include_untracked && ps.nr) {
+		int i;
+		char *ps_matched = xcalloc(ps.nr, 1);
+
+		for (i = 0; i < active_nr; i++)
+			ce_path_match(&the_index, active_cache[i], &ps,
+				      ps_matched);
+
+		if (report_path_error(ps_matched, &ps, NULL)) {
+			fprintf_ln(stderr, _("Did you forget to 'git add'?"));
+			ret = -1;
+			free(ps_matched);
+			goto done;
+		}
+		free(ps_matched);
+	}
+
+	if (refresh_cache(REFRESH_QUIET)) {
+		ret = -1;
+		goto done;
+	}
+
+	if (!check_changes(ps, include_untracked)) {
+		if (!quiet)
+			printf_ln(_("No local changes to save"));
+		goto done;
+	}
+
+	if (!reflog_exists(ref_stash) && do_clear_stash()) {
+		ret = -1;
+		fprintf_ln(stderr, _("Cannot initialize stash"));
+		goto done;
+	}
+
+	if (stash_msg)
+		strbuf_addstr(&stash_msg_buf, stash_msg);
+	if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,
+			    &info, &patch)) {
+		ret = -1;
+		goto done;
+	}
+
+	if (do_store_stash(&info.w_commit, stash_msg_buf.buf, 1)) {
+		ret = -1;
+		fprintf_ln(stderr, _("Cannot save the current status"));
+		goto done;
+	}
+
+	printf_ln(_("Saved working directory and index state %s"),
+		  stash_msg_buf.buf);
+
+	if (!patch_mode) {
+		if (include_untracked && !ps.nr) {
+			struct child_process cp = CHILD_PROCESS_INIT;
+
+			cp.git_cmd = 1;
+			argv_array_pushl(&cp.args, "clean", "--force",
+					 "--quiet", "-d", NULL);
+			if (include_untracked == INCLUDE_ALL_FILES)
+				argv_array_push(&cp.args, "-x");
+			if (run_command(&cp)) {
+				ret = -1;
+				goto done;
+			}
+		}
+		discard_cache();
+		if (ps.nr) {
+			struct child_process cp_add = CHILD_PROCESS_INIT;
+			struct child_process cp_diff = CHILD_PROCESS_INIT;
+			struct child_process cp_apply = CHILD_PROCESS_INIT;
+			struct strbuf out = STRBUF_INIT;
+
+			cp_add.git_cmd = 1;
+			argv_array_push(&cp_add.args, "add");
+			if (!include_untracked)
+				argv_array_push(&cp_add.args, "-u");
+			if (include_untracked == INCLUDE_ALL_FILES)
+				argv_array_push(&cp_add.args, "--force");
+			argv_array_push(&cp_add.args, "--");
+			add_pathspecs(&cp_add.args, ps);
+			if (run_command(&cp_add)) {
+				ret = -1;
+				goto done;
+			}
+
+			cp_diff.git_cmd = 1;
+			argv_array_pushl(&cp_diff.args, "diff-index", "-p",
+					 "--cached", "--binary", "HEAD", "--",
+					 NULL);
+			add_pathspecs(&cp_diff.args, ps);
+			if (pipe_command(&cp_diff, NULL, 0, &out, 0, NULL, 0)) {
+				ret = -1;
+				goto done;
+			}
+
+			cp_apply.git_cmd = 1;
+			argv_array_pushl(&cp_apply.args, "apply", "--index",
+					 "-R", NULL);
+			if (pipe_command(&cp_apply, out.buf, out.len, NULL, 0,
+					 NULL, 0)) {
+				ret = -1;
+				goto done;
+			}
+		} else {
+			struct child_process cp = CHILD_PROCESS_INIT;
+			cp.git_cmd = 1;
+			argv_array_pushl(&cp.args, "reset", "--hard", "-q",
+					 NULL);
+			if (run_command(&cp)) {
+				ret = -1;
+				goto done;
+			}
+		}
+
+		if (keep_index == 1 && !is_null_oid(&info.i_tree)) {
+			struct child_process cp_ls = CHILD_PROCESS_INIT;
+			struct child_process cp_checkout = CHILD_PROCESS_INIT;
+			struct strbuf out = STRBUF_INIT;
+
+			if (reset_tree(&info.i_tree, 0, 1)) {
+				ret = -1;
+				goto done;
+			}
+
+			cp_ls.git_cmd = 1;
+			argv_array_pushl(&cp_ls.args, "ls-files", "-z",
+					 "--modified", "--", NULL);
+
+			add_pathspecs(&cp_ls.args, ps);
+			if (pipe_command(&cp_ls, NULL, 0, &out, 0, NULL, 0)) {
+				ret = -1;
+				goto done;
+			}
+
+			cp_checkout.git_cmd = 1;
+			argv_array_pushl(&cp_checkout.args, "checkout-index",
+					 "-z", "--force", "--stdin", NULL);
+			if (pipe_command(&cp_checkout, out.buf, out.len, NULL,
+					 0, NULL, 0)) {
+				ret = -1;
+				goto done;
+			}
+		}
+		goto done;
+	} else {
+		struct child_process cp = CHILD_PROCESS_INIT;
+
+		cp.git_cmd = 1;
+		argv_array_pushl(&cp.args, "apply", "-R", NULL);
+
+		if (pipe_command(&cp, patch.buf, patch.len, NULL, 0, NULL, 0)) {
+			fprintf_ln(stderr, _("Cannot remove worktree changes"));
+			ret = -1;
+			goto done;
+		}
+
+		if (keep_index < 1) {
+			struct child_process cp = CHILD_PROCESS_INIT;
+
+			cp.git_cmd = 1;
+			argv_array_pushl(&cp.args, "reset", "-q", "--", NULL);
+			add_pathspecs(&cp.args, ps);
+			if (run_command(&cp)) {
+				ret = -1;
+				goto done;
+			}
+		}
+		goto done;
+	}
+
+done:
+	strbuf_release(&stash_msg_buf);
+	return ret;
+}
+
+static int push_stash(int argc, const char **argv, const char *prefix)
+{
+	int keep_index = -1;
+	int patch_mode = 0;
+	int include_untracked = 0;
+	int quiet = 0;
+	const char *stash_msg = NULL;
+	struct pathspec ps;
+	struct option options[] = {
+		OPT_BOOL('k', "keep-index", &keep_index,
+			 N_("keep index")),
+		OPT_BOOL('p', "patch", &patch_mode,
+			 N_("stash in patch mode")),
+		OPT__QUIET(&quiet, N_("quiet mode")),
+		OPT_BOOL('u', "include-untracked", &include_untracked,
+			 N_("include untracked files in stash")),
+		OPT_SET_INT('a', "all", &include_untracked,
+			    N_("include ignore files"), 2),
+		OPT_STRING('m', "message", &stash_msg, N_("message"),
+			   N_("stash message")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_push_usage,
+			     0);
+
+	parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL, prefix, argv);
+	return do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode,
+			     include_untracked);
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -1272,6 +1508,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!store_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "create"))
 		return !!create_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "push"))
+		return !!push_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index a9b3064ff0..51d7a06601 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -429,7 +429,8 @@ save)
 	;;
 push)
 	shift
-	push_stash "$@"
+	cd "$START_DIR"
+	git stash--helper push "$@"
 	;;
 apply)
 	shift
@@ -465,7 +466,8 @@ branch)
 *)
 	case $# in
 	0)
-		push_stash &&
+		cd "$START_DIR"
+		git stash--helper push &&
 		say "$(gettext "(To restore them type \"git stash apply\")")"
 		;;
 	*)
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 20/27] stash: make push -q quiet
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (18 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 19/27] stash: convert push to builtin Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 21/27] stash: convert save to builtin Thomas Gummerer
                                     ` (10 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

There is a change in behaviour with this commit. When there was
no initial commit, the shell version of stash would still display
a message. This commit makes `push` to not display any message if
`--quiet` or `-q` is specified. Add tests for `--quiet`.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 builtin/stash--helper.c | 56 ++++++++++++++++++++++++++---------------
 t/t3903-stash.sh        | 23 +++++++++++++++++
 2 files changed, 59 insertions(+), 20 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 30c7aad76a..1c61352093 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -966,7 +966,7 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
 }
 
 static int stash_patch(struct stash_info *info, struct pathspec ps,
-		       struct strbuf *out_patch)
+		       struct strbuf *out_patch, int quiet)
 {
 	int ret = 0;
 	struct strbuf out = STRBUF_INIT;
@@ -1019,7 +1019,8 @@ static int stash_patch(struct stash_info *info, struct pathspec ps,
 	}
 
 	if (!out_patch->len) {
-		fprintf_ln(stderr, _("No changes selected"));
+		if (!quiet)
+			fprintf_ln(stderr, _("No changes selected"));
 		ret = 1;
 	}
 
@@ -1098,7 +1099,8 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
 
 static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 			   int include_untracked, int patch_mode,
-			   struct stash_info *info, struct strbuf *patch)
+			   struct stash_info *info, struct strbuf *patch,
+			   int quiet)
 {
 	int ret = 0;
 	int flags = 0;
@@ -1118,7 +1120,9 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 	refresh_cache(REFRESH_QUIET);
 
 	if (get_oid("HEAD", &info->b_commit)) {
-		fprintf_ln(stderr, _("You do not have the initial commit yet"));
+		if (!quiet)
+			fprintf_ln(stderr, _("You do not have "
+					     "the initial commit yet"));
 		ret = -1;
 		goto done;
 	} else {
@@ -1143,7 +1147,9 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 	if (write_cache_as_tree(&info->i_tree, 0, NULL) ||
 	    commit_tree(commit_tree_label.buf, commit_tree_label.len,
 			&info->i_tree, parents, &info->i_commit, NULL, NULL)) {
-		fprintf_ln(stderr, _("Cannot save the current index state"));
+		if (!quiet)
+			fprintf_ln(stderr, _("Cannot save the current "
+					     "index state"));
 		ret = -1;
 		goto done;
 	}
@@ -1151,26 +1157,29 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 	if (include_untracked && get_untracked_files(ps, include_untracked,
 						     &untracked_files)) {
 		if (save_untracked_files(info, &msg, untracked_files)) {
-			fprintf_ln(stderr, _("Cannot save "
-					     "the untracked files"));
+			if (!quiet)
+				fprintf_ln(stderr, _("Cannot save "
+						     "the untracked files"));
 			ret = -1;
 			goto done;
 		}
 		untracked_commit_option = 1;
 	}
 	if (patch_mode) {
-		ret = stash_patch(info, ps, patch);
+		ret = stash_patch(info, ps, patch, quiet);
 		if (ret < 0) {
-			fprintf_ln(stderr, _("Cannot save the current "
-					     "worktree state"));
+			if (!quiet)
+				fprintf_ln(stderr, _("Cannot save the current "
+						     "worktree state"));
 			goto done;
 		} else if (ret > 0) {
 			goto done;
 		}
 	} else {
 		if (stash_working_tree(info, ps)) {
-			fprintf_ln(stderr, _("Cannot save the current "
-					     "worktree state"));
+			if (!quiet)
+				fprintf_ln(stderr, _("Cannot save the current "
+						     "worktree state"));
 			ret = -1;
 			goto done;
 		}
@@ -1196,7 +1205,9 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 
 	if (commit_tree(stash_msg_buf->buf, stash_msg_buf->len, &info->w_tree,
 			parents, &info->w_commit, NULL, NULL)) {
-		fprintf_ln(stderr, _("Cannot record working tree state"));
+		if (!quiet)
+			fprintf_ln(stderr, _("Cannot record "
+					     "working tree state"));
 		ret = -1;
 		goto done;
 	}
@@ -1231,7 +1242,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)
 	memset(&ps, 0, sizeof(ps));
 	strbuf_addstr(&stash_msg_buf, stash_msg);
 	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
-			      NULL);
+			      NULL, 0);
 	if (!ret)
 		printf_ln("%s", oid_to_hex(&info.w_commit));
 
@@ -1293,26 +1304,29 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
 
 	if (!reflog_exists(ref_stash) && do_clear_stash()) {
 		ret = -1;
-		fprintf_ln(stderr, _("Cannot initialize stash"));
+		if (!quiet)
+			fprintf_ln(stderr, _("Cannot initialize stash"));
 		goto done;
 	}
 
 	if (stash_msg)
 		strbuf_addstr(&stash_msg_buf, stash_msg);
 	if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,
-			    &info, &patch)) {
+			    &info, &patch, quiet)) {
 		ret = -1;
 		goto done;
 	}
 
 	if (do_store_stash(&info.w_commit, stash_msg_buf.buf, 1)) {
 		ret = -1;
-		fprintf_ln(stderr, _("Cannot save the current status"));
+		if (!quiet)
+			fprintf_ln(stderr, _("Cannot save the current status"));
 		goto done;
 	}
 
-	printf_ln(_("Saved working directory and index state %s"),
-		  stash_msg_buf.buf);
+	if (!quiet)
+		printf_ln(_("Saved working directory and index state %s"),
+			  stash_msg_buf.buf);
 
 	if (!patch_mode) {
 		if (include_untracked && !ps.nr) {
@@ -1414,7 +1428,9 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
 		argv_array_pushl(&cp.args, "apply", "-R", NULL);
 
 		if (pipe_command(&cp, patch.buf, patch.len, NULL, 0, NULL, 0)) {
-			fprintf_ln(stderr, _("Cannot remove worktree changes"));
+			if (!quiet)
+				fprintf_ln(stderr, _("Cannot remove "
+						     "worktree changes"));
 			ret = -1;
 			goto done;
 		}
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 023421a36c..7dfa3a8038 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -1072,6 +1072,29 @@ test_expect_success 'push: <pathspec> not in the repository errors out' '
 	test_path_is_file untracked
 '
 
+test_expect_success 'push: -q is quiet with changes' '
+	>foo &&
+	git add foo &&
+	git stash push -q >output 2>&1 &&
+	test_must_be_empty output
+'
+
+test_expect_success 'push: -q is quiet with no changes' '
+	git stash push -q >output 2>&1 &&
+	test_must_be_empty output
+'
+
+test_expect_success 'push: -q is quiet even if there is no initial commit' '
+	git init foo_dir &&
+	test_when_finished rm -rf foo_dir &&
+	(
+		cd foo_dir &&
+		>bar &&
+		test_must_fail git stash push -q >output 2>&1 &&
+		test_must_be_empty output
+	)
+'
+
 test_expect_success 'untracked files are left in place when -u is not given' '
 	>file &&
 	git add file &&
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 21/27] stash: convert save to builtin
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (19 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 20/27] stash: make push -q quiet Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 22/27] stash: optimize `get_untracked_files()` and `check_changes()` Thomas Gummerer
                                     ` (9 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

Add stash save to the helper and delete functions which are no
longer needed (`show_help()`, `save_stash()`, `push_stash()`,
`create_stash()`, `clear_stash()`, `untracked_files()` and
`no_changes()`).

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 builtin/stash--helper.c |  50 ++++++
 git-stash.sh            | 328 +---------------------------------------
 2 files changed, 52 insertions(+), 326 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 1c61352093..8e4e96e353 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -26,6 +26,8 @@ static const char * const git_stash_helper_usage[] = {
 	N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
 	   "          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
 	   "          [--] [<pathspec>...]]"),
+	N_("git stash--helper save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+	   "          [-u|--include-untracked] [-a|--all] [<message>]"),
 	NULL
 };
 
@@ -81,6 +83,12 @@ static const char * const git_stash_helper_push_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_helper_save_usage[] = {
+	N_("git stash--helper save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+	   "          [-u|--include-untracked] [-a|--all] [<message>]"),
+	NULL
+};
+
 static const char *ref_stash = "refs/stash";
 static struct strbuf stash_index_path = STRBUF_INIT;
 
@@ -1486,6 +1494,46 @@ static int push_stash(int argc, const char **argv, const char *prefix)
 			     include_untracked);
 }
 
+static int save_stash(int argc, const char **argv, const char *prefix)
+{
+	int keep_index = -1;
+	int patch_mode = 0;
+	int include_untracked = 0;
+	int quiet = 0;
+	int ret = 0;
+	const char *stash_msg = NULL;
+	struct pathspec ps;
+	struct strbuf stash_msg_buf = STRBUF_INIT;
+	struct option options[] = {
+		OPT_BOOL('k', "keep-index", &keep_index,
+			 N_("keep index")),
+		OPT_BOOL('p', "patch", &patch_mode,
+			 N_("stash in patch mode")),
+		OPT__QUIET(&quiet, N_("quiet mode")),
+		OPT_BOOL('u', "include-untracked", &include_untracked,
+			 N_("include untracked files in stash")),
+		OPT_SET_INT('a', "all", &include_untracked,
+			    N_("include ignore files"), 2),
+		OPT_STRING('m', "message", &stash_msg, "message",
+			   N_("stash message")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_helper_save_usage,
+			     PARSE_OPT_KEEP_DASHDASH);
+
+	if (argc)
+		stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');
+
+	memset(&ps, 0, sizeof(ps));
+	ret = do_push_stash(ps, stash_msg, quiet, keep_index,
+			    patch_mode, include_untracked);
+
+	strbuf_release(&stash_msg_buf);
+	return ret;
+}
+
 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 {
 	pid_t pid = getpid();
@@ -1526,6 +1574,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!create_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "push"))
 		return !!push_stash(argc, argv, prefix);
+	else if (!strcmp(argv[0], "save"))
+		return !!save_stash(argc, argv, prefix);
 
 	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
 		      git_stash_helper_usage, options);
diff --git a/git-stash.sh b/git-stash.sh
index 51d7a06601..695f1feba3 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -36,331 +36,6 @@ else
        reset_color=
 fi
 
-no_changes () {
-	git diff-index --quiet --cached HEAD --ignore-submodules -- "$@" &&
-	git diff-files --quiet --ignore-submodules -- "$@" &&
-	(test -z "$untracked" || test -z "$(untracked_files "$@")")
-}
-
-untracked_files () {
-	if test "$1" = "-z"
-	then
-		shift
-		z=-z
-	else
-		z=
-	fi
-	excl_opt=--exclude-standard
-	test "$untracked" = "all" && excl_opt=
-	git ls-files -o $z $excl_opt -- "$@"
-}
-
-prepare_fallback_ident () {
-	if ! git -c user.useconfigonly=yes var GIT_COMMITTER_IDENT >/dev/null 2>&1
-	then
-		GIT_AUTHOR_NAME="git stash"
-		GIT_AUTHOR_EMAIL=git@stash
-		GIT_COMMITTER_NAME="git stash"
-		GIT_COMMITTER_EMAIL=git@stash
-		export GIT_AUTHOR_NAME
-		export GIT_AUTHOR_EMAIL
-		export GIT_COMMITTER_NAME
-		export GIT_COMMITTER_EMAIL
-	fi
-}
-
-clear_stash () {
-	if test $# != 0
-	then
-		die "$(gettext "git stash clear with parameters is unimplemented")"
-	fi
-	if current=$(git rev-parse --verify --quiet $ref_stash)
-	then
-		git update-ref -d $ref_stash $current
-	fi
-}
-
-create_stash () {
-
-	prepare_fallback_ident
-
-	stash_msg=
-	untracked=
-	while test $# != 0
-	do
-		case "$1" in
-		-m|--message)
-			shift
-			stash_msg=${1?"BUG: create_stash () -m requires an argument"}
-			;;
-		-m*)
-			stash_msg=${1#-m}
-			;;
-		--message=*)
-			stash_msg=${1#--message=}
-			;;
-		-u|--include-untracked)
-			shift
-			untracked=${1?"BUG: create_stash () -u requires an argument"}
-			;;
-		--)
-			shift
-			break
-			;;
-		esac
-		shift
-	done
-
-	git update-index -q --refresh
-	if no_changes "$@"
-	then
-		exit 0
-	fi
-
-	# state of the base commit
-	if b_commit=$(git rev-parse --verify HEAD)
-	then
-		head=$(git rev-list --oneline -n 1 HEAD --)
-	else
-		die "$(gettext "You do not have the initial commit yet")"
-	fi
-
-	if branch=$(git symbolic-ref -q HEAD)
-	then
-		branch=${branch#refs/heads/}
-	else
-		branch='(no branch)'
-	fi
-	msg=$(printf '%s: %s' "$branch" "$head")
-
-	# state of the index
-	i_tree=$(git write-tree) &&
-	i_commit=$(printf 'index on %s\n' "$msg" |
-		git commit-tree $i_tree -p $b_commit) ||
-		die "$(gettext "Cannot save the current index state")"
-
-	if test -n "$untracked"
-	then
-		# Untracked files are stored by themselves in a parentless commit, for
-		# ease of unpacking later.
-		u_commit=$(
-			untracked_files -z "$@" | (
-				GIT_INDEX_FILE="$TMPindex" &&
-				export GIT_INDEX_FILE &&
-				rm -f "$TMPindex" &&
-				git update-index -z --add --remove --stdin &&
-				u_tree=$(git write-tree) &&
-				printf 'untracked files on %s\n' "$msg" | git commit-tree $u_tree  &&
-				rm -f "$TMPindex"
-		) ) || die "$(gettext "Cannot save the untracked files")"
-
-		untracked_commit_option="-p $u_commit";
-	else
-		untracked_commit_option=
-	fi
-
-	if test -z "$patch_mode"
-	then
-
-		# state of the working tree
-		w_tree=$( (
-			git read-tree --index-output="$TMPindex" -m $i_tree &&
-			GIT_INDEX_FILE="$TMPindex" &&
-			export GIT_INDEX_FILE &&
-			git diff-index --name-only -z HEAD -- "$@" >"$TMP-stagenames" &&
-			git update-index -z --add --remove --stdin <"$TMP-stagenames" &&
-			git write-tree &&
-			rm -f "$TMPindex"
-		) ) ||
-			die "$(gettext "Cannot save the current worktree state")"
-
-	else
-
-		rm -f "$TMP-index" &&
-		GIT_INDEX_FILE="$TMP-index" git read-tree HEAD &&
-
-		# find out what the user wants
-		GIT_INDEX_FILE="$TMP-index" \
-			git add--interactive --patch=stash -- "$@" &&
-
-		# state of the working tree
-		w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
-		die "$(gettext "Cannot save the current worktree state")"
-
-		git diff-tree -p HEAD $w_tree -- >"$TMP-patch" &&
-		test -s "$TMP-patch" ||
-		die "$(gettext "No changes selected")"
-
-		rm -f "$TMP-index" ||
-		die "$(gettext "Cannot remove temporary index (can't happen)")"
-
-	fi
-
-	# create the stash
-	if test -z "$stash_msg"
-	then
-		stash_msg=$(printf 'WIP on %s' "$msg")
-	else
-		stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
-	fi
-	w_commit=$(printf '%s\n' "$stash_msg" |
-	git commit-tree $w_tree -p $b_commit -p $i_commit $untracked_commit_option) ||
-	die "$(gettext "Cannot record working tree state")"
-}
-
-push_stash () {
-	keep_index=
-	patch_mode=
-	untracked=
-	stash_msg=
-	while test $# != 0
-	do
-		case "$1" in
-		-k|--keep-index)
-			keep_index=t
-			;;
-		--no-keep-index)
-			keep_index=n
-			;;
-		-p|--patch)
-			patch_mode=t
-			# only default to keep if we don't already have an override
-			test -z "$keep_index" && keep_index=t
-			;;
-		-q|--quiet)
-			GIT_QUIET=t
-			;;
-		-u|--include-untracked)
-			untracked=untracked
-			;;
-		-a|--all)
-			untracked=all
-			;;
-		-m|--message)
-			shift
-			test -z ${1+x} && usage
-			stash_msg=$1
-			;;
-		-m*)
-			stash_msg=${1#-m}
-			;;
-		--message=*)
-			stash_msg=${1#--message=}
-			;;
-		--help)
-			show_help
-			;;
-		--)
-			shift
-			break
-			;;
-		-*)
-			option="$1"
-			eval_gettextln "error: unknown option for 'stash push': \$option"
-			usage
-			;;
-		*)
-			break
-			;;
-		esac
-		shift
-	done
-
-	eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")"
-
-	if test -n "$patch_mode" && test -n "$untracked"
-	then
-		die "$(gettext "Can't use --patch and --include-untracked or --all at the same time")"
-	fi
-
-	test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1
-
-	git update-index -q --refresh
-	if no_changes "$@"
-	then
-		say "$(gettext "No local changes to save")"
-		exit 0
-	fi
-
-	git reflog exists $ref_stash ||
-		clear_stash || die "$(gettext "Cannot initialize stash")"
-
-	create_stash -m "$stash_msg" -u "$untracked" -- "$@"
-	git stash--helper store -m "$stash_msg" -q $w_commit ||
-	die "$(gettext "Cannot save the current status")"
-	say "$(eval_gettext "Saved working directory and index state \$stash_msg")"
-
-	if test -z "$patch_mode"
-	then
-		test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
-		if test -n "$untracked" && test $# = 0
-		then
-			git clean --force --quiet -d $CLEAN_X_OPTION
-		fi
-
-		if test $# != 0
-		then
-			test -z "$untracked" && UPDATE_OPTION="-u" || UPDATE_OPTION=
-			test "$untracked" = "all" && FORCE_OPTION="--force" || FORCE_OPTION=
-			git add $UPDATE_OPTION $FORCE_OPTION -- "$@"
-			git diff-index -p --cached --binary HEAD -- "$@" |
-			git apply --index -R
-		else
-			git reset --hard -q
-		fi
-
-		if test "$keep_index" = "t" && test -n "$i_tree"
-		then
-			git read-tree --reset $i_tree
-			git ls-files -z --modified -- "$@" |
-			git checkout-index -z --force --stdin
-		fi
-	else
-		git apply -R < "$TMP-patch" ||
-		die "$(gettext "Cannot remove worktree changes")"
-
-		if test "$keep_index" != "t"
-		then
-			git reset -q -- "$@"
-		fi
-	fi
-}
-
-save_stash () {
-	push_options=
-	while test $# != 0
-	do
-		case "$1" in
-		--)
-			shift
-			break
-			;;
-		-*)
-			# pass all options through to push_stash
-			push_options="$push_options $1"
-			;;
-		*)
-			break
-			;;
-		esac
-		shift
-	done
-
-	stash_msg="$*"
-
-	if test -z "$stash_msg"
-	then
-		push_stash $push_options
-	else
-		push_stash $push_options -m "$stash_msg"
-	fi
-}
-
-show_help () {
-	exec git help stash
-	exit 1
-}
-
 #
 # Parses the remaining options looking for flags and
 # at most one revision defaulting to ${ref_stash}@{0}
@@ -425,7 +100,8 @@ show)
 	;;
 save)
 	shift
-	save_stash "$@"
+	cd "$START_DIR"
+	git stash--helper save "$@"
 	;;
 push)
 	shift
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 22/27] stash: optimize `get_untracked_files()` and `check_changes()`
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (20 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 21/27] stash: convert save to builtin Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 23/27] stash: replace all `write-tree` child processes with API calls Thomas Gummerer
                                     ` (8 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

This commits introduces a optimization by avoiding calling the
same functions again. For example, `git stash push -u`
would call at some points the following functions:

 * `check_changes()` (inside `do_push_stash()`)
 * `do_create_stash()`, which calls: `check_changes()` and
`get_untracked_files()`

Note that `check_changes()` also calls `get_untracked_files()`.
So, `check_changes()` is called 2 times and `get_untracked_files()`
3 times.

The old function `check_changes()` now consists of two functions:
`get_untracked_files()` and `check_changes_tracked_files()`.

These are the call chains for `push` and `create`:

 * `push_stash()` -> `do_push_stash()` -> `do_create_stash()`

 * `create_stash()` -> `do_create_stash()`

To prevent calling the same functions over and over again,
`check_changes()` inside `do_create_stash()` is now placed
in the caller functions (`create_stash()` and `do_push_stash()`).
This way `check_changes()` and `get_untracked files()` are called
only one time.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 builtin/stash--helper.c | 46 ++++++++++++++++++++++++-----------------
 1 file changed, 27 insertions(+), 19 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 8e4e96e353..4c51b58206 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -879,18 +879,17 @@ static int get_untracked_files(struct pathspec ps, int include_untracked,
 }
 
 /*
- * The return value of `check_changes()` can be:
+ * The return value of `check_changes_tracked_files()` can be:
  *
  * < 0 if there was an error
  * = 0 if there are no changes.
  * > 0 if there are changes.
  */
-static int check_changes(struct pathspec ps, int include_untracked)
+static int check_changes_tracked_files(struct pathspec ps)
 {
 	int result;
 	struct rev_info rev;
 	struct object_id dummy;
-	struct strbuf out = STRBUF_INIT;
 
 	/* No initial commit. */
 	if (get_oid("HEAD", &dummy))
@@ -918,14 +917,25 @@ static int check_changes(struct pathspec ps, int include_untracked)
 	if (diff_result_code(&rev.diffopt, result))
 		return 1;
 
+	return 0;
+}
+
+/*
+ * The function will fill `untracked_files` with the names of untracked files
+ * It will return 1 if there were any changes and 0 if there were not.
+ */
+static int check_changes(struct pathspec ps, int include_untracked,
+			 struct strbuf *untracked_files)
+{
+	int ret = 0;
+	if (check_changes_tracked_files(ps))
+		ret = 1;
+
 	if (include_untracked && get_untracked_files(ps, include_untracked,
-						     &out)) {
-		strbuf_release(&out);
-		return 1;
-	}
+						     untracked_files))
+		ret = 1;
 
-	strbuf_release(&out);
-	return 0;
+	return ret;
 }
 
 static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
@@ -1137,7 +1147,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 		head_commit = lookup_commit(the_repository, &info->b_commit);
 	}
 
-	if (!check_changes(ps, include_untracked)) {
+	if (!check_changes(ps, include_untracked, &untracked_files)) {
 		ret = 1;
 		goto done;
 	}
@@ -1162,8 +1172,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 		goto done;
 	}
 
-	if (include_untracked && get_untracked_files(ps, include_untracked,
-						     &untracked_files)) {
+	if (include_untracked) {
 		if (save_untracked_files(info, &msg, untracked_files)) {
 			if (!quiet)
 				fprintf_ln(stderr, _("Cannot save "
@@ -1248,6 +1257,9 @@ static int create_stash(int argc, const char **argv, const char *prefix)
 			     0);
 
 	memset(&ps, 0, sizeof(ps));
+	if (!check_changes_tracked_files(ps))
+		return 0;
+
 	strbuf_addstr(&stash_msg_buf, stash_msg);
 	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
 			      NULL, 0);
@@ -1255,12 +1267,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)
 		printf_ln("%s", oid_to_hex(&info.w_commit));
 
 	strbuf_release(&stash_msg_buf);
-
-	/*
-	 * ret can be 1 if there were no changes. In this case, we should
-	 * not error out.
-	 */
-	return ret < 0;
+	return ret;
 }
 
 static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
@@ -1270,6 +1277,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
 	struct stash_info info;
 	struct strbuf patch = STRBUF_INIT;
 	struct strbuf stash_msg_buf = STRBUF_INIT;
+	struct strbuf untracked_files = STRBUF_INIT;
 
 	if (patch_mode && keep_index == -1)
 		keep_index = 1;
@@ -1304,7 +1312,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
 		goto done;
 	}
 
-	if (!check_changes(ps, include_untracked)) {
+	if (!check_changes(ps, include_untracked, &untracked_files)) {
 		if (!quiet)
 			printf_ln(_("No local changes to save"));
 		goto done;
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 23/27] stash: replace all `write-tree` child processes with API calls
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (21 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 22/27] stash: optimize `get_untracked_files()` and `check_changes()` Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 24/27] stash: convert `stash--helper.c` into `stash.c` Thomas Gummerer
                                     ` (7 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

Avoid spawning write-tree child processes by replacing the calls with
in-core API calls.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 builtin/stash--helper.c | 41 ++++++++++++-----------------------------
 1 file changed, 12 insertions(+), 29 deletions(-)

diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c
index 4c51b58206..5f8c99c12f 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash--helper.c
@@ -943,9 +943,8 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
 {
 	int ret = 0;
 	struct strbuf untracked_msg = STRBUF_INIT;
-	struct strbuf out = STRBUF_INIT;
 	struct child_process cp_upd_index = CHILD_PROCESS_INIT;
-	struct child_process cp_write_tree = CHILD_PROCESS_INIT;
+	struct index_state istate = { NULL };
 
 	cp_upd_index.git_cmd = 1;
 	argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add",
@@ -960,15 +959,11 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
 		goto done;
 	}
 
-	cp_write_tree.git_cmd = 1;
-	argv_array_push(&cp_write_tree.args, "write-tree");
-	argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s",
-			 stash_index_path.buf);
-	if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) {
+	if (write_index_as_tree(&info->u_tree, &istate, stash_index_path.buf, 0,
+				NULL)) {
 		ret = -1;
 		goto done;
 	}
-	get_oid_hex(out.buf, &info->u_tree);
 
 	if (commit_tree(untracked_msg.buf, untracked_msg.len,
 			&info->u_tree, NULL, &info->u_commit, NULL, NULL)) {
@@ -977,8 +972,8 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
 	}
 
 done:
+	discard_index(&istate);
 	strbuf_release(&untracked_msg);
-	strbuf_release(&out);
 	remove_path(stash_index_path.buf);
 	return ret;
 }
@@ -987,11 +982,10 @@ static int stash_patch(struct stash_info *info, struct pathspec ps,
 		       struct strbuf *out_patch, int quiet)
 {
 	int ret = 0;
-	struct strbuf out = STRBUF_INIT;
 	struct child_process cp_read_tree = CHILD_PROCESS_INIT;
 	struct child_process cp_add_i = CHILD_PROCESS_INIT;
-	struct child_process cp_write_tree = CHILD_PROCESS_INIT;
 	struct child_process cp_diff_tree = CHILD_PROCESS_INIT;
+	struct index_state istate = { NULL };
 
 	remove_path(stash_index_path.buf);
 
@@ -1017,17 +1011,12 @@ static int stash_patch(struct stash_info *info, struct pathspec ps,
 	}
 
 	/* State of the working tree. */
-	cp_write_tree.git_cmd = 1;
-	argv_array_push(&cp_write_tree.args, "write-tree");
-	argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s",
-			 stash_index_path.buf);
-	if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) {
+	if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0,
+				NULL)) {
 		ret = -1;
 		goto done;
 	}
 
-	get_oid_hex(out.buf, &info->w_tree);
-
 	cp_diff_tree.git_cmd = 1;
 	argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD",
 			 oid_to_hex(&info->w_tree), "--", NULL);
@@ -1043,7 +1032,7 @@ static int stash_patch(struct stash_info *info, struct pathspec ps,
 	}
 
 done:
-	strbuf_release(&out);
+	discard_index(&istate);
 	remove_path(stash_index_path.buf);
 	return ret;
 }
@@ -1053,9 +1042,8 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
 	int ret = 0;
 	struct rev_info rev;
 	struct child_process cp_upd_index = CHILD_PROCESS_INIT;
-	struct child_process cp_write_tree = CHILD_PROCESS_INIT;
-	struct strbuf out = STRBUF_INIT;
 	struct strbuf diff_output = STRBUF_INIT;
+	struct index_state istate = { NULL };
 
 	init_revisions(&rev, NULL);
 
@@ -1095,20 +1083,15 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
 		goto done;
 	}
 
-	cp_write_tree.git_cmd = 1;
-	argv_array_push(&cp_write_tree.args, "write-tree");
-	argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s",
-			 stash_index_path.buf);
-	if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) {
+	if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0,
+				NULL)) {
 		ret = -1;
 		goto done;
 	}
 
-	get_oid_hex(out.buf, &info->w_tree);
-
 done:
+	discard_index(&istate);
 	UNLEAK(rev);
-	strbuf_release(&out);
 	object_array_clear(&rev.pending);
 	strbuf_release(&diff_output);
 	remove_path(stash_index_path.buf);
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 24/27] stash: convert `stash--helper.c` into `stash.c`
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (22 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 23/27] stash: replace all `write-tree` child processes with API calls Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 25/27] stash: add back the original, scripted `git stash` Thomas Gummerer
                                     ` (6 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Thomas Gummerer

From: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>

The old shell script `git-stash.sh`  was removed and replaced
entirely by `builtin/stash.c`. In order to do that, `create` and
`push` were adapted to work without `stash.sh`. For example, before
this commit, `git stash create` called `git stash--helper create
--message "$*"`. If it called `git stash--helper create "$@"`, then
some of these changes wouldn't have been necessary.

This commit also removes the word `helper` since now stash is
called directly and not by a shell script.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 .gitignore                           |   1 -
 Makefile                             |   3 +-
 builtin.h                            |   2 +-
 builtin/{stash--helper.c => stash.c} | 156 +++++++++++++++------------
 git-stash.sh                         | 153 --------------------------
 git.c                                |   2 +-
 6 files changed, 92 insertions(+), 225 deletions(-)
 rename builtin/{stash--helper.c => stash.c} (91%)
 delete mode 100755 git-stash.sh

diff --git a/.gitignore b/.gitignore
index 6ecab90ab2..0d77ea5894 100644
--- a/.gitignore
+++ b/.gitignore
@@ -162,7 +162,6 @@
 /git-show-ref
 /git-stage
 /git-stash
-/git-stash--helper
 /git-status
 /git-stripspace
 /git-submodule
diff --git a/Makefile b/Makefile
index c246fc7078..8cee2731aa 100644
--- a/Makefile
+++ b/Makefile
@@ -619,7 +619,6 @@ SCRIPT_SH += git-quiltimport.sh
 SCRIPT_SH += git-legacy-rebase.sh
 SCRIPT_SH += git-remote-testgit.sh
 SCRIPT_SH += git-request-pull.sh
-SCRIPT_SH += git-stash.sh
 SCRIPT_SH += git-submodule.sh
 SCRIPT_SH += git-web--browse.sh
 
@@ -1117,7 +1116,7 @@ BUILTIN_OBJS += builtin/shortlog.o
 BUILTIN_OBJS += builtin/show-branch.o
 BUILTIN_OBJS += builtin/show-index.o
 BUILTIN_OBJS += builtin/show-ref.o
-BUILTIN_OBJS += builtin/stash--helper.o
+BUILTIN_OBJS += builtin/stash.o
 BUILTIN_OBJS += builtin/stripspace.o
 BUILTIN_OBJS += builtin/submodule--helper.o
 BUILTIN_OBJS += builtin/symbolic-ref.o
diff --git a/builtin.h b/builtin.h
index ff4460aff7..b78ab6e30b 100644
--- a/builtin.h
+++ b/builtin.h
@@ -225,7 +225,7 @@ extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_show_index(int argc, const char **argv, const char *prefix);
 extern int cmd_status(int argc, const char **argv, const char *prefix);
-extern int cmd_stash__helper(int argc, const char **argv, const char *prefix);
+extern int cmd_stash(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
diff --git a/builtin/stash--helper.c b/builtin/stash.c
similarity index 91%
rename from builtin/stash--helper.c
rename to builtin/stash.c
index 5f8c99c12f..d9f3956ef5 100644
--- a/builtin/stash--helper.c
+++ b/builtin/stash.c
@@ -16,75 +16,70 @@
 
 #define INCLUDE_ALL_FILES 2
 
-static const char * const git_stash_helper_usage[] = {
-	N_("git stash--helper list [<options>]"),
-	N_("git stash--helper show [<options>] [<stash>]"),
-	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
-	N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
-	N_("git stash--helper branch <branchname> [<stash>]"),
-	N_("git stash--helper clear"),
-	N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+static const char * const git_stash_usage[] = {
+	N_("git stash list [<options>]"),
+	N_("git stash show [<options>] [<stash>]"),
+	N_("git stash drop [-q|--quiet] [<stash>]"),
+	N_("git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
+	N_("git stash branch <branchname> [<stash>]"),
+	N_("git stash clear"),
+	N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
 	   "          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
 	   "          [--] [<pathspec>...]]"),
-	N_("git stash--helper save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+	N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
 	   "          [-u|--include-untracked] [-a|--all] [<message>]"),
 	NULL
 };
 
-static const char * const git_stash_helper_list_usage[] = {
-	N_("git stash--helper list [<options>]"),
+static const char * const git_stash_list_usage[] = {
+	N_("git stash list [<options>]"),
 	NULL
 };
 
-static const char * const git_stash_helper_show_usage[] = {
-	N_("git stash--helper show [<options>] [<stash>]"),
+static const char * const git_stash_show_usage[] = {
+	N_("git stash show [<options>] [<stash>]"),
 	NULL
 };
 
-static const char * const git_stash_helper_drop_usage[] = {
-	N_("git stash--helper drop [-q|--quiet] [<stash>]"),
+static const char * const git_stash_drop_usage[] = {
+	N_("git stash drop [-q|--quiet] [<stash>]"),
 	NULL
 };
 
-static const char * const git_stash_helper_pop_usage[] = {
-	N_("git stash--helper pop [--index] [-q|--quiet] [<stash>]"),
+static const char * const git_stash_pop_usage[] = {
+	N_("git stash pop [--index] [-q|--quiet] [<stash>]"),
 	NULL
 };
 
-static const char * const git_stash_helper_apply_usage[] = {
-	N_("git stash--helper apply [--index] [-q|--quiet] [<stash>]"),
+static const char * const git_stash_apply_usage[] = {
+	N_("git stash apply [--index] [-q|--quiet] [<stash>]"),
 	NULL
 };
 
-static const char * const git_stash_helper_branch_usage[] = {
-	N_("git stash--helper branch <branchname> [<stash>]"),
+static const char * const git_stash_branch_usage[] = {
+	N_("git stash branch <branchname> [<stash>]"),
 	NULL
 };
 
-static const char * const git_stash_helper_clear_usage[] = {
-	N_("git stash--helper clear"),
+static const char * const git_stash_clear_usage[] = {
+	N_("git stash clear"),
 	NULL
 };
 
-static const char * const git_stash_helper_store_usage[] = {
-	N_("git stash--helper store [-m|--message <message>] [-q|--quiet] <commit>"),
+static const char * const git_stash_store_usage[] = {
+	N_("git stash store [-m|--message <message>] [-q|--quiet] <commit>"),
 	NULL
 };
 
-static const char * const git_stash_helper_create_usage[] = {
-	N_("git stash--helper create [<message>]"),
-	NULL
-};
-
-static const char * const git_stash_helper_push_usage[] = {
-	N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+static const char * const git_stash_push_usage[] = {
+	N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
 	   "          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
 	   "          [--] [<pathspec>...]]"),
 	NULL
 };
 
-static const char * const git_stash_helper_save_usage[] = {
-	N_("git stash--helper save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+static const char * const git_stash_save_usage[] = {
+	N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
 	   "          [-u|--include-untracked] [-a|--all] [<message>]"),
 	NULL
 };
@@ -220,7 +215,7 @@ static int clear_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_clear_usage,
+			     git_stash_clear_usage,
 			     PARSE_OPT_STOP_AT_NON_OPTION);
 
 	if (argc)
@@ -521,7 +516,7 @@ static int apply_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_apply_usage, 0);
+			     git_stash_apply_usage, 0);
 
 	if (get_stash_info(&info, argc, argv))
 		return -1;
@@ -594,7 +589,7 @@ static int drop_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_drop_usage, 0);
+			     git_stash_drop_usage, 0);
 
 	if (get_stash_info(&info, argc, argv))
 		return -1;
@@ -620,7 +615,7 @@ static int pop_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_pop_usage, 0);
+			     git_stash_pop_usage, 0);
 
 	if (get_stash_info(&info, argc, argv))
 		return -1;
@@ -647,7 +642,7 @@ static int branch_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_branch_usage, 0);
+			     git_stash_branch_usage, 0);
 
 	if (!argc) {
 		fprintf_ln(stderr, _("No branch name specified"));
@@ -682,7 +677,7 @@ static int list_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_list_usage,
+			     git_stash_list_usage,
 			     PARSE_OPT_KEEP_UNKNOWN);
 
 	if (!ref_exists(ref_stash))
@@ -762,7 +757,7 @@ static int show_stash(int argc, const char **argv, const char *prefix)
 	argc = setup_revisions(argc, argv, &rev, NULL);
 	if (argc > 1) {
 		free_stash_info(&info);
-		usage_with_options(git_stash_helper_show_usage, options);
+		usage_with_options(git_stash_show_usage, options);
 	}
 
 	rev.diffopt.flags.recursive = 1;
@@ -808,7 +803,7 @@ static int store_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_store_usage,
+			     git_stash_store_usage,
 			     PARSE_OPT_KEEP_UNKNOWN);
 
 	if (argc != 1) {
@@ -1221,30 +1216,19 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
 
 static int create_stash(int argc, const char **argv, const char *prefix)
 {
-	int include_untracked = 0;
 	int ret = 0;
-	const char *stash_msg = NULL;
 	struct strbuf stash_msg_buf = STRBUF_INIT;
 	struct stash_info info;
 	struct pathspec ps;
-	struct option options[] = {
-		OPT_BOOL('u', "include-untracked", &include_untracked,
-			 N_("include untracked files in stash")),
-		OPT_STRING('m', "message", &stash_msg, N_("message"),
-			 N_("stash message")),
-		OPT_END()
-	};
 
-	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_create_usage,
-			     0);
+	/* Starting with argv[1], since argv[0] is "create" */
+	strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' ');
 
 	memset(&ps, 0, sizeof(ps));
 	if (!check_changes_tracked_files(ps))
 		return 0;
 
-	strbuf_addstr(&stash_msg_buf, stash_msg);
-	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
+	ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info,
 			      NULL, 0);
 	if (!ret)
 		printf_ln("%s", oid_to_hex(&info.w_commit));
@@ -1476,9 +1460,10 @@ static int push_stash(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
-	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_push_usage,
-			     0);
+	if (argc)
+		argc = parse_options(argc, argv, prefix, options,
+				     git_stash_push_usage,
+				     0);
 
 	parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL, prefix, argv);
 	return do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode,
@@ -1511,7 +1496,7 @@ static int save_stash(int argc, const char **argv, const char *prefix)
 	};
 
 	argc = parse_options(argc, argv, prefix, options,
-			     git_stash_helper_save_usage,
+			     git_stash_save_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
 	if (argc)
@@ -1525,10 +1510,12 @@ static int save_stash(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
-int cmd_stash__helper(int argc, const char **argv, const char *prefix)
+int cmd_stash(int argc, const char **argv, const char *prefix)
 {
+	int i = -1;
 	pid_t pid = getpid();
 	const char *index_file;
+	struct argv_array args = ARGV_ARRAY_INIT;
 
 	struct option options[] = {
 		OPT_END()
@@ -1536,16 +1523,16 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 
 	git_config(git_diff_basic_config, NULL);
 
-	argc = parse_options(argc, argv, prefix, options, git_stash_helper_usage,
+	argc = parse_options(argc, argv, prefix, options, git_stash_usage,
 			     PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
 
 	index_file = get_index_file();
 	strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file,
 		    (uintmax_t)pid);
 
-	if (argc < 1)
-		usage_with_options(git_stash_helper_usage, options);
-	if (!strcmp(argv[0], "apply"))
+	if (!argc)
+		return !!push_stash(0, NULL, prefix);
+	else if (!strcmp(argv[0], "apply"))
 		return !!apply_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "clear"))
 		return !!clear_stash(argc, argv, prefix);
@@ -1567,7 +1554,42 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
 		return !!push_stash(argc, argv, prefix);
 	else if (!strcmp(argv[0], "save"))
 		return !!save_stash(argc, argv, prefix);
+	else if (*argv[0] != '-')
+		usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
+			      git_stash_usage, options);
+
+	if (strcmp(argv[0], "-p")) {
+		while (++i < argc && strcmp(argv[i], "--")) {
+			/*
+			 * `akpqu` is a string which contains all short options,
+			 * except `-m` which is verified separately.
+			 */
+			if ((strlen(argv[i]) == 2) && *argv[i] == '-' &&
+			    strchr("akpqu", argv[i][1]))
+				continue;
+
+			if (!strcmp(argv[i], "--all") ||
+			    !strcmp(argv[i], "--keep-index") ||
+			    !strcmp(argv[i], "--no-keep-index") ||
+			    !strcmp(argv[i], "--patch") ||
+			    !strcmp(argv[i], "--quiet") ||
+			    !strcmp(argv[i], "--include-untracked"))
+				continue;
+
+			/*
+			 * `-m` and `--message=` are verified separately because
+			 * they need to be immediately followed by a string
+			 * (i.e.`-m"foobar"` or `--message="foobar"`).
+			 */
+			if (starts_with(argv[i], "-m") ||
+			    starts_with(argv[i], "--message="))
+				continue;
+
+			usage_with_options(git_stash_usage, options);
+		}
+	}
 
-	usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
-		      git_stash_helper_usage, options);
+	argv_array_push(&args, "push");
+	argv_array_pushv(&args, argv);
+	return !!push_stash(args.argc, args.argv, prefix);
 }
diff --git a/git-stash.sh b/git-stash.sh
deleted file mode 100755
index 695f1feba3..0000000000
--- a/git-stash.sh
+++ /dev/null
@@ -1,153 +0,0 @@
-#!/bin/sh
-# Copyright (c) 2007, Nanako Shiraishi
-
-dashless=$(basename "$0" | sed -e 's/-/ /')
-USAGE="list [<options>]
-   or: $dashless show [<stash>]
-   or: $dashless drop [-q|--quiet] [<stash>]
-   or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
-   or: $dashless branch <branchname> [<stash>]
-   or: $dashless save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
-		      [-u|--include-untracked] [-a|--all] [<message>]
-   or: $dashless [push [--patch] [-k|--[no-]keep-index] [-q|--quiet]
-		       [-u|--include-untracked] [-a|--all] [-m <message>]
-		       [-- <pathspec>...]]
-   or: $dashless clear"
-
-SUBDIRECTORY_OK=Yes
-OPTIONS_SPEC=
-START_DIR=$(pwd)
-. git-sh-setup
-require_work_tree
-prefix=$(git rev-parse --show-prefix) || exit 1
-cd_to_toplevel
-
-TMP="$GIT_DIR/.git-stash.$$"
-TMPindex=${GIT_INDEX_FILE-"$(git rev-parse --git-path index)"}.stash.$$
-trap 'rm -f "$TMP-"* "$TMPindex"' 0
-
-ref_stash=refs/stash
-
-if git config --get-colorbool color.interactive; then
-       help_color="$(git config --get-color color.interactive.help 'red bold')"
-       reset_color="$(git config --get-color '' reset)"
-else
-       help_color=
-       reset_color=
-fi
-
-#
-# Parses the remaining options looking for flags and
-# at most one revision defaulting to ${ref_stash}@{0}
-# if none found.
-#
-# Derives related tree and commit objects from the
-# revision, if one is found.
-#
-# stash records the work tree, and is a merge between the
-# base commit (first parent) and the index tree (second parent).
-#
-#   REV is set to the symbolic version of the specified stash-like commit
-#   IS_STASH_LIKE is non-blank if ${REV} looks like a stash
-#   IS_STASH_REF is non-blank if the ${REV} looks like a stash ref
-#   s is set to the SHA1 of the stash commit
-#   w_commit is set to the commit containing the working tree
-#   b_commit is set to the base commit
-#   i_commit is set to the commit containing the index tree
-#   u_commit is set to the commit containing the untracked files tree
-#   w_tree is set to the working tree
-#   b_tree is set to the base tree
-#   i_tree is set to the index tree
-#   u_tree is set to the untracked files tree
-#
-#   GIT_QUIET is set to t if -q is specified
-#   INDEX_OPTION is set to --index if --index is specified.
-#   FLAGS is set to the remaining flags (if allowed)
-#
-# dies if:
-#   * too many revisions specified
-#   * no revision is specified and there is no stash stack
-#   * a revision is specified which cannot be resolve to a SHA1
-#   * a non-existent stash reference is specified
-#   * unknown flags were set and ALLOW_UNKNOWN_FLAGS is not "t"
-#
-
-test "$1" = "-p" && set "push" "$@"
-
-PARSE_CACHE='--not-parsed'
-# The default command is "push" if nothing but options are given
-seen_non_option=
-for opt
-do
-	case "$opt" in
-	--) break ;;
-	-*) ;;
-	*) seen_non_option=t; break ;;
-	esac
-done
-
-test -n "$seen_non_option" || set "push" "$@"
-
-# Main command set
-case "$1" in
-list)
-	shift
-	git stash--helper list "$@"
-	;;
-show)
-	shift
-	git stash--helper show "$@"
-	;;
-save)
-	shift
-	cd "$START_DIR"
-	git stash--helper save "$@"
-	;;
-push)
-	shift
-	cd "$START_DIR"
-	git stash--helper push "$@"
-	;;
-apply)
-	shift
-	cd "$START_DIR"
-	git stash--helper apply "$@"
-	;;
-clear)
-	shift
-	git stash--helper clear "$@"
-	;;
-create)
-	shift
-	git stash--helper create --message "$*"
-	;;
-store)
-	shift
-	git stash--helper store "$@"
-	;;
-drop)
-	shift
-	git stash--helper drop "$@"
-	;;
-pop)
-	shift
-	cd "$START_DIR"
-	git stash--helper pop "$@"
-	;;
-branch)
-	shift
-	cd "$START_DIR"
-	git stash--helper branch "$@"
-	;;
-*)
-	case $# in
-	0)
-		cd "$START_DIR"
-		git stash--helper push &&
-		say "$(gettext "(To restore them type \"git stash apply\")")"
-		;;
-	*)
-		usage
-	esac
-	;;
-esac
diff --git a/git.c b/git.c
index 76ee02802e..49ab91b4ec 100644
--- a/git.c
+++ b/git.c
@@ -554,7 +554,7 @@ static struct cmd_struct commands[] = {
 	{ "show-index", cmd_show_index },
 	{ "show-ref", cmd_show_ref, RUN_SETUP },
 	{ "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
-	{ "stash--helper", cmd_stash__helper, RUN_SETUP | NEED_WORK_TREE },
+	{ "stash", cmd_stash, RUN_SETUP | NEED_WORK_TREE },
 	{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 	{ "stripspace", cmd_stripspace },
 	{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 25/27] stash: add back the original, scripted `git stash`
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (23 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 24/27] stash: convert `stash--helper.c` into `stash.c` Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 26/27] stash: optionally use the scripted version again Thomas Gummerer
                                     ` (5 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Johannes Schindelin, Thomas Gummerer

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This simply copies the version as of sd/stash-wo-user-name verbatim. As
of now, it is not hooked up.

The next commit will change the builtin `stash` to hand off to the
scripted `git stash` when `stash.useBuiltin=false`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 git-stash.sh | 769 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 769 insertions(+)
 create mode 100755 git-stash.sh

diff --git a/git-stash.sh b/git-stash.sh
new file mode 100755
index 0000000000..789ce2f41d
--- /dev/null
+++ b/git-stash.sh
@@ -0,0 +1,769 @@
+#!/bin/sh
+# Copyright (c) 2007, Nanako Shiraishi
+
+dashless=$(basename "$0" | sed -e 's/-/ /')
+USAGE="list [<options>]
+   or: $dashless show [<stash>]
+   or: $dashless drop [-q|--quiet] [<stash>]
+   or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
+   or: $dashless branch <branchname> [<stash>]
+   or: $dashless save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
+		      [-u|--include-untracked] [-a|--all] [<message>]
+   or: $dashless [push [--patch] [-k|--[no-]keep-index] [-q|--quiet]
+		       [-u|--include-untracked] [-a|--all] [-m <message>]
+		       [-- <pathspec>...]]
+   or: $dashless clear"
+
+SUBDIRECTORY_OK=Yes
+OPTIONS_SPEC=
+START_DIR=$(pwd)
+. git-sh-setup
+require_work_tree
+prefix=$(git rev-parse --show-prefix) || exit 1
+cd_to_toplevel
+
+TMP="$GIT_DIR/.git-stash.$$"
+TMPindex=${GIT_INDEX_FILE-"$(git rev-parse --git-path index)"}.stash.$$
+trap 'rm -f "$TMP-"* "$TMPindex"' 0
+
+ref_stash=refs/stash
+
+if git config --get-colorbool color.interactive; then
+       help_color="$(git config --get-color color.interactive.help 'red bold')"
+       reset_color="$(git config --get-color '' reset)"
+else
+       help_color=
+       reset_color=
+fi
+
+no_changes () {
+	git diff-index --quiet --cached HEAD --ignore-submodules -- "$@" &&
+	git diff-files --quiet --ignore-submodules -- "$@" &&
+	(test -z "$untracked" || test -z "$(untracked_files "$@")")
+}
+
+untracked_files () {
+	if test "$1" = "-z"
+	then
+		shift
+		z=-z
+	else
+		z=
+	fi
+	excl_opt=--exclude-standard
+	test "$untracked" = "all" && excl_opt=
+	git ls-files -o $z $excl_opt -- "$@"
+}
+
+prepare_fallback_ident () {
+	if ! git -c user.useconfigonly=yes var GIT_COMMITTER_IDENT >/dev/null 2>&1
+	then
+		GIT_AUTHOR_NAME="git stash"
+		GIT_AUTHOR_EMAIL=git@stash
+		GIT_COMMITTER_NAME="git stash"
+		GIT_COMMITTER_EMAIL=git@stash
+		export GIT_AUTHOR_NAME
+		export GIT_AUTHOR_EMAIL
+		export GIT_COMMITTER_NAME
+		export GIT_COMMITTER_EMAIL
+	fi
+}
+
+clear_stash () {
+	if test $# != 0
+	then
+		die "$(gettext "git stash clear with parameters is unimplemented")"
+	fi
+	if current=$(git rev-parse --verify --quiet $ref_stash)
+	then
+		git update-ref -d $ref_stash $current
+	fi
+}
+
+create_stash () {
+
+	prepare_fallback_ident
+
+	stash_msg=
+	untracked=
+	while test $# != 0
+	do
+		case "$1" in
+		-m|--message)
+			shift
+			stash_msg=${1?"BUG: create_stash () -m requires an argument"}
+			;;
+		-m*)
+			stash_msg=${1#-m}
+			;;
+		--message=*)
+			stash_msg=${1#--message=}
+			;;
+		-u|--include-untracked)
+			shift
+			untracked=${1?"BUG: create_stash () -u requires an argument"}
+			;;
+		--)
+			shift
+			break
+			;;
+		esac
+		shift
+	done
+
+	git update-index -q --refresh
+	if no_changes "$@"
+	then
+		exit 0
+	fi
+
+	# state of the base commit
+	if b_commit=$(git rev-parse --verify HEAD)
+	then
+		head=$(git rev-list --oneline -n 1 HEAD --)
+	else
+		die "$(gettext "You do not have the initial commit yet")"
+	fi
+
+	if branch=$(git symbolic-ref -q HEAD)
+	then
+		branch=${branch#refs/heads/}
+	else
+		branch='(no branch)'
+	fi
+	msg=$(printf '%s: %s' "$branch" "$head")
+
+	# state of the index
+	i_tree=$(git write-tree) &&
+	i_commit=$(printf 'index on %s\n' "$msg" |
+		git commit-tree $i_tree -p $b_commit) ||
+		die "$(gettext "Cannot save the current index state")"
+
+	if test -n "$untracked"
+	then
+		# Untracked files are stored by themselves in a parentless commit, for
+		# ease of unpacking later.
+		u_commit=$(
+			untracked_files -z "$@" | (
+				GIT_INDEX_FILE="$TMPindex" &&
+				export GIT_INDEX_FILE &&
+				rm -f "$TMPindex" &&
+				git update-index -z --add --remove --stdin &&
+				u_tree=$(git write-tree) &&
+				printf 'untracked files on %s\n' "$msg" | git commit-tree $u_tree  &&
+				rm -f "$TMPindex"
+		) ) || die "$(gettext "Cannot save the untracked files")"
+
+		untracked_commit_option="-p $u_commit";
+	else
+		untracked_commit_option=
+	fi
+
+	if test -z "$patch_mode"
+	then
+
+		# state of the working tree
+		w_tree=$( (
+			git read-tree --index-output="$TMPindex" -m $i_tree &&
+			GIT_INDEX_FILE="$TMPindex" &&
+			export GIT_INDEX_FILE &&
+			git diff-index --name-only -z HEAD -- "$@" >"$TMP-stagenames" &&
+			git update-index -z --add --remove --stdin <"$TMP-stagenames" &&
+			git write-tree &&
+			rm -f "$TMPindex"
+		) ) ||
+			die "$(gettext "Cannot save the current worktree state")"
+
+	else
+
+		rm -f "$TMP-index" &&
+		GIT_INDEX_FILE="$TMP-index" git read-tree HEAD &&
+
+		# find out what the user wants
+		GIT_INDEX_FILE="$TMP-index" \
+			git add--interactive --patch=stash -- "$@" &&
+
+		# state of the working tree
+		w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
+		die "$(gettext "Cannot save the current worktree state")"
+
+		git diff-tree -p HEAD $w_tree -- >"$TMP-patch" &&
+		test -s "$TMP-patch" ||
+		die "$(gettext "No changes selected")"
+
+		rm -f "$TMP-index" ||
+		die "$(gettext "Cannot remove temporary index (can't happen)")"
+
+	fi
+
+	# create the stash
+	if test -z "$stash_msg"
+	then
+		stash_msg=$(printf 'WIP on %s' "$msg")
+	else
+		stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
+	fi
+	w_commit=$(printf '%s\n' "$stash_msg" |
+	git commit-tree $w_tree -p $b_commit -p $i_commit $untracked_commit_option) ||
+	die "$(gettext "Cannot record working tree state")"
+}
+
+store_stash () {
+	while test $# != 0
+	do
+		case "$1" in
+		-m|--message)
+			shift
+			stash_msg="$1"
+			;;
+		-m*)
+			stash_msg=${1#-m}
+			;;
+		--message=*)
+			stash_msg=${1#--message=}
+			;;
+		-q|--quiet)
+			quiet=t
+			;;
+		*)
+			break
+			;;
+		esac
+		shift
+	done
+	test $# = 1 ||
+	die "$(eval_gettext "\"$dashless store\" requires one <commit> argument")"
+
+	w_commit="$1"
+	if test -z "$stash_msg"
+	then
+		stash_msg="Created via \"git stash store\"."
+	fi
+
+	git update-ref --create-reflog -m "$stash_msg" $ref_stash $w_commit
+	ret=$?
+	test $ret != 0 && test -z "$quiet" &&
+	die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")"
+	return $ret
+}
+
+push_stash () {
+	keep_index=
+	patch_mode=
+	untracked=
+	stash_msg=
+	while test $# != 0
+	do
+		case "$1" in
+		-k|--keep-index)
+			keep_index=t
+			;;
+		--no-keep-index)
+			keep_index=n
+			;;
+		-p|--patch)
+			patch_mode=t
+			# only default to keep if we don't already have an override
+			test -z "$keep_index" && keep_index=t
+			;;
+		-q|--quiet)
+			GIT_QUIET=t
+			;;
+		-u|--include-untracked)
+			untracked=untracked
+			;;
+		-a|--all)
+			untracked=all
+			;;
+		-m|--message)
+			shift
+			test -z ${1+x} && usage
+			stash_msg=$1
+			;;
+		-m*)
+			stash_msg=${1#-m}
+			;;
+		--message=*)
+			stash_msg=${1#--message=}
+			;;
+		--help)
+			show_help
+			;;
+		--)
+			shift
+			break
+			;;
+		-*)
+			option="$1"
+			eval_gettextln "error: unknown option for 'stash push': \$option"
+			usage
+			;;
+		*)
+			break
+			;;
+		esac
+		shift
+	done
+
+	eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")"
+
+	if test -n "$patch_mode" && test -n "$untracked"
+	then
+		die "$(gettext "Can't use --patch and --include-untracked or --all at the same time")"
+	fi
+
+	test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1
+
+	git update-index -q --refresh
+	if no_changes "$@"
+	then
+		say "$(gettext "No local changes to save")"
+		exit 0
+	fi
+
+	git reflog exists $ref_stash ||
+		clear_stash || die "$(gettext "Cannot initialize stash")"
+
+	create_stash -m "$stash_msg" -u "$untracked" -- "$@"
+	store_stash -m "$stash_msg" -q $w_commit ||
+	die "$(gettext "Cannot save the current status")"
+	say "$(eval_gettext "Saved working directory and index state \$stash_msg")"
+
+	if test -z "$patch_mode"
+	then
+		test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
+		if test -n "$untracked" && test $# = 0
+		then
+			git clean --force --quiet -d $CLEAN_X_OPTION
+		fi
+
+		if test $# != 0
+		then
+			test -z "$untracked" && UPDATE_OPTION="-u" || UPDATE_OPTION=
+			test "$untracked" = "all" && FORCE_OPTION="--force" || FORCE_OPTION=
+			git add $UPDATE_OPTION $FORCE_OPTION -- "$@"
+			git diff-index -p --cached --binary HEAD -- "$@" |
+			git apply --index -R
+		else
+			git reset --hard -q
+		fi
+
+		if test "$keep_index" = "t" && test -n "$i_tree"
+		then
+			git read-tree --reset $i_tree
+			git ls-files -z --modified -- "$@" |
+			git checkout-index -z --force --stdin
+		fi
+	else
+		git apply -R < "$TMP-patch" ||
+		die "$(gettext "Cannot remove worktree changes")"
+
+		if test "$keep_index" != "t"
+		then
+			git reset -q -- "$@"
+		fi
+	fi
+}
+
+save_stash () {
+	push_options=
+	while test $# != 0
+	do
+		case "$1" in
+		--)
+			shift
+			break
+			;;
+		-*)
+			# pass all options through to push_stash
+			push_options="$push_options $1"
+			;;
+		*)
+			break
+			;;
+		esac
+		shift
+	done
+
+	stash_msg="$*"
+
+	if test -z "$stash_msg"
+	then
+		push_stash $push_options
+	else
+		push_stash $push_options -m "$stash_msg"
+	fi
+}
+
+have_stash () {
+	git rev-parse --verify --quiet $ref_stash >/dev/null
+}
+
+list_stash () {
+	have_stash || return 0
+	git log --format="%gd: %gs" -g --first-parent -m "$@" $ref_stash --
+}
+
+show_stash () {
+	ALLOW_UNKNOWN_FLAGS=t
+	assert_stash_like "$@"
+
+	if test -z "$FLAGS"
+	then
+		if test "$(git config --bool stash.showStat || echo true)" = "true"
+		then
+			FLAGS=--stat
+		fi
+
+		if test "$(git config --bool stash.showPatch || echo false)" = "true"
+		then
+			FLAGS=${FLAGS}${FLAGS:+ }-p
+		fi
+
+		if test -z "$FLAGS"
+		then
+			return 0
+		fi
+	fi
+
+	git diff ${FLAGS} $b_commit $w_commit
+}
+
+show_help () {
+	exec git help stash
+	exit 1
+}
+
+#
+# Parses the remaining options looking for flags and
+# at most one revision defaulting to ${ref_stash}@{0}
+# if none found.
+#
+# Derives related tree and commit objects from the
+# revision, if one is found.
+#
+# stash records the work tree, and is a merge between the
+# base commit (first parent) and the index tree (second parent).
+#
+#   REV is set to the symbolic version of the specified stash-like commit
+#   IS_STASH_LIKE is non-blank if ${REV} looks like a stash
+#   IS_STASH_REF is non-blank if the ${REV} looks like a stash ref
+#   s is set to the SHA1 of the stash commit
+#   w_commit is set to the commit containing the working tree
+#   b_commit is set to the base commit
+#   i_commit is set to the commit containing the index tree
+#   u_commit is set to the commit containing the untracked files tree
+#   w_tree is set to the working tree
+#   b_tree is set to the base tree
+#   i_tree is set to the index tree
+#   u_tree is set to the untracked files tree
+#
+#   GIT_QUIET is set to t if -q is specified
+#   INDEX_OPTION is set to --index if --index is specified.
+#   FLAGS is set to the remaining flags (if allowed)
+#
+# dies if:
+#   * too many revisions specified
+#   * no revision is specified and there is no stash stack
+#   * a revision is specified which cannot be resolve to a SHA1
+#   * a non-existent stash reference is specified
+#   * unknown flags were set and ALLOW_UNKNOWN_FLAGS is not "t"
+#
+
+parse_flags_and_rev()
+{
+	test "$PARSE_CACHE" = "$*" && return 0 # optimisation
+	PARSE_CACHE="$*"
+
+	IS_STASH_LIKE=
+	IS_STASH_REF=
+	INDEX_OPTION=
+	s=
+	w_commit=
+	b_commit=
+	i_commit=
+	u_commit=
+	w_tree=
+	b_tree=
+	i_tree=
+	u_tree=
+
+	FLAGS=
+	REV=
+	for opt
+	do
+		case "$opt" in
+			-q|--quiet)
+				GIT_QUIET=-t
+			;;
+			--index)
+				INDEX_OPTION=--index
+			;;
+			--help)
+				show_help
+			;;
+			-*)
+				test "$ALLOW_UNKNOWN_FLAGS" = t ||
+					die "$(eval_gettext "unknown option: \$opt")"
+				FLAGS="${FLAGS}${FLAGS:+ }$opt"
+			;;
+			*)
+				REV="${REV}${REV:+ }'$opt'"
+			;;
+		esac
+	done
+
+	eval set -- $REV
+
+	case $# in
+		0)
+			have_stash || die "$(gettext "No stash entries found.")"
+			set -- ${ref_stash}@{0}
+		;;
+		1)
+			:
+		;;
+		*)
+			die "$(eval_gettext "Too many revisions specified: \$REV")"
+		;;
+	esac
+
+	case "$1" in
+		*[!0-9]*)
+			:
+		;;
+		*)
+			set -- "${ref_stash}@{$1}"
+		;;
+	esac
+
+	REV=$(git rev-parse --symbolic --verify --quiet "$1") || {
+		reference="$1"
+		die "$(eval_gettext "\$reference is not a valid reference")"
+	}
+
+	i_commit=$(git rev-parse --verify --quiet "$REV^2") &&
+	set -- $(git rev-parse "$REV" "$REV^1" "$REV:" "$REV^1:" "$REV^2:" 2>/dev/null) &&
+	s=$1 &&
+	w_commit=$1 &&
+	b_commit=$2 &&
+	w_tree=$3 &&
+	b_tree=$4 &&
+	i_tree=$5 &&
+	IS_STASH_LIKE=t &&
+	test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
+	IS_STASH_REF=t
+
+	u_commit=$(git rev-parse --verify --quiet "$REV^3") &&
+	u_tree=$(git rev-parse "$REV^3:" 2>/dev/null)
+}
+
+is_stash_like()
+{
+	parse_flags_and_rev "$@"
+	test -n "$IS_STASH_LIKE"
+}
+
+assert_stash_like() {
+	is_stash_like "$@" || {
+		args="$*"
+		die "$(eval_gettext "'\$args' is not a stash-like commit")"
+	}
+}
+
+is_stash_ref() {
+	is_stash_like "$@" && test -n "$IS_STASH_REF"
+}
+
+assert_stash_ref() {
+	is_stash_ref "$@" || {
+		args="$*"
+		die "$(eval_gettext "'\$args' is not a stash reference")"
+	}
+}
+
+apply_stash () {
+
+	assert_stash_like "$@"
+
+	git update-index -q --refresh || die "$(gettext "unable to refresh index")"
+
+	# current index state
+	c_tree=$(git write-tree) ||
+		die "$(gettext "Cannot apply a stash in the middle of a merge")"
+
+	unstashed_index_tree=
+	if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" &&
+			test "$c_tree" != "$i_tree"
+	then
+		git diff-tree --binary $s^2^..$s^2 | git apply --cached
+		test $? -ne 0 &&
+			die "$(gettext "Conflicts in index. Try without --index.")"
+		unstashed_index_tree=$(git write-tree) ||
+			die "$(gettext "Could not save index tree")"
+		git reset
+	fi
+
+	if test -n "$u_tree"
+	then
+		GIT_INDEX_FILE="$TMPindex" git read-tree "$u_tree" &&
+		GIT_INDEX_FILE="$TMPindex" git checkout-index --all &&
+		rm -f "$TMPindex" ||
+		die "$(gettext "Could not restore untracked files from stash entry")"
+	fi
+
+	eval "
+		GITHEAD_$w_tree='Stashed changes' &&
+		GITHEAD_$c_tree='Updated upstream' &&
+		GITHEAD_$b_tree='Version stash was based on' &&
+		export GITHEAD_$w_tree GITHEAD_$c_tree GITHEAD_$b_tree
+	"
+
+	if test -n "$GIT_QUIET"
+	then
+		GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY
+	fi
+	if git merge-recursive $b_tree -- $c_tree $w_tree
+	then
+		# No conflict
+		if test -n "$unstashed_index_tree"
+		then
+			git read-tree "$unstashed_index_tree"
+		else
+			a="$TMP-added" &&
+			git diff-index --cached --name-only --diff-filter=A $c_tree >"$a" &&
+			git read-tree --reset $c_tree &&
+			git update-index --add --stdin <"$a" ||
+				die "$(gettext "Cannot unstage modified files")"
+			rm -f "$a"
+		fi
+		squelch=
+		if test -n "$GIT_QUIET"
+		then
+			squelch='>/dev/null 2>&1'
+		fi
+		(cd "$START_DIR" && eval "git status $squelch") || :
+	else
+		# Merge conflict; keep the exit status from merge-recursive
+		status=$?
+		git rerere
+		if test -n "$INDEX_OPTION"
+		then
+			gettextln "Index was not unstashed." >&2
+		fi
+		exit $status
+	fi
+}
+
+pop_stash() {
+	assert_stash_ref "$@"
+
+	if apply_stash "$@"
+	then
+		drop_stash "$@"
+	else
+		status=$?
+		say "$(gettext "The stash entry is kept in case you need it again.")"
+		exit $status
+	fi
+}
+
+drop_stash () {
+	assert_stash_ref "$@"
+
+	git reflog delete --updateref --rewrite "${REV}" &&
+		say "$(eval_gettext "Dropped \${REV} (\$s)")" ||
+		die "$(eval_gettext "\${REV}: Could not drop stash entry")"
+
+	# clear_stash if we just dropped the last stash entry
+	git rev-parse --verify --quiet "$ref_stash@{0}" >/dev/null ||
+	clear_stash
+}
+
+apply_to_branch () {
+	test -n "$1" || die "$(gettext "No branch name specified")"
+	branch=$1
+	shift 1
+
+	set -- --index "$@"
+	assert_stash_like "$@"
+
+	git checkout -b $branch $REV^ &&
+	apply_stash "$@" && {
+		test -z "$IS_STASH_REF" || drop_stash "$@"
+	}
+}
+
+test "$1" = "-p" && set "push" "$@"
+
+PARSE_CACHE='--not-parsed'
+# The default command is "push" if nothing but options are given
+seen_non_option=
+for opt
+do
+	case "$opt" in
+	--) break ;;
+	-*) ;;
+	*) seen_non_option=t; break ;;
+	esac
+done
+
+test -n "$seen_non_option" || set "push" "$@"
+
+# Main command set
+case "$1" in
+list)
+	shift
+	list_stash "$@"
+	;;
+show)
+	shift
+	show_stash "$@"
+	;;
+save)
+	shift
+	save_stash "$@"
+	;;
+push)
+	shift
+	push_stash "$@"
+	;;
+apply)
+	shift
+	apply_stash "$@"
+	;;
+clear)
+	shift
+	clear_stash "$@"
+	;;
+create)
+	shift
+	create_stash -m "$*" && echo "$w_commit"
+	;;
+store)
+	shift
+	store_stash "$@"
+	;;
+drop)
+	shift
+	drop_stash "$@"
+	;;
+pop)
+	shift
+	pop_stash "$@"
+	;;
+branch)
+	shift
+	apply_to_branch "$@"
+	;;
+*)
+	case $# in
+	0)
+		push_stash &&
+		say "$(gettext "(To restore them type \"git stash apply\")")"
+		;;
+	*)
+		usage
+	esac
+	;;
+esac
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 26/27] stash: optionally use the scripted version again
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (24 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 25/27] stash: add back the original, scripted `git stash` Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-25 23:16                   ` [PATCH v13 27/27] tests: add a special setup where stash.useBuiltin is off Thomas Gummerer
                                     ` (4 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Johannes Schindelin, Thomas Gummerer

From: Johannes Schindelin <johannes.schindelin@gmx.de>

We recently converted the `git stash` command from Unix shell scripts
to builtins.

Let's end users a way out when they discover a bug in the
builtin command: `stash.useBuiltin`.

As the file name `git-stash` is already in use, let's rename the
scripted backend to `git-legacy-stash`.

To make the test suite pass with `stash.useBuiltin=false`, this commit
also backports rudimentary support for `-q` (but only *just* enough
to appease the test suite), and adds a super-ugly hack to force exit
code 129 for `git stash -h`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 .gitignore                          |  1 +
 Makefile                            |  1 +
 builtin/stash.c                     | 35 +++++++++++++++++++++++++++++
 git-stash.sh => git-legacy-stash.sh | 34 +++++++++++++++++++++++++---
 git-sh-setup.sh                     |  1 +
 git.c                               |  7 +++++-
 6 files changed, 75 insertions(+), 4 deletions(-)
 rename git-stash.sh => git-legacy-stash.sh (97%)

diff --git a/.gitignore b/.gitignore
index 0d77ea5894..7b0164675e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -82,6 +82,7 @@
 /git-interpret-trailers
 /git-instaweb
 /git-legacy-rebase
+/git-legacy-stash
 /git-log
 /git-ls-files
 /git-ls-remote
diff --git a/Makefile b/Makefile
index 8cee2731aa..810231a0b5 100644
--- a/Makefile
+++ b/Makefile
@@ -617,6 +617,7 @@ SCRIPT_SH += git-merge-resolve.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-quiltimport.sh
 SCRIPT_SH += git-legacy-rebase.sh
+SCRIPT_SH += git-legacy-stash.sh
 SCRIPT_SH += git-remote-testgit.sh
 SCRIPT_SH += git-request-pull.sh
 SCRIPT_SH += git-submodule.sh
diff --git a/builtin/stash.c b/builtin/stash.c
index d9f3956ef5..49c6d7948a 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -13,6 +13,7 @@
 #include "revision.h"
 #include "log-tree.h"
 #include "diffcore.h"
+#include "exec-cmd.h"
 
 #define INCLUDE_ALL_FILES 2
 
@@ -1510,6 +1511,26 @@ static int save_stash(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
+static int use_builtin_stash(void)
+{
+	struct child_process cp = CHILD_PROCESS_INIT;
+	struct strbuf out = STRBUF_INIT;
+	int ret;
+
+	argv_array_pushl(&cp.args,
+			 "config", "--bool", "stash.usebuiltin", NULL);
+	cp.git_cmd = 1;
+	if (capture_command(&cp, &out, 6)) {
+		strbuf_release(&out);
+		return 1;
+	}
+
+	strbuf_trim(&out);
+	ret = !strcmp("true", out.buf);
+	strbuf_release(&out);
+	return ret;
+}
+
 int cmd_stash(int argc, const char **argv, const char *prefix)
 {
 	int i = -1;
@@ -1521,6 +1542,20 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
+	if (!use_builtin_stash()) {
+		const char *path = mkpath("%s/git-legacy-stash",
+					  git_exec_path());
+
+		if (sane_execvp(path, (char **)argv) < 0)
+			die_errno(_("could not exec %s"), path);
+		else
+			BUG("sane_execvp() returned???");
+	}
+
+	prefix = setup_git_directory();
+	trace_repo_setup(prefix);
+	setup_work_tree();
+
 	git_config(git_diff_basic_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, options, git_stash_usage,
diff --git a/git-stash.sh b/git-legacy-stash.sh
similarity index 97%
rename from git-stash.sh
rename to git-legacy-stash.sh
index 789ce2f41d..8a8c4a9270 100755
--- a/git-stash.sh
+++ b/git-legacy-stash.sh
@@ -80,6 +80,28 @@ clear_stash () {
 	fi
 }
 
+maybe_quiet () {
+	case "$1" in
+	--keep-stdout)
+		shift
+		if test -n "$GIT_QUIET"
+		then
+			eval "$@" 2>/dev/null
+		else
+			eval "$@"
+		fi
+		;;
+	*)
+		if test -n "$GIT_QUIET"
+		then
+			eval "$@" >/dev/null 2>&1
+		else
+			eval "$@"
+		fi
+		;;
+	esac
+}
+
 create_stash () {
 
 	prepare_fallback_ident
@@ -112,15 +134,18 @@ create_stash () {
 	done
 
 	git update-index -q --refresh
-	if no_changes "$@"
+	if maybe_quiet no_changes "$@"
 	then
 		exit 0
 	fi
 
 	# state of the base commit
-	if b_commit=$(git rev-parse --verify HEAD)
+	if b_commit=$(maybe_quiet --keep-stdout git rev-parse --verify HEAD)
 	then
 		head=$(git rev-list --oneline -n 1 HEAD --)
+	elif test -n "$GIT_QUIET"
+	then
+		exit 1
 	else
 		die "$(gettext "You do not have the initial commit yet")"
 	fi
@@ -315,7 +340,7 @@ push_stash () {
 	test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1
 
 	git update-index -q --refresh
-	if no_changes "$@"
+	if maybe_quiet no_changes "$@"
 	then
 		say "$(gettext "No local changes to save")"
 		exit 0
@@ -370,6 +395,9 @@ save_stash () {
 	while test $# != 0
 	do
 		case "$1" in
+		-q|--quiet)
+			GIT_QUIET=t
+			;;
 		--)
 			shift
 			break
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index 378928518b..10d9764185 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -101,6 +101,7 @@ $LONG_USAGE")"
 	case "$1" in
 		-h)
 		echo "$LONG_USAGE"
+		case "$0" in *git-legacy-stash) exit 129;; esac
 		exit
 	esac
 fi
diff --git a/git.c b/git.c
index 49ab91b4ec..8de729c9e6 100644
--- a/git.c
+++ b/git.c
@@ -554,7 +554,12 @@ static struct cmd_struct commands[] = {
 	{ "show-index", cmd_show_index },
 	{ "show-ref", cmd_show_ref, RUN_SETUP },
 	{ "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
-	{ "stash", cmd_stash, RUN_SETUP | NEED_WORK_TREE },
+	/*
+	 * NEEDSWORK: Until the builtin stash is thoroughly robust and no
+	 * longer needs redirection to the stash shell script this is kept as
+	 * is, then should be changed to RUN_SETUP | NEED_WORK_TREE
+	 */
+	{ "stash", cmd_stash },
 	{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 	{ "stripspace", cmd_stripspace },
 	{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
-- 
2.21.0.rc2.291.g17236886c5


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

* [PATCH v13 27/27] tests: add a special setup where stash.useBuiltin is off
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (25 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 26/27] stash: optionally use the scripted version again Thomas Gummerer
@ 2019-02-25 23:16                   ` Thomas Gummerer
  2019-02-26 12:40                   ` [PATCH v13 00/27] Convert "git stash" to C builtin Johannes Schindelin
                                     ` (3 subsequent siblings)
  30 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-25 23:16 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Johannes Schindelin, Thomas Gummerer

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Add a GIT_TEST_STASH_USE_BUILTIN=false test mode which is equivalent
to running with stash.useBuiltin=false. This is needed to spot that
we're not introducing any regressions in the legacy stash version
while we're carrying both it and the new built-in version.

This imitates the equivalent treatment for the built-in rebase in
62c23938fae5 (tests: add a special setup where rebase.useBuiltin is off,
2018-11-14).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 builtin/stash.c | 5 ++++-
 t/README        | 4 ++++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/builtin/stash.c b/builtin/stash.c
index 49c6d7948a..1bfa24030c 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -1515,7 +1515,10 @@ static int use_builtin_stash(void)
 {
 	struct child_process cp = CHILD_PROCESS_INIT;
 	struct strbuf out = STRBUF_INIT;
-	int ret;
+	int ret, env = git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1);
+
+	if (env != -1)
+		return env;
 
 	argv_array_pushl(&cp.args,
 			 "config", "--bool", "stash.usebuiltin", NULL);
diff --git a/t/README b/t/README
index 28711cc508..9187eeea8e 100644
--- a/t/README
+++ b/t/README
@@ -349,6 +349,10 @@ GIT_TEST_REBASE_USE_BUILTIN=<boolean>, when false, disables the
 builtin version of git-rebase. See 'rebase.useBuiltin' in
 git-config(1).
 
+GIT_TEST_STASH_USE_BUILTIN=<boolean>, when false, disables the
+built-in version of git-stash. See 'stash.useBuiltin' in
+git-config(1).
+
 GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading
 of the index for the whole test suite by bypassing the default number of
 cache entries and thread minimums. Setting this to 1 will make the
-- 
2.21.0.rc2.291.g17236886c5


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

* Re: [PATCH v13 00/27] Convert "git stash" to C builtin
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (26 preceding siblings ...)
  2019-02-25 23:16                   ` [PATCH v13 27/27] tests: add a special setup where stash.useBuiltin is off Thomas Gummerer
@ 2019-02-26 12:40                   ` Johannes Schindelin
  2019-02-26 20:48                     ` Thomas Gummerer
  2019-02-26 21:45                   ` Ævar Arnfjörð Bjarmason
                                     ` (2 subsequent siblings)
  30 siblings, 1 reply; 106+ messages in thread
From: Johannes Schindelin @ 2019-02-26 12:40 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: git, Junio C Hamano, Paul-Sebastian Ungureanu, SZEDER Gábor

Hi Thomas,

On Mon, 25 Feb 2019, Thomas Gummerer wrote:

> As I was advocating for this series to go into 'next' without a large
> refactor of this series, I'll put my money were my mouth is and try to
> make the cleanups and fixes required, though without trying to avoid
> further external process calls, or changing the series around too much.

Thank you.

> range-diff below:
> 
> [...]
>  9:  f6bbd78127 ! 10:  45670448e8 stash: convert apply to builtin
>     @@ -17,7 +17,7 @@
>      
>          Signed-off-by: Joel Teichroeb <joel@teichroeb.net>
>          Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
>     -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
>     +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
>      
>       diff --git a/.gitignore b/.gitignore
>       --- a/.gitignore
>     @@ -96,7 +96,6 @@
>      + * i_tree is set to the index tree
>      + * u_tree is set to the untracked files tree
>      + */
>     -+
>      +struct stash_info {
>      +	struct object_id w_commit;
>      +	struct object_id b_commit;
>     @@ -357,7 +356,7 @@
>      +	if (refresh_cache(REFRESH_QUIET))
>      +		return -1;
>      +
>     -+	if (write_cache_as_tree(&c_tree, 0, NULL) || reset_tree(&c_tree, 0, 0))
>     ++	if (write_cache_as_tree(&c_tree, 0, NULL))

Thank you for picking this up.

>      +		return error(_("cannot apply a stash in the middle of a merge"));
>      +
>      +	if (index) {
>
> [...]
> 22:  51809c70ca ! 23:  56a5ce2aeb stash: convert `stash--helper.c` into `stash.c`
>     @@ -13,7 +13,7 @@
>          called directly and not by a shell script.
>      
>          Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
>     -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
>     +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
>      
>       diff --git a/.gitignore b/.gitignore
>       --- a/.gitignore
>     @@ -273,9 +273,11 @@
>       		return 0;
>       
>      -	strbuf_addstr(&stash_msg_buf, stash_msg);
>     - 	if (!(ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info, NULL, 0)))
>     +-	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
>     ++	ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info,
>     + 			      NULL, 0);

I do not quite understand this change, though. The end result seems to
look similar enough to the previous iteration (except for the kept line
wrapping which I would have undone in this patch), but how come that the
`include_untracked` crept into some earlier patch in this iteration?

The rest was obvious enough, thank you.

As I said before, I was very much in favor of getting this `stash-in-c`
business moving again by advancing it to `next`.

From my perspective (which is not informed by any official statement about
the role of `pu` or `next` because there is none as far as I know), the
purpose of `pu` is to get patches into a shape where the original author
is no longer the only one working on them. In my mind, that moment has
long passed, and the fact that `ps/stash-in-c` is still stuck in `pu`
really held me back on working more on this.

So if you manage to get this finally cooking again, all hail to you!

Thanks,
Dscho

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

* Re: [PATCH v13 00/27] Convert "git stash" to C builtin
  2019-02-26 12:40                   ` [PATCH v13 00/27] Convert "git stash" to C builtin Johannes Schindelin
@ 2019-02-26 20:48                     ` Thomas Gummerer
  0 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-02-26 20:48 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: git, Junio C Hamano, Paul-Sebastian Ungureanu, SZEDER Gábor

On 02/26, Johannes Schindelin wrote:
> Hi Thomas,
> 
> On Mon, 25 Feb 2019, Thomas Gummerer wrote:
> > 22:  51809c70ca ! 23:  56a5ce2aeb stash: convert `stash--helper.c` into `stash.c`
> >     @@ -13,7 +13,7 @@
> >          called directly and not by a shell script.
> >      
> >          Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
> >     -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
> >     +    Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
> >      
> >       diff --git a/.gitignore b/.gitignore
> >       --- a/.gitignore
> >     @@ -273,9 +273,11 @@
> >       		return 0;
> >       
> >      -	strbuf_addstr(&stash_msg_buf, stash_msg);
> >     - 	if (!(ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info, NULL, 0)))
> >     +-	ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info,
> >     ++	ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info,
> >     + 			      NULL, 0);
> 
> I do not quite understand this change, though. The end result seems to
> look similar enough to the previous iteration (except for the kept line
> wrapping which I would have undone in this patch), but how come that the
> `include_untracked` crept into some earlier patch in this iteration?

The reason for this is that previously we stopped passing
include_untracked through in patch 17.  As the option parsing in the
create_stash function was still in place at that point, it felt
incorrect to not pass it through to 'do_create_stash()'.

None of the tests failed at that point though, as nothing was actually
passing the command line options through anymore.  The last user of
that was removed in the "convert save to builtin" patch.

Arguably that would also be the right place to remove the option
parsing, rather than in this patch (convert `stash--helper.c` into
`stash.c`), but I tried to refrain from too much re-ordering to make
the changes more easily reviewable through range-diff.

Maybe I should have held back on these changes as well, or made the
other changes, dunno.  I wasn't always sure where to draw the line
between changing too much, which might hurt the chances of advancing
to next, because of the review work required, or doing too little,
which might hurt the chances because the series is not deemed in good
enough shape overall.

So what I ended up with is what I felt were the changes necessary to
advance the series, so we can start working on top, without the fear
of stepping on others peoples toes as much.

> The rest was obvious enough, thank you.
> 
> As I said before, I was very much in favor of getting this `stash-in-c`
> business moving again by advancing it to `next`.
> 
> From my perspective (which is not informed by any official statement about
> the role of `pu` or `next` because there is none as far as I know), the
> purpose of `pu` is to get patches into a shape where the original author
> is no longer the only one working on them. In my mind, that moment has
> long passed, and the fact that `ps/stash-in-c` is still stuck in `pu`
> really held me back on working more on this.

I mainly held back on doing much with it, because I didn't want to
step on Paul-Sebastians toes (and I thought we'd be okay with
advancing the series with the fixups on top).  But hopefully this is
now getting us into a state where we get this to next, and get further
work on top unblocked.

> So if you manage to get this finally cooking again, all hail to you!

I hope we can :)

> Thanks,
> Dscho

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

* Re: [PATCH v13 00/27] Convert "git stash" to C builtin
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (27 preceding siblings ...)
  2019-02-26 12:40                   ` [PATCH v13 00/27] Convert "git stash" to C builtin Johannes Schindelin
@ 2019-02-26 21:45                   ` Ævar Arnfjörð Bjarmason
  2019-02-26 22:37                     ` Johannes Schindelin
  2019-03-03  1:24                   ` Junio C Hamano
  2019-03-03  1:25                   ` Junio C Hamano
  30 siblings, 1 reply; 106+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2019-02-26 21:45 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: git, Junio C Hamano, Johannes Schindelin,
	Paul-Sebastian Ungureanu, SZEDER Gábor


On Tue, Feb 26 2019, Thomas Gummerer wrote:

> As I was advocating for this series to go into 'next' without a large
> refactor of this series, I'll put my money were my mouth is and try to
> make the cleanups and fixes required, though without trying to avoid
> further external process calls, or changing the series around too
> much.
>
> One thing to consider here is that we have a GSoC project planned
> based on 'git stash'.  If we can't get this to 'next' soon, I'd vote
> for taking that project out of this years GSoC, and maybe try again
> next year, if nobody implemented the feature in the meantime.

FWIW I'd like to +1 getting it into "next" so this can be given thorough
testing in the 2.22

If there's still bugs or other regressions I think it's better sorted
out without the cognitive load of reviewing it all again.

Worst case we can always add something on top to flip the default of
stash.useBuiltin.

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

* Re: [PATCH v13 00/27] Convert "git stash" to C builtin
  2019-02-26 21:45                   ` Ævar Arnfjörð Bjarmason
@ 2019-02-26 22:37                     ` Johannes Schindelin
  0 siblings, 0 replies; 106+ messages in thread
From: Johannes Schindelin @ 2019-02-26 22:37 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Thomas Gummerer, git, Junio C Hamano, Paul-Sebastian Ungureanu,
	SZEDER Gábor

[-- Attachment #1: Type: text/plain, Size: 1285 bytes --]

Hi Ævar,

On Tue, 26 Feb 2019, Ævar Arnfjörð Bjarmason wrote:

> 
> On Tue, Feb 26 2019, Thomas Gummerer wrote:
> 
> > As I was advocating for this series to go into 'next' without a large
> > refactor of this series, I'll put my money were my mouth is and try to
> > make the cleanups and fixes required, though without trying to avoid
> > further external process calls, or changing the series around too
> > much.
> >
> > One thing to consider here is that we have a GSoC project planned
> > based on 'git stash'.  If we can't get this to 'next' soon, I'd vote
> > for taking that project out of this years GSoC, and maybe try again
> > next year, if nobody implemented the feature in the meantime.
> 
> FWIW I'd like to +1 getting it into "next" so this can be given thorough
> testing in the 2.22

Indeed. It is time.

> If there's still bugs or other regressions I think it's better sorted
> out without the cognitive load of reviewing it all again.

Fully agree!

> Worst case we can always add something on top to flip the default of
> stash.useBuiltin.

I actually don't think that will be necessary. If my track record with
fixing all kinds of built-in rebase corner-case bugs leading up to v2.20.0
is any indication, the built-in stash will be cooking well.

Ciao,
Dscho

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

* Re: [PATCH v13 00/27] Convert "git stash" to C builtin
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (28 preceding siblings ...)
  2019-02-26 21:45                   ` Ævar Arnfjörð Bjarmason
@ 2019-03-03  1:24                   ` Junio C Hamano
  2019-03-03  1:25                   ` Junio C Hamano
  30 siblings, 0 replies; 106+ messages in thread
From: Junio C Hamano @ 2019-03-03  1:24 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: git, Johannes Schindelin, Paul-Sebastian Ungureanu, SZEDER Gábor

Thomas Gummerer <t.gummerer@gmail.com> writes:

> One thing that came up in the latest reviews, was to keep the stash
> script intact throughout the series, and to not re-introduce it after
> deleting it.  I did however not do that, as that would make the
> range-diff quite a bit harder to read.

Of course, if you start from a suboptimal ordering and reorder to
make it right, the range-diff that tries to match and compare the
steps will become larger and harder to follow.  What else is new?

That is refusing to think clearly hiding behind tautology.  

> In addition removing the
> script bit by bit also allowed us to find the precise commit in which
> the missing 'discard_cache()' bug was introduced, which made it a bit
> easier to pinpoint where the bug comes from imo.

In an ideal ordering, you would do a command line parser first and
dispatch the rewritten commands piece by piece to the C
reimplementation while diverting the remaining unwritten parts to
scripted "legacy" one.  That would allow us to find the precise
commit that was faulty the same way.

Having said all that.

I do not care too deeply about seeing this particular series done in
the right order anymore.  Everybody's eyes are tired of looking at
it, and I do not mind to see you guys declare "nobody can review
this, so let's keep it in 'next' and patch breakages up as they are
found out", which seems to be happening here.  

We can divert our review cycles to other topics that haven't faced
reviewer fatigue; they still have chance to be done right ;-).

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

* Re: [PATCH v13 00/27] Convert "git stash" to C builtin
  2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
                                     ` (29 preceding siblings ...)
  2019-03-03  1:24                   ` Junio C Hamano
@ 2019-03-03  1:25                   ` Junio C Hamano
  2019-03-03 10:03                     ` Thomas Gummerer
  30 siblings, 1 reply; 106+ messages in thread
From: Junio C Hamano @ 2019-03-03  1:25 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: git, Johannes Schindelin, Paul-Sebastian Ungureanu, SZEDER Gábor

Thomas Gummerer <t.gummerer@gmail.com> writes:

> As I was advocating for this series to go into 'next' without a large
> refactor of this series, I'll put my money were my mouth is and try to
> make the cleanups and fixes required, though without trying to avoid
> further external process calls, or changing the series around too
> much.

Thanks for a well-written summary.  One thing that is missing in the
write-up is if you kept the same base (i.e. on top of eacdb4d24f) or
rebased the series on top of somewhere else.  I'd assume you built
on the same base as before in the meantime (I'll know soon enough,
when I sit down on a terminal and resume working on the code ;-)





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

* Re: [PATCH v13 00/27] Convert "git stash" to C builtin
  2019-03-03  1:25                   ` Junio C Hamano
@ 2019-03-03 10:03                     ` Thomas Gummerer
  0 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-03-03 10:03 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Johannes Schindelin, Paul-Sebastian Ungureanu, SZEDER Gábor

On 03/03, Junio C Hamano wrote:
> Thomas Gummerer <t.gummerer@gmail.com> writes:
> 
> > As I was advocating for this series to go into 'next' without a large
> > refactor of this series, I'll put my money were my mouth is and try to
> > make the cleanups and fixes required, though without trying to avoid
> > further external process calls, or changing the series around too
> > much.
> 
> Thanks for a well-written summary.  One thing that is missing in the
> write-up is if you kept the same base (i.e. on top of eacdb4d24f) or
> rebased the series on top of somewhere else.  I'd assume you built
> on the same base as before in the meantime (I'll know soon enough,
> when I sit down on a terminal and resume working on the code ;-)

Right, I forgot mentioning that.  Yes it is still based on eacdb4d24f,
as there was no good reason to change that.

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

* Re: [PATCH v13 12/27] stash: convert drop and clear to builtin
  2019-02-25 23:16                   ` [PATCH v13 12/27] stash: convert drop and clear to builtin Thomas Gummerer
@ 2019-03-07 19:15                     ` Jeff King
  2019-03-09 18:30                       ` Thomas Gummerer
  0 siblings, 1 reply; 106+ messages in thread
From: Jeff King @ 2019-03-07 19:15 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: git, Junio C Hamano, Johannes Schindelin,
	Paul-Sebastian Ungureanu, SZEDER Gábor, Joel Teichroeb

On Mon, Feb 25, 2019 at 11:16:16PM +0000, Thomas Gummerer wrote:

> +static int do_drop_stash(const char *prefix, struct stash_info *info, int quiet)

This series hit next recently, so I started building it merged with my
-Wunused-parameters series. This "prefix" parameter is not ever used.
Skimming through the function, I don't see anything that _should_ be
using it, so I think it's just cruft, and not indicative of a bug.

The same is true of create_stash() elsewhere in the series. But there it
might be worth keeping for consistency with the other top-level action
functions. The other ones pass "prefix" to parse_options(), but
create_stash() doesn't actually parse any options (and intentionally so,
since even "--help" should be taken as part of the stash message).

-Peff

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

* Re: [PATCH v13 18/27] stash: convert create to builtin
  2019-02-25 23:16                   ` [PATCH v13 18/27] stash: convert create " Thomas Gummerer
@ 2019-03-07 19:18                     ` Jeff King
  2019-03-08 15:30                       ` Johannes Schindelin
  0 siblings, 1 reply; 106+ messages in thread
From: Jeff King @ 2019-03-07 19:18 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: git, Junio C Hamano, Johannes Schindelin,
	Paul-Sebastian Ungureanu, SZEDER Gábor, Matthew Kraai

On Mon, Feb 25, 2019 at 11:16:22PM +0000, Thomas Gummerer wrote:

> +static void add_pathspecs(struct argv_array *args,
> +			  struct pathspec ps) {

Here and elsewhere in the series, I notice that we pass the pathspec
struct by value, which is quite unusual for our codebase (and
potentially confusing, if any of the callers were to mutate the pointers
in the struct).

Is there any reason this shouldn't be "const struct pathspec *ps" pretty
much throughout the file?

-Peff

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

* Re: [PATCH v13 18/27] stash: convert create to builtin
  2019-03-07 19:18                     ` Jeff King
@ 2019-03-08 15:30                       ` Johannes Schindelin
  2019-03-09 18:26                         ` Thomas Gummerer
  0 siblings, 1 reply; 106+ messages in thread
From: Johannes Schindelin @ 2019-03-08 15:30 UTC (permalink / raw)
  To: Jeff King
  Cc: Thomas Gummerer, git, Junio C Hamano, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Matthew Kraai

Hi Peff,

On Thu, 7 Mar 2019, Jeff King wrote:

> On Mon, Feb 25, 2019 at 11:16:22PM +0000, Thomas Gummerer wrote:
> 
> > +static void add_pathspecs(struct argv_array *args,
> > +			  struct pathspec ps) {
> 
> Here and elsewhere in the series, I notice that we pass the pathspec
> struct by value, which is quite unusual for our codebase (and
> potentially confusing, if any of the callers were to mutate the pointers
> in the struct).
> 
> Is there any reason this shouldn't be "const struct pathspec *ps" pretty
> much throughout the file?

I am quite certain that this is merely an oversight. It totes slipped
by my review, for example.

Thanks for catching!
Dscho

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

* Re: [PATCH v13 18/27] stash: convert create to builtin
  2019-03-08 15:30                       ` Johannes Schindelin
@ 2019-03-09 18:26                         ` Thomas Gummerer
  2019-03-11  1:47                           ` Junio C Hamano
  0 siblings, 1 reply; 106+ messages in thread
From: Thomas Gummerer @ 2019-03-09 18:26 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Jeff King, git, Junio C Hamano, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Matthew Kraai

On 03/08, Johannes Schindelin wrote:
> Hi Peff,
> 
> On Thu, 7 Mar 2019, Jeff King wrote:
> 
> > On Mon, Feb 25, 2019 at 11:16:22PM +0000, Thomas Gummerer wrote:
> > 
> > > +static void add_pathspecs(struct argv_array *args,
> > > +			  struct pathspec ps) {
> > 
> > Here and elsewhere in the series, I notice that we pass the pathspec
> > struct by value, which is quite unusual for our codebase (and
> > potentially confusing, if any of the callers were to mutate the pointers
> > in the struct).
> > 
> > Is there any reason this shouldn't be "const struct pathspec *ps" pretty
> > much throughout the file?
> 
> I am quite certain that this is merely an oversight. It totes slipped
> by my review, for example.

Yep, it slipped by me as well.  Here's a patch to fix it:

--- >8 ---
Subject: [PATCH 1/2] stash: pass pathspec as pointer

Passing the pathspec by value is potentially confusing, as the copy is
only a shallow copy, so save the overhead of the copy, and pass the
pathspec struct as a pointer.

In addition use copy_pathspec to copy the pathspec into
rev.prune_data, so the copy is a proper deep copy, and owned by the
revision API, as that's what the API expects.

Reported-by: Jeff King <peff@peff.net>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 builtin/stash.c | 50 ++++++++++++++++++++++++-------------------------
 1 file changed, 25 insertions(+), 25 deletions(-)

diff --git a/builtin/stash.c b/builtin/stash.c
index 1bfa24030c..6eb67c75c3 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -826,11 +826,11 @@ static int store_stash(int argc, const char **argv, const char *prefix)
 }
 
 static void add_pathspecs(struct argv_array *args,
-			  struct pathspec ps) {
+			  const struct pathspec *ps) {
 	int i;
 
-	for (i = 0; i < ps.nr; i++)
-		argv_array_push(args, ps.items[i].match);
+	for (i = 0; i < ps->nr; i++)
+		argv_array_push(args, ps->items[i].match);
 }
 
 /*
@@ -840,7 +840,7 @@ static void add_pathspecs(struct argv_array *args,
  * = 0 if there are not any untracked files
  * > 0 if there are untracked files
  */
-static int get_untracked_files(struct pathspec ps, int include_untracked,
+static int get_untracked_files(const struct pathspec *ps, int include_untracked,
 			       struct strbuf *untracked_files)
 {
 	int i;
@@ -853,12 +853,12 @@ static int get_untracked_files(struct pathspec ps, int include_untracked,
 	if (include_untracked != INCLUDE_ALL_FILES)
 		setup_standard_excludes(&dir);
 
-	seen = xcalloc(ps.nr, 1);
+	seen = xcalloc(ps->nr, 1);
 
-	max_len = fill_directory(&dir, the_repository->index, &ps);
+	max_len = fill_directory(&dir, the_repository->index, ps);
 	for (i = 0; i < dir.nr; i++) {
 		struct dir_entry *ent = dir.entries[i];
-		if (dir_path_match(&the_index, ent, &ps, max_len, seen)) {
+		if (dir_path_match(&the_index, ent, ps, max_len, seen)) {
 			found++;
 			strbuf_addstr(untracked_files, ent->name);
 			/* NUL-terminate: will be fed to update-index -z */
@@ -881,7 +881,7 @@ static int get_untracked_files(struct pathspec ps, int include_untracked,
  * = 0 if there are no changes.
  * > 0 if there are changes.
  */
-static int check_changes_tracked_files(struct pathspec ps)
+static int check_changes_tracked_files(const struct pathspec *ps)
 {
 	int result;
 	struct rev_info rev;
@@ -895,7 +895,7 @@ static int check_changes_tracked_files(struct pathspec ps)
 		return -1;
 
 	init_revisions(&rev, NULL);
-	rev.prune_data = ps;
+	copy_pathspec(&rev.prune_data, ps);
 
 	rev.diffopt.flags.quick = 1;
 	rev.diffopt.flags.ignore_submodules = 1;
@@ -920,7 +920,7 @@ static int check_changes_tracked_files(struct pathspec ps)
  * The function will fill `untracked_files` with the names of untracked files
  * It will return 1 if there were any changes and 0 if there were not.
  */
-static int check_changes(struct pathspec ps, int include_untracked,
+static int check_changes(const struct pathspec *ps, int include_untracked,
 			 struct strbuf *untracked_files)
 {
 	int ret = 0;
@@ -974,7 +974,7 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
 	return ret;
 }
 
-static int stash_patch(struct stash_info *info, struct pathspec ps,
+static int stash_patch(struct stash_info *info, const struct pathspec *ps,
 		       struct strbuf *out_patch, int quiet)
 {
 	int ret = 0;
@@ -1033,7 +1033,7 @@ static int stash_patch(struct stash_info *info, struct pathspec ps,
 	return ret;
 }
 
-static int stash_working_tree(struct stash_info *info, struct pathspec ps)
+static int stash_working_tree(struct stash_info *info, const struct pathspec *ps)
 {
 	int ret = 0;
 	struct rev_info rev;
@@ -1050,7 +1050,7 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
 	}
 	set_alternate_index_output(NULL);
 
-	rev.prune_data = ps;
+	copy_pathspec(&rev.prune_data, ps);
 	rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
 	rev.diffopt.format_callback = add_diff_to_buf;
 	rev.diffopt.format_callback_data = &diff_output;
@@ -1094,7 +1094,7 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
 	return ret;
 }
 
-static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
+static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,
 			   int include_untracked, int patch_mode,
 			   struct stash_info *info, struct strbuf *patch,
 			   int quiet)
@@ -1226,10 +1226,10 @@ static int create_stash(int argc, const char **argv, const char *prefix)
 	strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' ');
 
 	memset(&ps, 0, sizeof(ps));
-	if (!check_changes_tracked_files(ps))
+	if (!check_changes_tracked_files(&ps))
 		return 0;
 
-	ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info,
+	ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, &info,
 			      NULL, 0);
 	if (!ret)
 		printf_ln("%s", oid_to_hex(&info.w_commit));
@@ -1238,7 +1238,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
-static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
+static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,
 			 int keep_index, int patch_mode, int include_untracked)
 {
 	int ret = 0;
@@ -1258,15 +1258,15 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
 	}
 
 	read_cache_preload(NULL);
-	if (!include_untracked && ps.nr) {
+	if (!include_untracked && ps->nr) {
 		int i;
-		char *ps_matched = xcalloc(ps.nr, 1);
+		char *ps_matched = xcalloc(ps->nr, 1);
 
 		for (i = 0; i < active_nr; i++)
-			ce_path_match(&the_index, active_cache[i], &ps,
+			ce_path_match(&the_index, active_cache[i], ps,
 				      ps_matched);
 
-		if (report_path_error(ps_matched, &ps, NULL)) {
+		if (report_path_error(ps_matched, ps, NULL)) {
 			fprintf_ln(stderr, _("Did you forget to 'git add'?"));
 			ret = -1;
 			free(ps_matched);
@@ -1313,7 +1313,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
 			  stash_msg_buf.buf);
 
 	if (!patch_mode) {
-		if (include_untracked && !ps.nr) {
+		if (include_untracked && !ps->nr) {
 			struct child_process cp = CHILD_PROCESS_INIT;
 
 			cp.git_cmd = 1;
@@ -1327,7 +1327,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
 			}
 		}
 		discard_cache();
-		if (ps.nr) {
+		if (ps->nr) {
 			struct child_process cp_add = CHILD_PROCESS_INIT;
 			struct child_process cp_diff = CHILD_PROCESS_INIT;
 			struct child_process cp_apply = CHILD_PROCESS_INIT;
@@ -1467,7 +1467,7 @@ static int push_stash(int argc, const char **argv, const char *prefix)
 				     0);
 
 	parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL, prefix, argv);
-	return do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode,
+	return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
 			     include_untracked);
 }
 
@@ -1504,7 +1504,7 @@ static int save_stash(int argc, const char **argv, const char *prefix)
 		stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');
 
 	memset(&ps, 0, sizeof(ps));
-	ret = do_push_stash(ps, stash_msg, quiet, keep_index,
+	ret = do_push_stash(&ps, stash_msg, quiet, keep_index,
 			    patch_mode, include_untracked);
 
 	strbuf_release(&stash_msg_buf);
-- 
2.21.0.474.g541d9dca55

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

* Re: [PATCH v13 12/27] stash: convert drop and clear to builtin
  2019-03-07 19:15                     ` Jeff King
@ 2019-03-09 18:30                       ` Thomas Gummerer
  2019-03-10 23:26                         ` Jeff King
  2019-03-11  1:40                         ` Junio C Hamano
  0 siblings, 2 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-03-09 18:30 UTC (permalink / raw)
  To: Jeff King
  Cc: git, Junio C Hamano, Johannes Schindelin,
	Paul-Sebastian Ungureanu, SZEDER Gábor, Joel Teichroeb

On 03/07, Jeff King wrote:
> On Mon, Feb 25, 2019 at 11:16:16PM +0000, Thomas Gummerer wrote:
> 
> > +static int do_drop_stash(const char *prefix, struct stash_info *info, int quiet)
> 
> This series hit next recently, so I started building it merged with my
> -Wunused-parameters series. This "prefix" parameter is not ever used.
> Skimming through the function, I don't see anything that _should_ be
> using it, so I think it's just cruft, and not indicative of a bug.

Agreed, I think it's only cruft, and shouldn't be used anywhere in the
function.  Below is a patch to remove the parameter.

> The same is true of create_stash() elsewhere in the series. But there it
> might be worth keeping for consistency with the other top-level action
> functions. The other ones pass "prefix" to parse_options(), but
> create_stash() doesn't actually parse any options (and intentionally so,
> since even "--help" should be taken as part of the stash message).

Agreed, I'd be happy to keep the parameter there.  Looking at your
fork, you seem to have some WIP patches to introduce a UNUSED macro
for parameters like this, which I don't think I've seen on the list
yet (though I may have just missed them).

I guess it's probably best for you to mark this parameter as UNUSED as
part of your series, but if you have a different preference on how to
handle it, let me know.

--- >8 ---
Subject: [PATCH 2/2] stash: drop unused parameter

Drop the unused prefix parameter in do_drop_stash.

We also have an unused "prefix" parameter in the 'create_stash'
function, however we leave that in place for symmetry with the other
top-level functions.

Reported-by: Jeff King <peff@peff.net>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
 builtin/stash.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/builtin/stash.c b/builtin/stash.c
index 6eb67c75c3..069bf14846 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -527,7 +527,7 @@ static int apply_stash(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
-static int do_drop_stash(const char *prefix, struct stash_info *info, int quiet)
+static int do_drop_stash(struct stash_info *info, int quiet)
 {
 	int ret;
 	struct child_process cp_reflog = CHILD_PROCESS_INIT;
@@ -597,7 +597,7 @@ static int drop_stash(int argc, const char **argv, const char *prefix)
 
 	assert_stash_ref(&info);
 
-	ret = do_drop_stash(prefix, &info, quiet);
+	ret = do_drop_stash(&info, quiet);
 	free_stash_info(&info);
 	return ret;
 }
@@ -626,7 +626,7 @@ static int pop_stash(int argc, const char **argv, const char *prefix)
 		printf_ln(_("The stash entry is kept in case "
 			    "you need it again."));
 	else
-		ret = do_drop_stash(prefix, &info, quiet);
+		ret = do_drop_stash(&info, quiet);
 
 	free_stash_info(&info);
 	return ret;
@@ -663,7 +663,7 @@ static int branch_stash(int argc, const char **argv, const char *prefix)
 	if (!ret)
 		ret = do_apply_stash(prefix, &info, 1, 0);
 	if (!ret && info.is_stash_ref)
-		ret = do_drop_stash(prefix, &info, 0);
+		ret = do_drop_stash(&info, 0);
 
 	free_stash_info(&info);
 
-- 
2.21.0.474.g541d9dca55

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

* Re: [PATCH v13 12/27] stash: convert drop and clear to builtin
  2019-03-09 18:30                       ` Thomas Gummerer
@ 2019-03-10 23:26                         ` Jeff King
  2019-03-11  1:40                         ` Junio C Hamano
  1 sibling, 0 replies; 106+ messages in thread
From: Jeff King @ 2019-03-10 23:26 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: git, Junio C Hamano, Johannes Schindelin,
	Paul-Sebastian Ungureanu, SZEDER Gábor, Joel Teichroeb

On Sat, Mar 09, 2019 at 06:30:21PM +0000, Thomas Gummerer wrote:

> On 03/07, Jeff King wrote:
> > On Mon, Feb 25, 2019 at 11:16:16PM +0000, Thomas Gummerer wrote:
> > 
> > > +static int do_drop_stash(const char *prefix, struct stash_info *info, int quiet)
> > 
> > This series hit next recently, so I started building it merged with my
> > -Wunused-parameters series. This "prefix" parameter is not ever used.
> > Skimming through the function, I don't see anything that _should_ be
> > using it, so I think it's just cruft, and not indicative of a bug.
> 
> Agreed, I think it's only cruft, and shouldn't be used anywhere in the
> function.  Below is a patch to remove the parameter.

Thanks. I did something similar temporarily for my own tree, but I'll
assume yours will go on top of the stash topic upstream. The patch below
looks good (and matches what I did locally).

> > The same is true of create_stash() elsewhere in the series. But there it
> > might be worth keeping for consistency with the other top-level action
> > functions. The other ones pass "prefix" to parse_options(), but
> > create_stash() doesn't actually parse any options (and intentionally so,
> > since even "--help" should be taken as part of the stash message).
> 
> Agreed, I'd be happy to keep the parameter there.  Looking at your
> fork, you seem to have some WIP patches to introduce a UNUSED macro
> for parameters like this, which I don't think I've seen on the list
> yet (though I may have just missed them).
> 
> I guess it's probably best for you to mark this parameter as UNUSED as
> part of your series, but if you have a different preference on how to
> handle it, let me know.

Yep, that sounds good; I'll eventually mark this UNUSED when I send
those patches. The "mark unused" ones haven't hit the list yet. I've
been trickling the patches out, 10 or so at a time, but I'm still on the
"drop ones that we can" patches, and haven't even gotten to the "mark
ones we want to keep" patches. :)

-Peff

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

* Re: [PATCH v13 12/27] stash: convert drop and clear to builtin
  2019-03-09 18:30                       ` Thomas Gummerer
  2019-03-10 23:26                         ` Jeff King
@ 2019-03-11  1:40                         ` Junio C Hamano
  2019-03-11 21:40                           ` Thomas Gummerer
  1 sibling, 1 reply; 106+ messages in thread
From: Junio C Hamano @ 2019-03-11  1:40 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: Jeff King, git, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Joel Teichroeb

Thomas Gummerer <t.gummerer@gmail.com> writes:

> Agreed, I'd be happy to keep the parameter there.  Looking at your
> fork, you seem to have some WIP patches to introduce a UNUSED macro
> for parameters like this, which I don't think I've seen on the list
> yet (though I may have just missed them).
>
> I guess it's probably best for you to mark this parameter as UNUSED as
> part of your series, but if you have a different preference on how to
> handle it, let me know.

I agree that the uniformity among near-toplevel helpers like
create_stash() is a good thing to have.

In the meantime, you want the patch you sent (below) on top of the
stash-in-c topic to address do_drop_stash()?

Thanks for working well together.

> --- >8 ---
> Subject: [PATCH 2/2] stash: drop unused parameter
>
> Drop the unused prefix parameter in do_drop_stash.
>
> We also have an unused "prefix" parameter in the 'create_stash'
> function, however we leave that in place for symmetry with the other
> top-level functions.
>
> Reported-by: Jeff King <peff@peff.net>
> Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
> ---
>  builtin/stash.c | 8 ++++----
>  1 file changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/builtin/stash.c b/builtin/stash.c
> index 6eb67c75c3..069bf14846 100644
> --- a/builtin/stash.c
> +++ b/builtin/stash.c
> @@ -527,7 +527,7 @@ static int apply_stash(int argc, const char **argv, const char *prefix)
>  	return ret;
>  }
>  
> -static int do_drop_stash(const char *prefix, struct stash_info *info, int quiet)
> +static int do_drop_stash(struct stash_info *info, int quiet)
>  {
>  	int ret;
>  	struct child_process cp_reflog = CHILD_PROCESS_INIT;
> @@ -597,7 +597,7 @@ static int drop_stash(int argc, const char **argv, const char *prefix)
>  
>  	assert_stash_ref(&info);
>  
> -	ret = do_drop_stash(prefix, &info, quiet);
> +	ret = do_drop_stash(&info, quiet);
>  	free_stash_info(&info);
>  	return ret;
>  }
> @@ -626,7 +626,7 @@ static int pop_stash(int argc, const char **argv, const char *prefix)
>  		printf_ln(_("The stash entry is kept in case "
>  			    "you need it again."));
>  	else
> -		ret = do_drop_stash(prefix, &info, quiet);
> +		ret = do_drop_stash(&info, quiet);
>  
>  	free_stash_info(&info);
>  	return ret;
> @@ -663,7 +663,7 @@ static int branch_stash(int argc, const char **argv, const char *prefix)
>  	if (!ret)
>  		ret = do_apply_stash(prefix, &info, 1, 0);
>  	if (!ret && info.is_stash_ref)
> -		ret = do_drop_stash(prefix, &info, 0);
> +		ret = do_drop_stash(&info, 0);
>  
>  	free_stash_info(&info);

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

* Re: [PATCH v13 18/27] stash: convert create to builtin
  2019-03-09 18:26                         ` Thomas Gummerer
@ 2019-03-11  1:47                           ` Junio C Hamano
  2019-03-11  7:30                             ` Junio C Hamano
  0 siblings, 1 reply; 106+ messages in thread
From: Junio C Hamano @ 2019-03-11  1:47 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: Johannes Schindelin, Jeff King, git, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Matthew Kraai

Thomas Gummerer <t.gummerer@gmail.com> writes:

> Subject: [PATCH 1/2] stash: pass pathspec as pointer
>
> Passing the pathspec by value is potentially confusing, as the copy is
> only a shallow copy, so save the overhead of the copy, and pass the
> pathspec struct as a pointer.
>
> In addition use copy_pathspec to copy the pathspec into
> rev.prune_data, so the copy is a proper deep copy, and owned by the
> revision API, as that's what the API expects.

It does make quite a lot of sense, but do we need clear_pathspec()
at strategic places after we are done using these copied instances?

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

* Re: [PATCH v13 18/27] stash: convert create to builtin
  2019-03-11  1:47                           ` Junio C Hamano
@ 2019-03-11  7:30                             ` Junio C Hamano
  2019-03-11 21:42                               ` Thomas Gummerer
  0 siblings, 1 reply; 106+ messages in thread
From: Junio C Hamano @ 2019-03-11  7:30 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: Johannes Schindelin, Jeff King, git, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Matthew Kraai

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

> Thomas Gummerer <t.gummerer@gmail.com> writes:
>
>> Subject: [PATCH 1/2] stash: pass pathspec as pointer
>>
>> Passing the pathspec by value is potentially confusing, as the copy is
>> only a shallow copy, so save the overhead of the copy, and pass the
>> pathspec struct as a pointer.
>>
>> In addition use copy_pathspec to copy the pathspec into
>> rev.prune_data, so the copy is a proper deep copy, and owned by the
>> revision API, as that's what the API expects.
>
> It does make quite a lot of sense, but do we need clear_pathspec()
> at strategic places after we are done using these copied instances?

Another thing is that this also needs Dscho's fix to pass down the
pathspec that was originally given, not the parsed part.

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

* Re: [PATCH v13 12/27] stash: convert drop and clear to builtin
  2019-03-11  1:40                         ` Junio C Hamano
@ 2019-03-11 21:40                           ` Thomas Gummerer
  0 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-03-11 21:40 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jeff King, git, Johannes Schindelin, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Joel Teichroeb

On 03/11, Junio C Hamano wrote:
> Thomas Gummerer <t.gummerer@gmail.com> writes:
> 
> > Agreed, I'd be happy to keep the parameter there.  Looking at your
> > fork, you seem to have some WIP patches to introduce a UNUSED macro
> > for parameters like this, which I don't think I've seen on the list
> > yet (though I may have just missed them).
> >
> > I guess it's probably best for you to mark this parameter as UNUSED as
> > part of your series, but if you have a different preference on how to
> > handle it, let me know.
> 
> I agree that the uniformity among near-toplevel helpers like
> create_stash() is a good thing to have.
> 
> In the meantime, you want the patch you sent (below) on top of the
> stash-in-c topic to address do_drop_stash()?

Yes, I think that would be good, thanks! (And if I read Peff's reply
in this thread correctly, I think that's his preference as well).

I also just realized that this is patch 2/2 as I created it on the
same branch as the pathspec fixups.  But they are really independent,
even though they should both end up on top of ps/stash-in-c.

> Thanks for working well together.
> 
> > --- >8 ---
> > Subject: [PATCH 2/2] stash: drop unused parameter
> >
> > Drop the unused prefix parameter in do_drop_stash.
> >
> > We also have an unused "prefix" parameter in the 'create_stash'
> > function, however we leave that in place for symmetry with the other
> > top-level functions.
> >
> > Reported-by: Jeff King <peff@peff.net>
> > Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
> > ---
> >  builtin/stash.c | 8 ++++----
> >  1 file changed, 4 insertions(+), 4 deletions(-)
> >
> > diff --git a/builtin/stash.c b/builtin/stash.c
> > index 6eb67c75c3..069bf14846 100644
> > --- a/builtin/stash.c
> > +++ b/builtin/stash.c
> > @@ -527,7 +527,7 @@ static int apply_stash(int argc, const char **argv, const char *prefix)
> >  	return ret;
> >  }
> >  
> > -static int do_drop_stash(const char *prefix, struct stash_info *info, int quiet)
> > +static int do_drop_stash(struct stash_info *info, int quiet)
> >  {
> >  	int ret;
> >  	struct child_process cp_reflog = CHILD_PROCESS_INIT;
> > @@ -597,7 +597,7 @@ static int drop_stash(int argc, const char **argv, const char *prefix)
> >  
> >  	assert_stash_ref(&info);
> >  
> > -	ret = do_drop_stash(prefix, &info, quiet);
> > +	ret = do_drop_stash(&info, quiet);
> >  	free_stash_info(&info);
> >  	return ret;
> >  }
> > @@ -626,7 +626,7 @@ static int pop_stash(int argc, const char **argv, const char *prefix)
> >  		printf_ln(_("The stash entry is kept in case "
> >  			    "you need it again."));
> >  	else
> > -		ret = do_drop_stash(prefix, &info, quiet);
> > +		ret = do_drop_stash(&info, quiet);
> >  
> >  	free_stash_info(&info);
> >  	return ret;
> > @@ -663,7 +663,7 @@ static int branch_stash(int argc, const char **argv, const char *prefix)
> >  	if (!ret)
> >  		ret = do_apply_stash(prefix, &info, 1, 0);
> >  	if (!ret && info.is_stash_ref)
> > -		ret = do_drop_stash(prefix, &info, 0);
> > +		ret = do_drop_stash(&info, 0);
> >  
> >  	free_stash_info(&info);

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

* Re: [PATCH v13 18/27] stash: convert create to builtin
  2019-03-11  7:30                             ` Junio C Hamano
@ 2019-03-11 21:42                               ` Thomas Gummerer
  2019-03-11 22:16                                 ` [PATCH v2] stash: pass pathspec as pointer Thomas Gummerer
  0 siblings, 1 reply; 106+ messages in thread
From: Thomas Gummerer @ 2019-03-11 21:42 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin, Jeff King, git, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Matthew Kraai

On 03/11, Junio C Hamano wrote:
> Junio C Hamano <gitster@pobox.com> writes:
> 
> > Thomas Gummerer <t.gummerer@gmail.com> writes:
> >
> >> Subject: [PATCH 1/2] stash: pass pathspec as pointer
> >>
> >> Passing the pathspec by value is potentially confusing, as the copy is
> >> only a shallow copy, so save the overhead of the copy, and pass the
> >> pathspec struct as a pointer.
> >>
> >> In addition use copy_pathspec to copy the pathspec into
> >> rev.prune_data, so the copy is a proper deep copy, and owned by the
> >> revision API, as that's what the API expects.
> >
> > It does make quite a lot of sense, but do we need clear_pathspec()
> > at strategic places after we are done using these copied instances?
> 
> Another thing is that this also needs Dscho's fix to pass down the
> pathspec that was originally given, not the parsed part.

Good catch on both acconts.  I'll send a new patch soon, adding the
clear_pathspec() calls and rebasing it on top of Dscho's fix.

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

* [PATCH v2] stash: pass pathspec as pointer
  2019-03-11 21:42                               ` Thomas Gummerer
@ 2019-03-11 22:16                                 ` Thomas Gummerer
  2019-03-12  6:50                                   ` Junio C Hamano
  2019-03-12 22:35                                   ` Johannes Schindelin
  0 siblings, 2 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-03-11 22:16 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin, Jeff King, git, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Matthew Kraai

Passing the pathspec by value is potentially confusing, as the copy is
only a shallow copy, so save the overhead of the copy, and pass the
pathspec struct as a pointer.

In addition use copy_pathspec to copy the pathspec into
rev.prune_data, so the copy is a proper deep copy, and owned by the
revision API, as that's what the API expects.

Reported-by: Jeff King <peff@peff.net>
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---

> Good catch on both acconts.  I'll send a new patch soon, adding the
> clear_pathspec() calls and rebasing it on top of Dscho's fix.

Here it is.  Thanks for the review of the first round Junio!

This is on top of Dscho's series at
<pull.159.git.gitgitgadget@gmail.com> applied to ps/stash-in-c.

 builtin/stash.c | 68 +++++++++++++++++++++++++++----------------------
 1 file changed, 38 insertions(+), 30 deletions(-)

diff --git a/builtin/stash.c b/builtin/stash.c
index 2f29d037c8..e0528d4cc8 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -826,11 +826,11 @@ static int store_stash(int argc, const char **argv, const char *prefix)
 }
 
 static void add_pathspecs(struct argv_array *args,
-			  struct pathspec ps) {
+			  const struct pathspec *ps) {
 	int i;
 
-	for (i = 0; i < ps.nr; i++)
-		argv_array_push(args, ps.items[i].original);
+	for (i = 0; i < ps->nr; i++)
+		argv_array_push(args, ps->items[i].original);
 }
 
 /*
@@ -840,7 +840,7 @@ static void add_pathspecs(struct argv_array *args,
  * = 0 if there are not any untracked files
  * > 0 if there are untracked files
  */
-static int get_untracked_files(struct pathspec ps, int include_untracked,
+static int get_untracked_files(const struct pathspec *ps, int include_untracked,
 			       struct strbuf *untracked_files)
 {
 	int i;
@@ -853,12 +853,12 @@ static int get_untracked_files(struct pathspec ps, int include_untracked,
 	if (include_untracked != INCLUDE_ALL_FILES)
 		setup_standard_excludes(&dir);
 
-	seen = xcalloc(ps.nr, 1);
+	seen = xcalloc(ps->nr, 1);
 
-	max_len = fill_directory(&dir, the_repository->index, &ps);
+	max_len = fill_directory(&dir, the_repository->index, ps);
 	for (i = 0; i < dir.nr; i++) {
 		struct dir_entry *ent = dir.entries[i];
-		if (dir_path_match(&the_index, ent, &ps, max_len, seen)) {
+		if (dir_path_match(&the_index, ent, ps, max_len, seen)) {
 			found++;
 			strbuf_addstr(untracked_files, ent->name);
 			/* NUL-terminate: will be fed to update-index -z */
@@ -881,11 +881,12 @@ static int get_untracked_files(struct pathspec ps, int include_untracked,
  * = 0 if there are no changes.
  * > 0 if there are changes.
  */
-static int check_changes_tracked_files(struct pathspec ps)
+static int check_changes_tracked_files(const struct pathspec *ps)
 {
 	int result;
 	struct rev_info rev;
 	struct object_id dummy;
+	int ret = 0;
 
 	/* No initial commit. */
 	if (get_oid("HEAD", &dummy))
@@ -895,7 +896,7 @@ static int check_changes_tracked_files(struct pathspec ps)
 		return -1;
 
 	init_revisions(&rev, NULL);
-	rev.prune_data = ps;
+	copy_pathspec(&rev.prune_data, ps);
 
 	rev.diffopt.flags.quick = 1;
 	rev.diffopt.flags.ignore_submodules = 1;
@@ -905,22 +906,28 @@ static int check_changes_tracked_files(struct pathspec ps)
 	diff_setup_done(&rev.diffopt);
 
 	result = run_diff_index(&rev, 1);
-	if (diff_result_code(&rev.diffopt, result))
-		return 1;
+	if (diff_result_code(&rev.diffopt, result)) {
+		ret = 1;
+		goto done;
+	}
 
 	object_array_clear(&rev.pending);
 	result = run_diff_files(&rev, 0);
-	if (diff_result_code(&rev.diffopt, result))
-		return 1;
+	if (diff_result_code(&rev.diffopt, result)) {
+		ret = 1;
+		goto done;
+	}
 
-	return 0;
+done:
+	clear_pathspec(&rev.prune_data);
+	return ret;
 }
 
 /*
  * The function will fill `untracked_files` with the names of untracked files
  * It will return 1 if there were any changes and 0 if there were not.
  */
-static int check_changes(struct pathspec ps, int include_untracked,
+static int check_changes(const struct pathspec *ps, int include_untracked,
 			 struct strbuf *untracked_files)
 {
 	int ret = 0;
@@ -974,7 +981,7 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
 	return ret;
 }
 
-static int stash_patch(struct stash_info *info, struct pathspec ps,
+static int stash_patch(struct stash_info *info, const struct pathspec *ps,
 		       struct strbuf *out_patch, int quiet)
 {
 	int ret = 0;
@@ -1033,7 +1040,7 @@ static int stash_patch(struct stash_info *info, struct pathspec ps,
 	return ret;
 }
 
-static int stash_working_tree(struct stash_info *info, struct pathspec ps)
+static int stash_working_tree(struct stash_info *info, const struct pathspec *ps)
 {
 	int ret = 0;
 	struct rev_info rev;
@@ -1042,6 +1049,7 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
 	struct index_state istate = { NULL };
 
 	init_revisions(&rev, NULL);
+	copy_pathspec(&rev.prune_data, ps);
 
 	set_alternate_index_output(stash_index_path.buf);
 	if (reset_tree(&info->i_tree, 0, 0)) {
@@ -1050,7 +1058,6 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
 	}
 	set_alternate_index_output(NULL);
 
-	rev.prune_data = ps;
 	rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
 	rev.diffopt.format_callback = add_diff_to_buf;
 	rev.diffopt.format_callback_data = &diff_output;
@@ -1089,12 +1096,13 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
 	discard_index(&istate);
 	UNLEAK(rev);
 	object_array_clear(&rev.pending);
+	clear_pathspec(&rev.prune_data);
 	strbuf_release(&diff_output);
 	remove_path(stash_index_path.buf);
 	return ret;
 }
 
-static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
+static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,
 			   int include_untracked, int patch_mode,
 			   struct stash_info *info, struct strbuf *patch,
 			   int quiet)
@@ -1226,10 +1234,10 @@ static int create_stash(int argc, const char **argv, const char *prefix)
 	strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' ');
 
 	memset(&ps, 0, sizeof(ps));
-	if (!check_changes_tracked_files(ps))
+	if (!check_changes_tracked_files(&ps))
 		return 0;
 
-	ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info,
+	ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, &info,
 			      NULL, 0);
 	if (!ret)
 		printf_ln("%s", oid_to_hex(&info.w_commit));
@@ -1238,7 +1246,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
-static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
+static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,
 			 int keep_index, int patch_mode, int include_untracked)
 {
 	int ret = 0;
@@ -1258,15 +1266,15 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
 	}
 
 	read_cache_preload(NULL);
-	if (!include_untracked && ps.nr) {
+	if (!include_untracked && ps->nr) {
 		int i;
-		char *ps_matched = xcalloc(ps.nr, 1);
+		char *ps_matched = xcalloc(ps->nr, 1);
 
 		for (i = 0; i < active_nr; i++)
-			ce_path_match(&the_index, active_cache[i], &ps,
+			ce_path_match(&the_index, active_cache[i], ps,
 				      ps_matched);
 
-		if (report_path_error(ps_matched, &ps, NULL)) {
+		if (report_path_error(ps_matched, ps, NULL)) {
 			fprintf_ln(stderr, _("Did you forget to 'git add'?"));
 			ret = -1;
 			free(ps_matched);
@@ -1313,7 +1321,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
 			  stash_msg_buf.buf);
 
 	if (!patch_mode) {
-		if (include_untracked && !ps.nr) {
+		if (include_untracked && !ps->nr) {
 			struct child_process cp = CHILD_PROCESS_INIT;
 
 			cp.git_cmd = 1;
@@ -1327,7 +1335,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet,
 			}
 		}
 		discard_cache();
-		if (ps.nr) {
+		if (ps->nr) {
 			struct child_process cp_add = CHILD_PROCESS_INIT;
 			struct child_process cp_diff = CHILD_PROCESS_INIT;
 			struct child_process cp_apply = CHILD_PROCESS_INIT;
@@ -1468,7 +1476,7 @@ static int push_stash(int argc, const char **argv, const char *prefix)
 
 	parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN,
 		       prefix, argv);
-	return do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode,
+	return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
 			     include_untracked);
 }
 
@@ -1505,7 +1513,7 @@ static int save_stash(int argc, const char **argv, const char *prefix)
 		stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');
 
 	memset(&ps, 0, sizeof(ps));
-	ret = do_push_stash(ps, stash_msg, quiet, keep_index,
+	ret = do_push_stash(&ps, stash_msg, quiet, keep_index,
 			    patch_mode, include_untracked);
 
 	strbuf_release(&stash_msg_buf);
-- 
2.21.0.474.g541d9dca55

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

* Re: [PATCH v2] stash: pass pathspec as pointer
  2019-03-11 22:16                                 ` [PATCH v2] stash: pass pathspec as pointer Thomas Gummerer
@ 2019-03-12  6:50                                   ` Junio C Hamano
  2019-03-12 22:35                                   ` Johannes Schindelin
  1 sibling, 0 replies; 106+ messages in thread
From: Junio C Hamano @ 2019-03-12  6:50 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: Johannes Schindelin, Jeff King, git, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Matthew Kraai

Thomas Gummerer <t.gummerer@gmail.com> writes:

>> Good catch on both acconts.  I'll send a new patch soon, adding the
>> clear_pathspec() calls and rebasing it on top of Dscho's fix.
>
> Here it is.  Thanks for the review of the first round Junio!
>
> This is on top of Dscho's series at
> <pull.159.git.gitgitgadget@gmail.com> applied to ps/stash-in-c.

That's called js/stash-in-c-pathspec-fix; as this patch is also
about pathspec, I guess it is a good idea to just keep them in a
single topic, so I'll apply it there.


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

* Re: [PATCH v2] stash: pass pathspec as pointer
  2019-03-11 22:16                                 ` [PATCH v2] stash: pass pathspec as pointer Thomas Gummerer
  2019-03-12  6:50                                   ` Junio C Hamano
@ 2019-03-12 22:35                                   ` Johannes Schindelin
  2019-03-12 23:40                                     ` Thomas Gummerer
  1 sibling, 1 reply; 106+ messages in thread
From: Johannes Schindelin @ 2019-03-12 22:35 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: Junio C Hamano, Jeff King, git, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Matthew Kraai

Hi Thomas,

On Mon, 11 Mar 2019, Thomas Gummerer wrote:

> Passing the pathspec by value is potentially confusing, as the copy is
> only a shallow copy, so save the overhead of the copy, and pass the
> pathspec struct as a pointer.

Not only confusing, but also wasteful ;-)

> In addition use copy_pathspec to copy the pathspec into
> rev.prune_data, so the copy is a proper deep copy, and owned by the
> revision API, as that's what the API expects.

Good.

> [...]
> diff --git a/builtin/stash.c b/builtin/stash.c
> index 2f29d037c8..e0528d4cc8 100644
> --- a/builtin/stash.c
> +++ b/builtin/stash.c
> @@ -826,11 +826,11 @@ static int store_stash(int argc, const char **argv, const char *prefix)
>  }
>  
>  static void add_pathspecs(struct argv_array *args,
> -			  struct pathspec ps) {
> +			  const struct pathspec *ps) {

I see that you added the `const` keyword. While it does not hurt, I would
probably not have bothered...

> [...]
> @@ -1042,6 +1049,7 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
>  	struct index_state istate = { NULL };
>  
>  	init_revisions(&rev, NULL);
> +	copy_pathspec(&rev.prune_data, ps);

This moved here... because...

>  
>  	set_alternate_index_output(stash_index_path.buf);
>  	if (reset_tree(&info->i_tree, 0, 0)) {

... this `if` block could jump to...


> @@ -1050,7 +1058,6 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
>  	}
>  	set_alternate_index_output(NULL);
>  
> -	rev.prune_data = ps;
>  	rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
>  	rev.diffopt.format_callback = add_diff_to_buf;
>  	rev.diffopt.format_callback_data = &diff_output;
> @@ -1089,12 +1096,13 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)

... this point (the `done:` label is *just* one line further up, and this
is a static diff, so we cannot just increase the context when we need to
see more, unlike, say, GitHub PRs) and...

>  	discard_index(&istate);
>  	UNLEAK(rev);
>  	object_array_clear(&rev.pending);
> +	clear_pathspec(&rev.prune_data);

... we add this call here.

However, we would not have needed to move the initialization of
`rev.prune_data`, I don't think, because `init_revision()` zeros the
entire struct, including `prune_data`, which would have made
`clear_pathspec()` safe to call, too.

Both of my comments need no action, and the rest of the patch looks good
to me.

Thank you for going through this!
Dscho

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

* Re: [PATCH v2] stash: pass pathspec as pointer
  2019-03-12 22:35                                   ` Johannes Schindelin
@ 2019-03-12 23:40                                     ` Thomas Gummerer
  2019-03-13  1:47                                       ` Junio C Hamano
  2019-03-13 22:14                                       ` Johannes Schindelin
  0 siblings, 2 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-03-12 23:40 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Junio C Hamano, Jeff King, git, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Matthew Kraai

On 03/12, Johannes Schindelin wrote:
> Hi Thomas,
> 
> On Mon, 11 Mar 2019, Thomas Gummerer wrote:
> 
> > Passing the pathspec by value is potentially confusing, as the copy is
> > only a shallow copy, so save the overhead of the copy, and pass the
> > pathspec struct as a pointer.
> 
> Not only confusing, but also wasteful ;-)
> 
> > In addition use copy_pathspec to copy the pathspec into
> > rev.prune_data, so the copy is a proper deep copy, and owned by the
> > revision API, as that's what the API expects.
> 
> Good.
> 
> > [...]
> > diff --git a/builtin/stash.c b/builtin/stash.c
> > index 2f29d037c8..e0528d4cc8 100644
> > --- a/builtin/stash.c
> > +++ b/builtin/stash.c
> > @@ -826,11 +826,11 @@ static int store_stash(int argc, const char **argv, const char *prefix)
> >  }
> >  
> >  static void add_pathspecs(struct argv_array *args,
> > -			  struct pathspec ps) {
> > +			  const struct pathspec *ps) {
> 
> I see that you added the `const` keyword. While it does not hurt, I would
> probably not have bothered...

That's fair, I went with what seemed most common in the codebase.
More than half the parameters seem to be using "const struct
pathspec", so that seems to be the more common way if we don't require
the parameter to be modifyable.

$ git grep -F "struct pathspec *" | wc -l
81
$ git grep -F "const struct pathspec *" | wc -l
67


> > [...]
> > @@ -1042,6 +1049,7 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
> >  	struct index_state istate = { NULL };
> >  
> >  	init_revisions(&rev, NULL);
> > +	copy_pathspec(&rev.prune_data, ps);
> 
> This moved here... because...
> 
> >  
> >  	set_alternate_index_output(stash_index_path.buf);
> >  	if (reset_tree(&info->i_tree, 0, 0)) {
> 
> ... this `if` block could jump to...
> 
> 
> > @@ -1050,7 +1058,6 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
> >  	}
> >  	set_alternate_index_output(NULL);
> >  
> > -	rev.prune_data = ps;
> >  	rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
> >  	rev.diffopt.format_callback = add_diff_to_buf;
> >  	rev.diffopt.format_callback_data = &diff_output;
> > @@ -1089,12 +1096,13 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
> 
> ... this point (the `done:` label is *just* one line further up, and this
> is a static diff, so we cannot just increase the context when we need to
> see more, unlike, say, GitHub PRs) and...
> 
> >  	discard_index(&istate);
> >  	UNLEAK(rev);
> >  	object_array_clear(&rev.pending);
> > +	clear_pathspec(&rev.prune_data);
> 
> ... we add this call here.
> 
> However, we would not have needed to move the initialization of
> `rev.prune_data`, I don't think, because `init_revision()` zeros the
> entire struct, including `prune_data`, which would have made
> `clear_pathspec()` safe to call, too.

'clear_pathspec()' doesn't actually check whether the parameter passed
to it is NULL or not before dereferencing it.  The first few lines of
the function are:

	void clear_pathspec(struct pathspec *pathspec)
	{
		int i, j;

		for (i = 0; i < pathspec->nr; i++) {
		[...]

So I think moving the 'copy_pathspec()' earlier is actually required.
It may make sense to make 'clear_pathspec()' safe to call with a NULL
pointer, dunno.

> Both of my comments need no action, and the rest of the patch looks good
> to me.

Thanks for your review!

> Thank you for going through this!
> Dscho

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

* Re: [PATCH v2] stash: pass pathspec as pointer
  2019-03-12 23:40                                     ` Thomas Gummerer
@ 2019-03-13  1:47                                       ` Junio C Hamano
  2019-03-13 22:14                                       ` Johannes Schindelin
  1 sibling, 0 replies; 106+ messages in thread
From: Junio C Hamano @ 2019-03-13  1:47 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: Johannes Schindelin, Jeff King, git, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Matthew Kraai

Thomas Gummerer <t.gummerer@gmail.com> writes:

>> I see that you added the `const` keyword. While it does not hurt, I would
>> probably not have bothered...
>
> That's fair, I went with what seemed most common in the codebase.
> More than half the parameters seem to be using "const struct
> pathspec", so that seems to be the more common way if we don't require
> the parameter to be modifyable.

Yes, when you prepare a struct at a callsite and pass it thru a long
callchain, it is very helpful to both humans and compilers reading
the code to declare that the structure would not be modified, if the
code indeed keeps it constant.  A caller that used to passed the
structure by value certainly hasn't been expecting the callee would
modify its contents and it needs to read back the updated value, so
I find that most of these constifing, if not all, very much in line
with the original's spirit.

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

* Re: [PATCH v2] stash: pass pathspec as pointer
  2019-03-12 23:40                                     ` Thomas Gummerer
  2019-03-13  1:47                                       ` Junio C Hamano
@ 2019-03-13 22:14                                       ` Johannes Schindelin
  2019-03-15 22:33                                         ` Thomas Gummerer
  1 sibling, 1 reply; 106+ messages in thread
From: Johannes Schindelin @ 2019-03-13 22:14 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: Junio C Hamano, Jeff King, git, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Matthew Kraai

Hi Thomas,

On Tue, 12 Mar 2019, Thomas Gummerer wrote:

> On 03/12, Johannes Schindelin wrote:
> 
> > On Mon, 11 Mar 2019, Thomas Gummerer wrote:
> > > [...]
> > > @@ -1042,6 +1049,7 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
> > >  	struct index_state istate = { NULL };
> > >  
> > >  	init_revisions(&rev, NULL);
> > > +	copy_pathspec(&rev.prune_data, ps);
> > 
> > This moved here... because...
> > 
> > >  
> > >  	set_alternate_index_output(stash_index_path.buf);
> > >  	if (reset_tree(&info->i_tree, 0, 0)) {
> > 
> > ... this `if` block could jump to...
> > 
> > 
> > > @@ -1050,7 +1058,6 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
> > >  	}
> > >  	set_alternate_index_output(NULL);
> > >  
> > > -	rev.prune_data = ps;
> > >  	rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
> > >  	rev.diffopt.format_callback = add_diff_to_buf;
> > >  	rev.diffopt.format_callback_data = &diff_output;
> > > @@ -1089,12 +1096,13 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps)
> > 
> > ... this point (the `done:` label is *just* one line further up, and this
> > is a static diff, so we cannot just increase the context when we need to
> > see more, unlike, say, GitHub PRs) and...
> > 
> > >  	discard_index(&istate);
> > >  	UNLEAK(rev);
> > >  	object_array_clear(&rev.pending);
> > > +	clear_pathspec(&rev.prune_data);
> > 
> > ... we add this call here.
> > 
> > However, we would not have needed to move the initialization of
> > `rev.prune_data`, I don't think, because `init_revision()` zeros the
> > entire struct, including `prune_data`, which would have made
> > `clear_pathspec()` safe to call, too.
> 
> 'clear_pathspec()' doesn't actually check whether the parameter passed
> to it is NULL or not before dereferencing it.

In this case, it does not need to check for NULL, as `&rev.prune_data`
will always be non-NULL: `rev`'s `prune_data` field is of type `struct
patchspec`, i.e. *not* a pointer (in which case the type would be `struct
pathspec *`). See for yourself:

	https://github.com/git/git/blob/v2.21.0/revision.h#L91

Ciao,
Dscho

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

* regression in new built-in stash + fsmonitor (was Re: [PATCH v13 11/27] stash: convert apply to builtin)
  2019-02-25 23:16                   ` [PATCH v13 11/27] stash: convert apply to builtin Thomas Gummerer
@ 2019-03-14 13:19                     ` Ævar Arnfjörð Bjarmason
  2019-03-14 15:20                       ` Johannes Schindelin
  0 siblings, 1 reply; 106+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2019-03-14 13:19 UTC (permalink / raw)
  To: Thomas Gummerer
  Cc: git, Junio C Hamano, Johannes Schindelin,
	Paul-Sebastian Ungureanu, SZEDER Gábor, Joel Teichroeb


On Tue, Feb 26 2019, Thomas Gummerer wrote:

> From: Joel Teichroeb <joel@teichroeb.net>
>
> Add a builtin helper for performing stash commands. Converting
> all at once proved hard to review, so starting with just apply
> lets conversion get started without the other commands being
> finished.
>
> The helper is being implemented as a drop in replacement for
> stash so that when it is complete it can simply be renamed and
> the shell script deleted.
>
> Delete the contents of the apply_stash shell function and replace
> it with a call to stash--helper apply until pop is also
> converted.

This

    GIT_TEST_FSMONITOR=$PWD/t7519/fsmonitor-all ./t3420-rebase-autostash.sh

Now fails, which bisects to 8a0fc8d19d ("stash: convert apply to
builtin", 2019-02-25).

Tested on both a CentOS 6 & modern Debian testing machine:

    + git rebase -i --autostash HEAD^
    Created autostash: 5cd734b
    HEAD is now at 0c4d2f1 third commit
    hint: Waiting for your editor to close the file...
    error: There was a problem with the editor '"$FAKE_EDITOR"'.
    Applied autostash.
    + exit_code=1
    + test 1 -eq 0
    + test_match_signal 13 1
    + test 1 = 141
    + test 1 = 269
    + return 1
    + test 1 -gt 129
    + test 1 -eq 127
    + test 1 -eq 126
    + return 0
    + rm -f abort-editor.sh
    + echo conflicting-content
    + test_cmp expected file0
    + diff -u expected file0
    --- expected    2019-03-14 13:19:08.212215263 +0000
    +++ file0       2019-03-14 13:19:08.196215250 +0000
    @@ -1 +1 @@
    -conflicting-content
    +uncommitted-content
    error: last command exited with $?=1
    not ok 36 - autostash is saved on editor failure with conflict

Are you able to reproduce this? And if so I suggest running the test
suite with some of the other GIT_TEST_* modes documented in
t/README. Maybe it'll turn up something else...

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

* Re: regression in new built-in stash + fsmonitor (was Re: [PATCH v13 11/27] stash: convert apply to builtin)
  2019-03-14 13:19                     ` regression in new built-in stash + fsmonitor (was Re: [PATCH v13 11/27] stash: convert apply to builtin) Ævar Arnfjörð Bjarmason
@ 2019-03-14 15:20                       ` Johannes Schindelin
  2019-03-14 15:40                         ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 106+ messages in thread
From: Johannes Schindelin @ 2019-03-14 15:20 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Thomas Gummerer, git, Junio C Hamano, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Joel Teichroeb

[-- Attachment #1: Type: text/plain, Size: 2112 bytes --]

Hi Ævar,

On Thu, 14 Mar 2019, Ævar Arnfjörð Bjarmason wrote:

> On Tue, Feb 26 2019, Thomas Gummerer wrote:
> 
> > From: Joel Teichroeb <joel@teichroeb.net>
> >
> > Add a builtin helper for performing stash commands. Converting
> > all at once proved hard to review, so starting with just apply
> > lets conversion get started without the other commands being
> > finished.
> >
> > The helper is being implemented as a drop in replacement for
> > stash so that when it is complete it can simply be renamed and
> > the shell script deleted.
> >
> > Delete the contents of the apply_stash shell function and replace
> > it with a call to stash--helper apply until pop is also
> > converted.
> 
> This
> 
>     GIT_TEST_FSMONITOR=$PWD/t7519/fsmonitor-all ./t3420-rebase-autostash.sh
> 
> Now fails, which bisects to 8a0fc8d19d ("stash: convert apply to
> builtin", 2019-02-25).
> 
> Tested on both a CentOS 6 & modern Debian testing machine:
> 
>     + git rebase -i --autostash HEAD^
>     Created autostash: 5cd734b
>     HEAD is now at 0c4d2f1 third commit
>     hint: Waiting for your editor to close the file...
>     error: There was a problem with the editor '"$FAKE_EDITOR"'.
>     Applied autostash.
>     + exit_code=1
>     + test 1 -eq 0
>     + test_match_signal 13 1
>     + test 1 = 141
>     + test 1 = 269
>     + return 1
>     + test 1 -gt 129
>     + test 1 -eq 127
>     + test 1 -eq 126
>     + return 0
>     + rm -f abort-editor.sh
>     + echo conflicting-content
>     + test_cmp expected file0
>     + diff -u expected file0
>     --- expected    2019-03-14 13:19:08.212215263 +0000
>     +++ file0       2019-03-14 13:19:08.196215250 +0000
>     @@ -1 +1 @@
>     -conflicting-content
>     +uncommitted-content
>     error: last command exited with $?=1
>     not ok 36 - autostash is saved on editor failure with conflict
> 
> Are you able to reproduce this? And if so I suggest running the test
> suite with some of the other GIT_TEST_* modes documented in
> t/README. Maybe it'll turn up something else...

Yep, totally can reproduce it :-(

I'll try to investigate a bit,
Dscho

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

* Re: regression in new built-in stash + fsmonitor (was Re: [PATCH v13 11/27] stash: convert apply to builtin)
  2019-03-14 15:20                       ` Johannes Schindelin
@ 2019-03-14 15:40                         ` Ævar Arnfjörð Bjarmason
  2019-03-14 22:45                           ` Johannes Schindelin
  0 siblings, 1 reply; 106+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2019-03-14 15:40 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Thomas Gummerer, git, Junio C Hamano, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Joel Teichroeb


On Thu, Mar 14 2019, Johannes Schindelin wrote:

> Hi Ævar,
>
> On Thu, 14 Mar 2019, Ævar Arnfjörð Bjarmason wrote:
>
>> On Tue, Feb 26 2019, Thomas Gummerer wrote:
>>
>> > From: Joel Teichroeb <joel@teichroeb.net>
>> >
>> > Add a builtin helper for performing stash commands. Converting
>> > all at once proved hard to review, so starting with just apply
>> > lets conversion get started without the other commands being
>> > finished.
>> >
>> > The helper is being implemented as a drop in replacement for
>> > stash so that when it is complete it can simply be renamed and
>> > the shell script deleted.
>> >
>> > Delete the contents of the apply_stash shell function and replace
>> > it with a call to stash--helper apply until pop is also
>> > converted.
>>
>> This
>>
>>     GIT_TEST_FSMONITOR=$PWD/t7519/fsmonitor-all ./t3420-rebase-autostash.sh
>>
>> Now fails, which bisects to 8a0fc8d19d ("stash: convert apply to
>> builtin", 2019-02-25).
>>
>> Tested on both a CentOS 6 & modern Debian testing machine:
>>
>>     + git rebase -i --autostash HEAD^
>>     Created autostash: 5cd734b
>>     HEAD is now at 0c4d2f1 third commit
>>     hint: Waiting for your editor to close the file...
>>     error: There was a problem with the editor '"$FAKE_EDITOR"'.
>>     Applied autostash.
>>     + exit_code=1
>>     + test 1 -eq 0
>>     + test_match_signal 13 1
>>     + test 1 = 141
>>     + test 1 = 269
>>     + return 1
>>     + test 1 -gt 129
>>     + test 1 -eq 127
>>     + test 1 -eq 126
>>     + return 0
>>     + rm -f abort-editor.sh
>>     + echo conflicting-content
>>     + test_cmp expected file0
>>     + diff -u expected file0
>>     --- expected    2019-03-14 13:19:08.212215263 +0000
>>     +++ file0       2019-03-14 13:19:08.196215250 +0000
>>     @@ -1 +1 @@
>>     -conflicting-content
>>     +uncommitted-content
>>     error: last command exited with $?=1
>>     not ok 36 - autostash is saved on editor failure with conflict
>>
>> Are you able to reproduce this? And if so I suggest running the test
>> suite with some of the other GIT_TEST_* modes documented in
>> t/README. Maybe it'll turn up something else...
>
> Yep, totally can reproduce it :-(

In the meantime I did a build with "next" (so stash-in-C) using the
standard test mode and these:

    (cd t && GIT_TEST_GETTEXT_POISON=true /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
    (cd t && GIT_TEST_FSMONITOR=$PWD/t7519/fsmonitor-all GIT_SKIP_TESTS="t3404.8 t3420.36" /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
    (cd t && GIT_TEST_SPLIT_INDEX=true /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
    (cd t && GIT_TEST_FULL_IN_PACK_ARRAY=true GIT_TEST_OE_SIZE=10 /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
    (cd t && GIT_TEST_COMMIT_GRAPH=true /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
    (cd t && GIT_TEST_MULTI_PACK_INDEX=true /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
    (cd t && GIT_TEST_STASH_USE_BUILTIN=false /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
    (cd t && GIT_TEST_CHECK_COLLISIONS=false /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)

Only this specific test failed.

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

* Re: regression in new built-in stash + fsmonitor (was Re: [PATCH v13 11/27] stash: convert apply to builtin)
  2019-03-14 15:40                         ` Ævar Arnfjörð Bjarmason
@ 2019-03-14 22:45                           ` Johannes Schindelin
  2019-03-14 23:39                             ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 106+ messages in thread
From: Johannes Schindelin @ 2019-03-14 22:45 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Thomas Gummerer, git, Junio C Hamano, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Joel Teichroeb

[-- Attachment #1: Type: text/plain, Size: 5666 bytes --]

Hi Ævar,

On Thu, 14 Mar 2019, Ævar Arnfjörð Bjarmason wrote:

> On Thu, Mar 14 2019, Johannes Schindelin wrote:
> 
> > On Thu, 14 Mar 2019, Ævar Arnfjörð Bjarmason wrote:
> >
> >> On Tue, Feb 26 2019, Thomas Gummerer wrote:
> >>
> >> > From: Joel Teichroeb <joel@teichroeb.net>
> >> >
> >> > Add a builtin helper for performing stash commands. Converting
> >> > all at once proved hard to review, so starting with just apply
> >> > lets conversion get started without the other commands being
> >> > finished.
> >> >
> >> > The helper is being implemented as a drop in replacement for
> >> > stash so that when it is complete it can simply be renamed and
> >> > the shell script deleted.
> >> >
> >> > Delete the contents of the apply_stash shell function and replace
> >> > it with a call to stash--helper apply until pop is also
> >> > converted.
> >>
> >> This
> >>
> >>     GIT_TEST_FSMONITOR=$PWD/t7519/fsmonitor-all ./t3420-rebase-autostash.sh
> >>
> >> Now fails, which bisects to 8a0fc8d19d ("stash: convert apply to
> >> builtin", 2019-02-25).
> >>
> >> Tested on both a CentOS 6 & modern Debian testing machine:
> >>
> >>     + git rebase -i --autostash HEAD^
> >>     Created autostash: 5cd734b
> >>     HEAD is now at 0c4d2f1 third commit
> >>     hint: Waiting for your editor to close the file...
> >>     error: There was a problem with the editor '"$FAKE_EDITOR"'.
> >>     Applied autostash.
> >>     + exit_code=1
> >>     + test 1 -eq 0
> >>     + test_match_signal 13 1
> >>     + test 1 = 141
> >>     + test 1 = 269
> >>     + return 1
> >>     + test 1 -gt 129
> >>     + test 1 -eq 127
> >>     + test 1 -eq 126
> >>     + return 0
> >>     + rm -f abort-editor.sh
> >>     + echo conflicting-content
> >>     + test_cmp expected file0
> >>     + diff -u expected file0
> >>     --- expected    2019-03-14 13:19:08.212215263 +0000
> >>     +++ file0       2019-03-14 13:19:08.196215250 +0000
> >>     @@ -1 +1 @@
> >>     -conflicting-content
> >>     +uncommitted-content
> >>     error: last command exited with $?=1
> >>     not ok 36 - autostash is saved on editor failure with conflict
> >>
> >> Are you able to reproduce this? And if so I suggest running the test
> >> suite with some of the other GIT_TEST_* modes documented in
> >> t/README. Maybe it'll turn up something else...
> >
> > Yep, totally can reproduce it :-(

Well, isn't this exciting: we found not a bug in the built-in stash (even
if Junio probably expected yet another one), but an fsmonitor one! Even
better, I think this might be the bug that Alex Vandiver was chasing and
that he talked about at the Contributors' Summit last year in Barcelona.

The symptom is that cache entries are sometimes considered up to date,
when they really are not.

And the reason is that the fsmonitor has this honking global flag
`has_run_once` (it is not really global, it is `static` to
`refresh_fsmonitor()`, but that's the same for all practical purposes, as
it is *not* specific to one `struct index_state`), which was kind of okay
as long as `the_index` was used implicitly by everything.

Except it was not okay when `discard_index()` (or `discard_cache()`) was
called: in that case, the flag was not re-set. And re-set it needs to be,
in that case, otherwise the fsmonitor is not asked which entries need to
be updated.

I saw this pretty early on in my investigation and marked it up for a
follow-up task, wasting hours of investigation by not believing that this
could be the culprit of the bug you described. I did not believe it
because `git stash apply` is *spawned*, so there is not even an index that
needs to be discarded (I thought; more on that one later).

It is quite curious that this is the only occasion that our test suite
covers that particular part of the fsmonitor...

I do not really want to rely on implementation details of the rebase to verify
that the fsmonitor is queried again (and, crucially, resets the
FSMONITOR_VALID flag of the file(s) indicated as out of date) after the index
is discarded and re-read.

I guess the best bet is to extend `t/helper/test-read-cache.c` to optionally
output information about a specific cache entry, then refresh it, and then run
that test helper with fsmonitor-all (which should mark anything as modified,
all the time). That should verify that the fix works.

I did exactly that, and pushed the result to https://github.com/dscho/git
as `fix-fsmonitor` branch.

Could I ask you to test that one (it is based off of Git for Windows'
`master`, but that should compile cleanly for you)?

For now, I am a bit spent, so I'll leave the rest for tomorrow.

> In the meantime I did a build with "next" (so stash-in-C) using the
> standard test mode and these:
> 
>     (cd t && GIT_TEST_GETTEXT_POISON=true /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
>     (cd t && GIT_TEST_FSMONITOR=$PWD/t7519/fsmonitor-all GIT_SKIP_TESTS="t3404.8 t3420.36" /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
>     (cd t && GIT_TEST_SPLIT_INDEX=true /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
>     (cd t && GIT_TEST_FULL_IN_PACK_ARRAY=true GIT_TEST_OE_SIZE=10 /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
>     (cd t && GIT_TEST_COMMIT_GRAPH=true /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
>     (cd t && GIT_TEST_MULTI_PACK_INDEX=true /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
>     (cd t && GIT_TEST_STASH_USE_BUILTIN=false /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
>     (cd t && GIT_TEST_CHECK_COLLISIONS=false /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
> 
> Only this specific test failed.

Well, good!

Thank you for getting the ball rolling!
Dscho

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

* Re: regression in new built-in stash + fsmonitor (was Re: [PATCH v13 11/27] stash: convert apply to builtin)
  2019-03-14 22:45                           ` Johannes Schindelin
@ 2019-03-14 23:39                             ` Ævar Arnfjörð Bjarmason
  2019-03-15  2:23                               ` Ben Peart
  0 siblings, 1 reply; 106+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2019-03-14 23:39 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Thomas Gummerer, git, Junio C Hamano, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Joel Teichroeb, Ben Peart


On Thu, Mar 14 2019, Johannes Schindelin wrote:

> Hi Ævar,
>
> On Thu, 14 Mar 2019, Ævar Arnfjörð Bjarmason wrote:
>
>> On Thu, Mar 14 2019, Johannes Schindelin wrote:
>>
>> > On Thu, 14 Mar 2019, Ævar Arnfjörð Bjarmason wrote:
>> >
>> >> On Tue, Feb 26 2019, Thomas Gummerer wrote:
>> >>
>> >> > From: Joel Teichroeb <joel@teichroeb.net>
>> >> >
>> >> > Add a builtin helper for performing stash commands. Converting
>> >> > all at once proved hard to review, so starting with just apply
>> >> > lets conversion get started without the other commands being
>> >> > finished.
>> >> >
>> >> > The helper is being implemented as a drop in replacement for
>> >> > stash so that when it is complete it can simply be renamed and
>> >> > the shell script deleted.
>> >> >
>> >> > Delete the contents of the apply_stash shell function and replace
>> >> > it with a call to stash--helper apply until pop is also
>> >> > converted.
>> >>
>> >> This
>> >>
>> >>     GIT_TEST_FSMONITOR=$PWD/t7519/fsmonitor-all ./t3420-rebase-autostash.sh
>> >>
>> >> Now fails, which bisects to 8a0fc8d19d ("stash: convert apply to
>> >> builtin", 2019-02-25).
>> >>
>> >> Tested on both a CentOS 6 & modern Debian testing machine:
>> >>
>> >>     + git rebase -i --autostash HEAD^
>> >>     Created autostash: 5cd734b
>> >>     HEAD is now at 0c4d2f1 third commit
>> >>     hint: Waiting for your editor to close the file...
>> >>     error: There was a problem with the editor '"$FAKE_EDITOR"'.
>> >>     Applied autostash.
>> >>     + exit_code=1
>> >>     + test 1 -eq 0
>> >>     + test_match_signal 13 1
>> >>     + test 1 = 141
>> >>     + test 1 = 269
>> >>     + return 1
>> >>     + test 1 -gt 129
>> >>     + test 1 -eq 127
>> >>     + test 1 -eq 126
>> >>     + return 0
>> >>     + rm -f abort-editor.sh
>> >>     + echo conflicting-content
>> >>     + test_cmp expected file0
>> >>     + diff -u expected file0
>> >>     --- expected    2019-03-14 13:19:08.212215263 +0000
>> >>     +++ file0       2019-03-14 13:19:08.196215250 +0000
>> >>     @@ -1 +1 @@
>> >>     -conflicting-content
>> >>     +uncommitted-content
>> >>     error: last command exited with $?=1
>> >>     not ok 36 - autostash is saved on editor failure with conflict
>> >>
>> >> Are you able to reproduce this? And if so I suggest running the test
>> >> suite with some of the other GIT_TEST_* modes documented in
>> >> t/README. Maybe it'll turn up something else...
>> >
>> > Yep, totally can reproduce it :-(
>
> Well, isn't this exciting: we found not a bug in the built-in stash (even
> if Junio probably expected yet another one), but an fsmonitor one! Even
> better, I think this might be the bug that Alex Vandiver was chasing and
> that he talked about at the Contributors' Summit last year in Barcelona.
>
> The symptom is that cache entries are sometimes considered up to date,
> when they really are not.
>
> And the reason is that the fsmonitor has this honking global flag
> `has_run_once` (it is not really global, it is `static` to
> `refresh_fsmonitor()`, but that's the same for all practical purposes, as
> it is *not* specific to one `struct index_state`), which was kind of okay
> as long as `the_index` was used implicitly by everything.
>
> Except it was not okay when `discard_index()` (or `discard_cache()`) was
> called: in that case, the flag was not re-set. And re-set it needs to be,
> in that case, otherwise the fsmonitor is not asked which entries need to
> be updated.
>
> I saw this pretty early on in my investigation and marked it up for a
> follow-up task, wasting hours of investigation by not believing that this
> could be the culprit of the bug you described. I did not believe it
> because `git stash apply` is *spawned*, so there is not even an index that
> needs to be discarded (I thought; more on that one later).
>
> It is quite curious that this is the only occasion that our test suite
> covers that particular part of the fsmonitor...
>
> I do not really want to rely on implementation details of the rebase to verify
> that the fsmonitor is queried again (and, crucially, resets the
> FSMONITOR_VALID flag of the file(s) indicated as out of date) after the index
> is discarded and re-read.
>
> I guess the best bet is to extend `t/helper/test-read-cache.c` to optionally
> output information about a specific cache entry, then refresh it, and then run
> that test helper with fsmonitor-all (which should mark anything as modified,
> all the time). That should verify that the fix works.
>
> I did exactly that, and pushed the result to https://github.com/dscho/git
> as `fix-fsmonitor` branch.
>
> Could I ask you to test that one (it is based off of Git for Windows'
> `master`, but that should compile cleanly for you)?
>
> For now, I am a bit spent, so I'll leave the rest for tomorrow.

It fixes not just this issue, but now the whole test suite passes with
GIT_TEST_FSMONITOR, i.e. this test that's been failing for ~2 years also
works now:
https://public-inbox.org/git/87k1vwn9qe.fsf@evledraar.gmail.com/

>> In the meantime I did a build with "next" (so stash-in-C) using the
>> standard test mode and these:
>>
>>     (cd t && GIT_TEST_GETTEXT_POISON=true /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
>>     (cd t && GIT_TEST_FSMONITOR=$PWD/t7519/fsmonitor-all GIT_SKIP_TESTS="t3404.8 t3420.36" /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
>>     (cd t && GIT_TEST_SPLIT_INDEX=true /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
>>     (cd t && GIT_TEST_FULL_IN_PACK_ARRAY=true GIT_TEST_OE_SIZE=10 /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
>>     (cd t && GIT_TEST_COMMIT_GRAPH=true /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
>>     (cd t && GIT_TEST_MULTI_PACK_INDEX=true /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
>>     (cd t && GIT_TEST_STASH_USE_BUILTIN=false /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
>>     (cd t && GIT_TEST_CHECK_COLLISIONS=false /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
>>
>> Only this specific test failed.
>
> Well, good!
>
> Thank you for getting the ball rolling!
> Dscho

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

* RE: regression in new built-in stash + fsmonitor (was Re: [PATCH v13 11/27] stash: convert apply to builtin)
  2019-03-14 23:39                             ` Ævar Arnfjörð Bjarmason
@ 2019-03-15  2:23                               ` Ben Peart
  0 siblings, 0 replies; 106+ messages in thread
From: Ben Peart @ 2019-03-15  2:23 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason, Johannes Schindelin
  Cc: Thomas Gummerer, git, gitster, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Joel Teichroeb

> It fixes not just this issue, but now the whole test suite passes with
> GIT_TEST_FSMONITOR, i.e. this test that's been failing for ~2 years also
> works now:
> https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fpubli
> c-
> inbox.org%2Fgit%2F87k1vwn9qe.fsf%40evledraar.gmail.com%2F&amp;data=
> 02%7C01%7CBen.Peart%40microsoft.com%7C758f6272cb9741291c0208d6a8
> d650b4%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C6368820357
> 67290383&amp;sdata=H4J7xguBJVxanMfn4y0I0HsXBNYcrMS9IrwHn3aWwO
> U%3D&amp;reserved=0
> 
> >> In the meantime I did a build with "next" (so stash-in-C) using the
> >> standard test mode and these:
> >>
> >>     (cd t && GIT_TEST_GETTEXT_POISON=true /usr/bin/prove
> $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
> >>     (cd t && GIT_TEST_FSMONITOR=$PWD/t7519/fsmonitor-all
> GIT_SKIP_TESTS="t3404.8 t3420.36" /usr/bin/prove
> $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
> >>     (cd t && GIT_TEST_SPLIT_INDEX=true /usr/bin/prove
> $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
> >>     (cd t && GIT_TEST_FULL_IN_PACK_ARRAY=true GIT_TEST_OE_SIZE=10
> /usr/bin/prove $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
> >>     (cd t && GIT_TEST_COMMIT_GRAPH=true /usr/bin/prove
> $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
> >>     (cd t && GIT_TEST_MULTI_PACK_INDEX=true /usr/bin/prove
> $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
> >>     (cd t && GIT_TEST_STASH_USE_BUILTIN=false /usr/bin/prove
> $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
> >>     (cd t && GIT_TEST_CHECK_COLLISIONS=false /usr/bin/prove
> >> $BKNG_GIT_HARNESS_OPTIONS t[0-9]*.sh)
> >>
> >> Only this specific test failed.
> >
> > Well, good!
> >
> > Thank you for getting the ball rolling!

Awesome find and fix.  Thanks for chasing this down and CC'ing me so I am aware.

> > Dscho

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

* Re: [PATCH v2] stash: pass pathspec as pointer
  2019-03-13 22:14                                       ` Johannes Schindelin
@ 2019-03-15 22:33                                         ` Thomas Gummerer
  0 siblings, 0 replies; 106+ messages in thread
From: Thomas Gummerer @ 2019-03-15 22:33 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Junio C Hamano, Jeff King, git, Paul-Sebastian Ungureanu,
	SZEDER Gábor, Matthew Kraai

On 03/13, Johannes Schindelin wrote:
> Hi Thomas,
> 
> On Tue, 12 Mar 2019, Thomas Gummerer wrote:
> 
> > On 03/12, Johannes Schindelin wrote:
> > > However, we would not have needed to move the initialization of
> > > `rev.prune_data`, I don't think, because `init_revision()` zeros the
> > > entire struct, including `prune_data`, which would have made
> > > `clear_pathspec()` safe to call, too.
> > 
> > 'clear_pathspec()' doesn't actually check whether the parameter passed
> > to it is NULL or not before dereferencing it.
> 
> In this case, it does not need to check for NULL, as `&rev.prune_data`
> will always be non-NULL: `rev`'s `prune_data` field is of type `struct
> patchspec`, i.e. *not* a pointer (in which case the type would be `struct
> pathspec *`). See for yourself:
> 
> 	https://github.com/git/git/blob/v2.21.0/revision.h#L91

Doh, you're right of course, I totally missed that.  Thanks for the
pointer, and sorry for the noise!

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

end of thread, other threads:[~2019-03-15 22:33 UTC | newest]

Thread overview: 106+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <https://public-inbox.org/git/cover.1542925164.git.ungureanupaulsebastian@gmail.com/>
2018-12-20 19:44 ` [PATCH v12 00/26] Convert "git stash" to C builtin Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 01/26] sha1-name.c: add `get_oidf()` which acts like `get_oid()` Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 02/26] strbuf.c: add `strbuf_join_argv()` Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 03/26] strbuf.c: add `strbuf_insertf()` and `strbuf_vinsertf()` Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 04/26] ident: add the ability to provide a "fallback identity" Paul-Sebastian Ungureanu
2018-12-26 21:21     ` Junio C Hamano
2018-12-27 21:24       ` Johannes Schindelin
2018-12-28 19:40         ` Junio C Hamano
2018-12-20 19:44   ` [PATCH v12 05/26] stash: improve option parsing test coverage Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 06/26] t3903: modernize style Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 07/26] stash: rename test cases to be more descriptive Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 08/26] stash: add tests for `git stash show` config Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 09/26] stash: mention options in `show` synopsis Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 10/26] stash: convert apply to builtin Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 11/26] stash: convert drop and clear " Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 12/26] stash: convert branch " Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 13/26] stash: convert pop " Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 14/26] stash: convert list " Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 15/26] stash: convert show " Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 16/26] stash: convert store " Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 17/26] stash: convert create " Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 18/26] stash: convert push " Paul-Sebastian Ungureanu
2019-02-08 11:30     ` SZEDER Gábor
2019-02-10 22:17       ` Thomas Gummerer
2019-02-11  1:13         ` SZEDER Gábor
2019-02-12 23:18           ` Thomas Gummerer
2019-02-19  0:23             ` SZEDER Gábor
2019-02-19 10:47               ` Johannes Schindelin
2019-02-19 19:59                 ` Junio C Hamano
2019-02-20 21:01                   ` Johannes Schindelin
2019-02-19 23:59                 ` Thomas Gummerer
2019-02-20  4:37                   ` Junio C Hamano
2019-02-20 21:10                     ` Johannes Schindelin
2019-02-20 22:30                     ` Thomas Gummerer
2019-02-25 23:16                 ` [PATCH v13 00/27] Convert "git stash" to C builtin Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 01/27] sha1-name.c: add `get_oidf()` which acts like `get_oid()` Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 02/27] strbuf.c: add `strbuf_join_argv()` Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 03/27] strbuf.c: add `strbuf_insertf()` and `strbuf_vinsertf()` Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 04/27] ident: add the ability to provide a "fallback identity" Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 05/27] stash: improve option parsing test coverage Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 06/27] t3903: modernize style Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 07/27] t3903: add test for --intent-to-add file Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 08/27] stash: rename test cases to be more descriptive Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 09/27] stash: add tests for `git stash show` config Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 10/27] stash: mention options in `show` synopsis Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 11/27] stash: convert apply to builtin Thomas Gummerer
2019-03-14 13:19                     ` regression in new built-in stash + fsmonitor (was Re: [PATCH v13 11/27] stash: convert apply to builtin) Ævar Arnfjörð Bjarmason
2019-03-14 15:20                       ` Johannes Schindelin
2019-03-14 15:40                         ` Ævar Arnfjörð Bjarmason
2019-03-14 22:45                           ` Johannes Schindelin
2019-03-14 23:39                             ` Ævar Arnfjörð Bjarmason
2019-03-15  2:23                               ` Ben Peart
2019-02-25 23:16                   ` [PATCH v13 12/27] stash: convert drop and clear to builtin Thomas Gummerer
2019-03-07 19:15                     ` Jeff King
2019-03-09 18:30                       ` Thomas Gummerer
2019-03-10 23:26                         ` Jeff King
2019-03-11  1:40                         ` Junio C Hamano
2019-03-11 21:40                           ` Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 13/27] stash: convert branch " Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 14/27] stash: convert pop " Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 15/27] stash: convert list " Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 16/27] stash: convert show " Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 17/27] stash: convert store " Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 18/27] stash: convert create " Thomas Gummerer
2019-03-07 19:18                     ` Jeff King
2019-03-08 15:30                       ` Johannes Schindelin
2019-03-09 18:26                         ` Thomas Gummerer
2019-03-11  1:47                           ` Junio C Hamano
2019-03-11  7:30                             ` Junio C Hamano
2019-03-11 21:42                               ` Thomas Gummerer
2019-03-11 22:16                                 ` [PATCH v2] stash: pass pathspec as pointer Thomas Gummerer
2019-03-12  6:50                                   ` Junio C Hamano
2019-03-12 22:35                                   ` Johannes Schindelin
2019-03-12 23:40                                     ` Thomas Gummerer
2019-03-13  1:47                                       ` Junio C Hamano
2019-03-13 22:14                                       ` Johannes Schindelin
2019-03-15 22:33                                         ` Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 19/27] stash: convert push to builtin Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 20/27] stash: make push -q quiet Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 21/27] stash: convert save to builtin Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 22/27] stash: optimize `get_untracked_files()` and `check_changes()` Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 23/27] stash: replace all `write-tree` child processes with API calls Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 24/27] stash: convert `stash--helper.c` into `stash.c` Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 25/27] stash: add back the original, scripted `git stash` Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 26/27] stash: optionally use the scripted version again Thomas Gummerer
2019-02-25 23:16                   ` [PATCH v13 27/27] tests: add a special setup where stash.useBuiltin is off Thomas Gummerer
2019-02-26 12:40                   ` [PATCH v13 00/27] Convert "git stash" to C builtin Johannes Schindelin
2019-02-26 20:48                     ` Thomas Gummerer
2019-02-26 21:45                   ` Ævar Arnfjörð Bjarmason
2019-02-26 22:37                     ` Johannes Schindelin
2019-03-03  1:24                   ` Junio C Hamano
2019-03-03  1:25                   ` Junio C Hamano
2019-03-03 10:03                     ` Thomas Gummerer
2018-12-20 19:44   ` [PATCH v12 19/26] stash: make push -q quiet Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 20/26] stash: convert save to builtin Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 21/26] stash: optimize `get_untracked_files()` and `check_changes()` Paul-Sebastian Ungureanu
2019-01-06 22:47     ` Thomas Gummerer
2018-12-20 19:44   ` [PATCH v12 22/26] stash: replace all `write-tree` child processes with API calls Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 23/26] stash: convert `stash--helper.c` into `stash.c` Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 24/26] stash: add back the original, scripted `git stash` Paul-Sebastian Ungureanu
2018-12-20 19:44   ` [PATCH v12 25/26] stash: optionally use the scripted version again Paul-Sebastian Ungureanu
2019-01-06 22:59     ` Thomas Gummerer
2018-12-20 19:44   ` [PATCH v12 26/26] tests: add a special setup where stash.useBuiltin is off Paul-Sebastian Ungureanu
2019-01-03 23:39   ` [PATCH v12 00/26] Convert "git stash" to C builtin Junio C Hamano
2019-01-18 12:06     ` Johannes Schindelin
2019-01-18 17:49       ` Junio C Hamano

git@vger.kernel.org list mirror (unofficial, one of many)

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://public-inbox.org/git
	git clone --mirror http://ou63pmih66umazou.onion/git
	git clone --mirror http://czquwvybam4bgbro.onion/git
	git clone --mirror http://hjrcffqmbrq6wope.onion/git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V1 git git/ https://public-inbox.org/git \
		git@vger.kernel.org
	public-inbox-index git

Example config snippet for mirrors.
Newsgroups are available over NNTP:
	nntp://news.public-inbox.org/inbox.comp.version-control.git
	nntp://ou63pmih66umazou.onion/inbox.comp.version-control.git
	nntp://czquwvybam4bgbro.onion/inbox.comp.version-control.git
	nntp://hjrcffqmbrq6wope.onion/inbox.comp.version-control.git
	nntp://news.gmane.io/gmane.comp.version-control.git
 note: .onion URLs require Tor: https://www.torproject.org/

code repositories for the project(s) associated with this inbox:

	https://80x24.org/mirrors/git.git

AGPL code for this site: git clone https://public-inbox.org/public-inbox.git