git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help  so far)
@ 2019-04-10 17:37 Johannes Schindelin via GitGitGadget
  2019-04-10 17:37 ` [PATCH 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
                   ` (11 more replies)
  0 siblings, 12 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-04-10 17:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

This is the first leg on the long journey to a fully built-in git add -i 
(next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3
[https://github.com/gitgitgadget/git/pull/172], 4
[https://github.com/gitgitgadget/git/pull/173], 5
[https://github.com/gitgitgadget/git/pull/174], and 6
[https://github.com/gitgitgadget/git/pull/175]).

It reflects the part that was submitted a couple of times (see 
https://github.com/gitgitgadget/git/pull/103) during the Outreachy project
by Slavica Ðukic that continued the journey based on an initial patch series
by Daniel Ferreira.

This part only implements the status and the help part, like Slavica's last
iteration did, in the interest of making the review remotely more
reviewable. I fear that this attempt of making it a bit more reviewable is
pretty futile, as so many things changed. So I will ask the reviewers for
forgiveness: please be kind, and give this sort of a fresh review.

I threw in a couple of major changes on top of that iteration, though:

 * The original plan was to add a helper (git add--helper) that takes over
   more and more responsibility from the Perl script over the course of the
   conversion.
   
   This plan is no longer in effect, as I encountered a serious problem with
   that: the MSYS2 runtime used by the Perl interpreter which Git for
   Windows employs to run git add -i has a curious bug (that is safely
   outside the purview of this here patch series) where it fails to read
   from standard input after it spawned a non-MSYS2 program (such as 
   add--helper) that reads from standard input. So that wouldn't work, but
   quit the interactive add as soon as even one interactive command had been
   handled by the helper. To keep my git add -i in a working state, I
   therefore adopted a different strategy:
   
   Just like git difftool was converted by starting with a built-in that did
   nothing but handing off to the scripted version, guarded by the (opt-in) 
   difftool.useBuiltin config setting, I start this patch series by a
   built-in add -i that does nothing else but state that it is not
   implemented yet, guarded by the (opt-in) add.interactive.useBuiltin 
   config setting.
   
   In contrast to the git difftool situation, it is quite a bit easier here,
   as we do not even have to rename the script to 
   git-legacy-add--interactive.perl: the add--interactive command is an
   implementation detail that users are not even supposed to know about.
   Therefore, we can implement that road fork between the built-in and the
   scripted version in builtin/add.c, i.e. in the user-facing git add 
   command.
   
   This will also naturally help with the transition to a fully built-in git
   add -i/git add -p, as we saw with the built-in git rebase how important
   it is for end users to have an escape hatch (and for that reason, tried
   our best to provide the same with the built-in git stash).
   
   
 * The help command was actually not hooked up in git add -i in Slavica's
   last iteration, but was only available as a special option of the git
   add--helper command. As that command no longer exists, I kind of had to
   implement some way to let the built-in git add -i show the help text.
   
   
 * The main loop of git add -i (i.e. the thing that lets you choose status 
   or help) is now implemented (but only lists status and help, of course),
   as it makes use of that feature that took the main chunk of the Outreachy
   project: the function to determine unique prefixes of a list of strings.
   
   
 * Speaking of the unique prefixes: the functionality to determine those is
   now encapsulated in the prefix-map.c file, and I also added a regression
   test.
   
   
 * Speaking of the tests: I also implemented support for the environment
   variable GIT_TEST_ADD_I_USE_BUILTIN: by setting it, the test suite can be
   forced to use the built-in, or the Perl script, version of git add -i.
   Needless to say: by the end of this patch series, running the test suite
   with GIT_TEST_ADD_I_USE_BUILTIN=true will still result in a ton of test
   failures due to not-yet-implemented commands, but it will also
   demonstrate what already works.
   
   
 * Since the main loop starts not only by showing the status, but refreshes
   the index before that, I added that, and I actually refactored that code
   into a new function (repo_refresh_and_write_index()), as it will be used
   a couple of times by the end of the complete conversion of git add -i 
   into a built-in command.

Daniel Ferreira (2):
  diff: export diffstat interface
  built-in add -i: implement the `status` command

Johannes Schindelin (6):
  Start to implement a built-in version of `git add --interactive`
  built-in add -i: refresh the index before running `status`
  built-in add -i: color the header in the `status` command
  built-in add -i: implement the main loop
  built-in add -i: support `?` (prompt help)
  built-in add -i: implement the `help` command

Slavica Djukic (3):
  Add a function to determine unique prefixes for a list of strings
  built-in add -i: show unique prefixes of the commands
  built-in add -i: use color in the main loop

 Documentation/config/add.txt |   5 +
 Makefile                     |   3 +
 add-interactive.c            | 560 +++++++++++++++++++++++++++++++++++
 add-interactive.h            |  10 +
 builtin/add.c                |  16 +-
 diff.c                       |  37 +--
 diff.h                       |  19 ++
 prefix-map.c                 | 111 +++++++
 prefix-map.h                 |  40 +++
 repository.c                 |  19 ++
 repository.h                 |   7 +
 t/README                     |   4 +
 t/helper/test-prefix-map.c   |  58 ++++
 t/helper/test-tool.c         |   1 +
 t/helper/test-tool.h         |   1 +
 t/t0016-prefix-map.sh        |  10 +
 t/t3701-add-interactive.sh   |  24 ++
 17 files changed, 902 insertions(+), 23 deletions(-)
 create mode 100644 add-interactive.c
 create mode 100644 add-interactive.h
 create mode 100644 prefix-map.c
 create mode 100644 prefix-map.h
 create mode 100644 t/helper/test-prefix-map.c
 create mode 100755 t/t0016-prefix-map.sh


base-commit: 8104ec994ea3849a968b4667d072fedd1e688642
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/170
-- 
gitgitgadget

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

* [PATCH 01/11] Start to implement a built-in version of `git add --interactive`
  2019-04-10 17:37 [PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
@ 2019-04-10 17:37 ` Johannes Schindelin via GitGitGadget
  2019-04-18 14:31   ` Jeff Hostetler
  2019-04-10 17:37 ` [PATCH 02/11] diff: export diffstat interface Daniel Ferreira via GitGitGadget
                   ` (10 subsequent siblings)
  11 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-04-10 17:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

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

This is hardly the first conversion of a Git command that is implemented
as a script to a built-in. So far, the most successful strategy for such
conversions has been to add a built-in helper and call that for more and
more functionality from the script, as more and more parts are
converted.

With the interactive add, we choose a different strategy. The sole
reason for this is that on Windows (where such a conversion has the most
benefits in terms of speed and robustness) we face the very specific
problem that a `system()` call in Perl seems to close `stdin` in the
parent process when the spawned process consumes even one character from
`stdin`. And that just does not work for us here, as it would stop the
main loop as soon as any interactive command was performed by the
helper. Which is almost all of the commands in `git add -i`.

It is almost as if Perl told us once again that it does not want us to
use it on Windows.

Instead, we follow the opposite route where we start with a bare-bones
version of the built-in interactive add, guarded by the new
`add.interactive.useBuiltin` config variable, and then add more and more
functionality to it, until it is feature complete.

At this point, the built-in version of `git add -i` only states that it
cannot do anything yet ;-)

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/config/add.txt |  5 +++++
 Makefile                     |  1 +
 add-interactive.c            | 13 +++++++++++++
 add-interactive.h            | 10 ++++++++++
 builtin/add.c                | 16 +++++++++++++++-
 t/README                     |  4 ++++
 6 files changed, 48 insertions(+), 1 deletion(-)
 create mode 100644 add-interactive.c
 create mode 100644 add-interactive.h

diff --git a/Documentation/config/add.txt b/Documentation/config/add.txt
index 4d753f006e..c9f748f81c 100644
--- a/Documentation/config/add.txt
+++ b/Documentation/config/add.txt
@@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::
 	option of linkgit:git-add[1].  `add.ignore-errors` is deprecated,
 	as it does not follow the usual naming convention for configuration
 	variables.
+
+add.interactive.useBuiltin::
+	[EXPERIMENTAL] Set to `true` to use the experimental built-in
+	implementation of the interactive version of linkgit:git-add[1]
+	instead of the Perl script version. Is `false` by default.
diff --git a/Makefile b/Makefile
index c5240942f2..18e656a32f 100644
--- a/Makefile
+++ b/Makefile
@@ -848,6 +848,7 @@ LIB_H = $(shell $(FIND) . \
 	-name '*.h' -print)
 
 LIB_OBJS += abspath.o
+LIB_OBJS += add-interactive.o
 LIB_OBJS += advice.o
 LIB_OBJS += alias.o
 LIB_OBJS += alloc.o
diff --git a/add-interactive.c b/add-interactive.c
new file mode 100644
index 0000000000..540bf185d8
--- /dev/null
+++ b/add-interactive.c
@@ -0,0 +1,13 @@
+#include "cache.h"
+#include "add-interactive.h"
+#include "config.h"
+
+int add_i_config(const char *var, const char *value, void *cb)
+{
+	return git_default_config(var, value, cb);
+}
+
+int run_add_i(struct repository *r, const struct pathspec *ps)
+{
+	die(_("No commands are available in the built-in `git add -i` yet!"));
+}
diff --git a/add-interactive.h b/add-interactive.h
new file mode 100644
index 0000000000..e6e6e051eb
--- /dev/null
+++ b/add-interactive.h
@@ -0,0 +1,10 @@
+#ifndef ADD_INTERACTIVE_H
+#define ADD_INTERACTIVE_H
+
+int add_i_config(const char *var, const char *value, void *cb);
+
+struct repository;
+struct pathspec;
+int run_add_i(struct repository *r, const struct pathspec *ps);
+
+#endif
diff --git a/builtin/add.c b/builtin/add.c
index db2dfa4350..5a32a755c8 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -20,6 +20,7 @@
 #include "bulk-checkin.h"
 #include "argv-array.h"
 #include "submodule.h"
+#include "add-interactive.h"
 
 static const char * const builtin_add_usage[] = {
 	N_("git add [<options>] [--] <pathspec>..."),
@@ -28,6 +29,7 @@ static const char * const builtin_add_usage[] = {
 static int patch_interactive, add_interactive, edit_interactive;
 static int take_worktree_changes;
 static int add_renormalize;
+static int use_builtin_add_i;
 
 struct update_callback_data {
 	int flags;
@@ -186,6 +188,9 @@ int run_add_interactive(const char *revision, const char *patch_mode,
 	int status, i;
 	struct argv_array argv = ARGV_ARRAY_INIT;
 
+	if (use_builtin_add_i && !patch_mode)
+		return !!run_add_i(the_repository, pathspec);
+
 	argv_array_push(&argv, "add--interactive");
 	if (patch_mode)
 		argv_array_push(&argv, patch_mode);
@@ -319,7 +324,12 @@ static int add_config(const char *var, const char *value, void *cb)
 		ignore_add_errors = git_config_bool(var, value);
 		return 0;
 	}
-	return git_default_config(var, value, cb);
+	if (!strcmp(var, "add.interactive.usebuiltin")) {
+		use_builtin_add_i = git_config_bool(var, value);
+		return 0;
+	}
+
+	return add_i_config(var, value, cb);
 }
 
 static const char embedded_advice[] = N_(
@@ -394,8 +404,12 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 	int require_pathspec;
 	char *seen = NULL;
 	struct lock_file lock_file = LOCK_INIT;
+	int use_builtin_add_i_env =
+		git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
 
 	git_config(add_config, NULL);
+	if (use_builtin_add_i_env >= 0)
+		use_builtin_add_i = use_builtin_add_i_env;
 
 	argc = parse_options(argc, argv, prefix, builtin_add_options,
 			  builtin_add_usage, PARSE_OPT_KEEP_ARGV0);
diff --git a/t/README b/t/README
index 886bbec5bc..6408a1847e 100644
--- a/t/README
+++ b/t/README
@@ -383,6 +383,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_ADD_I_USE_BUILTIN=<boolean>, when true, enables the
+builtin version of git add -i. See 'add.interactive.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
-- 
gitgitgadget


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

* [PATCH 02/11] diff: export diffstat interface
  2019-04-10 17:37 [PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  2019-04-10 17:37 ` [PATCH 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
@ 2019-04-10 17:37 ` Daniel Ferreira via GitGitGadget
  2019-04-10 17:37 ` [PATCH 03/11] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Daniel Ferreira via GitGitGadget @ 2019-04-10 17:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Daniel Ferreira

From: Daniel Ferreira <bnmvco@gmail.com>

Make the diffstat interface (namely, the diffstat_t struct and
compute_diffstat) no longer be internal to diff.c and allow it to be used
by other parts of git.

This is helpful for code that may want to easily extract information
from files using the diff machinery, while flushing it differently from
how the show_* functions used by diff_flush() do it. One example is the
builtin implementation of git-add--interactive's status.

Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 37 +++++++++++++++----------------------
 diff.h | 19 +++++++++++++++++++
 2 files changed, 34 insertions(+), 22 deletions(-)

diff --git a/diff.c b/diff.c
index 5306c48652..daa5f3a736 100644
--- a/diff.c
+++ b/diff.c
@@ -2489,22 +2489,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)
 	}
 }
 
-struct diffstat_t {
-	int nr;
-	int alloc;
-	struct diffstat_file {
-		char *from_name;
-		char *name;
-		char *print_name;
-		const char *comments;
-		unsigned is_unmerged:1;
-		unsigned is_binary:1;
-		unsigned is_renamed:1;
-		unsigned is_interesting:1;
-		uintmax_t added, deleted;
-	} **files;
-};
-
 static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
 					  const char *name_a,
 					  const char *name_b)
@@ -6001,12 +5985,7 @@ void diff_flush(struct diff_options *options)
 	    dirstat_by_line) {
 		struct diffstat_t diffstat;
 
-		memset(&diffstat, 0, sizeof(struct diffstat_t));
-		for (i = 0; i < q->nr; i++) {
-			struct diff_filepair *p = q->queue[i];
-			if (check_pair_status(p))
-				diff_flush_stat(p, options, &diffstat);
-		}
+		compute_diffstat(options, &diffstat, q);
 		if (output_format & DIFF_FORMAT_NUMSTAT)
 			show_numstat(&diffstat, options);
 		if (output_format & DIFF_FORMAT_DIFFSTAT)
@@ -6306,6 +6285,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)
 	return ignored;
 }
 
+void compute_diffstat(struct diff_options *options,
+		      struct diffstat_t *diffstat,
+		      struct diff_queue_struct *q)
+{
+	int i;
+
+	memset(diffstat, 0, sizeof(struct diffstat_t));
+	for (i = 0; i < q->nr; i++) {
+		struct diff_filepair *p = q->queue[i];
+		if (check_pair_status(p))
+			diff_flush_stat(p, options, diffstat);
+	}
+}
+
 void diff_addremove(struct diff_options *options,
 		    int addremove, unsigned mode,
 		    const struct object_id *oid,
diff --git a/diff.h b/diff.h
index b512d0477a..ae9bedfab8 100644
--- a/diff.h
+++ b/diff.h
@@ -240,6 +240,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);
 void diff_emit_submodule_pipethrough(struct diff_options *o,
 				     const char *line, int len);
 
+struct diffstat_t {
+	int nr;
+	int alloc;
+	struct diffstat_file {
+		char *from_name;
+		char *name;
+		char *print_name;
+		const char *comments;
+		unsigned is_unmerged:1;
+		unsigned is_binary:1;
+		unsigned is_renamed:1;
+		unsigned is_interesting:1;
+		uintmax_t added, deleted;
+	} **files;
+};
+
 enum color_diff {
 	DIFF_RESET = 0,
 	DIFF_CONTEXT = 1,
@@ -328,6 +344,9 @@ void diff_change(struct diff_options *,
 
 struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);
 
+void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
+		      struct diff_queue_struct *q);
+
 #define DIFF_SETUP_REVERSE      	1
 #define DIFF_SETUP_USE_SIZE_CACHE	4
 
-- 
gitgitgadget


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

* [PATCH 03/11] built-in add -i: implement the `status` command
  2019-04-10 17:37 [PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  2019-04-10 17:37 ` [PATCH 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
  2019-04-10 17:37 ` [PATCH 02/11] diff: export diffstat interface Daniel Ferreira via GitGitGadget
@ 2019-04-10 17:37 ` Daniel Ferreira via GitGitGadget
  2019-04-10 17:37 ` [PATCH 04/11] built-in add -i: refresh the index before running `status` Johannes Schindelin via GitGitGadget
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Daniel Ferreira via GitGitGadget @ 2019-04-10 17:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Daniel Ferreira

From: Daniel Ferreira <bnmvco@gmail.com>

This implements the `status` command of `git add -i`. The data
structures introduced in this commit will be extended as needed later.

At this point, we re-implement only part of the `list_and_choose()`
function of the Perl script `git-add--interactive.perl` and call it
`list()`. It does not yet color anything, or do columns, or allow user
input.

Over the course of the next commits, we will introduce a
`list_and_choose()` function that uses `list()` to display the list of
options and let the user choose one or more of the displayed items. This
will be used to implement the main loop of the built-in `git add -i`, at
which point the new `status` command can actually be used.

Note that we pass the list of items as a `struct item **` as opposed to
a `struct item *`, to allow for the actual items to contain much more
information than merely the name.

Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 265 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 264 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index 540bf185d8..f627a56eeb 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -1,13 +1,276 @@
 #include "cache.h"
 #include "add-interactive.h"
 #include "config.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "refs.h"
 
 int add_i_config(const char *var, const char *value, void *cb)
 {
 	return git_default_config(var, value, cb);
 }
 
+struct item {
+	const char *name;
+};
+
+struct list_options {
+	const char *header;
+	void (*print_item)(int i, struct item *item, void *print_item_data);
+	void *print_item_data;
+};
+
+static void list(struct item **list, size_t nr, struct list_options *opts)
+{
+	int i;
+
+	if (!nr)
+		return;
+
+	if (opts->header)
+		printf("%s\n", opts->header);
+
+	for (i = 0; i < nr; i++) {
+		opts->print_item(i, list[i], opts->print_item_data);
+		putchar('\n');
+	}
+}
+
+struct adddel {
+	uintmax_t add, del;
+	unsigned seen:1, binary:1;
+};
+
+struct file_list {
+	struct file_item {
+		struct item item;
+		struct adddel index, worktree;
+	} **file;
+	size_t nr, alloc;
+};
+
+static void add_file_item(struct file_list *list, const char *name)
+{
+	struct file_item *item;
+
+	FLEXPTR_ALLOC_STR(item, item.name, name);
+
+	ALLOC_GROW(list->file, list->nr + 1, list->alloc);
+	list->file[list->nr++] = item;
+}
+
+static void reset_file_list(struct file_list *list)
+{
+	size_t i;
+
+	for (i = 0; i < list->nr; i++)
+		free(list->file[i]);
+	list->nr = 0;
+}
+
+static void release_file_list(struct file_list *list)
+{
+	reset_file_list(list);
+	FREE_AND_NULL(list->file);
+	list->alloc = 0;
+}
+
+static int file_item_cmp(const void *a, const void *b)
+{
+	const struct file_item * const *f1 = a;
+	const struct file_item * const *f2 = b;
+
+	return strcmp((*f1)->item.name, (*f2)->item.name);
+}
+
+struct pathname_entry {
+	struct hashmap_entry ent;
+	size_t index;
+	char pathname[FLEX_ARRAY];
+};
+
+static int pathname_entry_cmp(const void *unused_cmp_data,
+			      const void *entry, const void *entry_or_key,
+			      const void *pathname)
+{
+	const struct pathname_entry *e1 = entry, *e2 = entry_or_key;
+
+	return strcmp(e1->pathname,
+		      pathname ? (const char *)pathname : e2->pathname);
+}
+
+struct collection_status {
+	enum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase;
+
+	const char *reference;
+
+	struct file_list *list;
+	struct hashmap file_map;
+};
+
+static void collect_changes_cb(struct diff_queue_struct *q,
+			       struct diff_options *options,
+			       void *data)
+{
+	struct collection_status *s = data;
+	struct diffstat_t stat = { 0 };
+	int i;
+
+	if (!q->nr)
+		return;
+
+	compute_diffstat(options, &stat, q);
+
+	for (i = 0; i < stat.nr; i++) {
+		const char *name = stat.files[i]->name;
+		int hash = strhash(name);
+		struct pathname_entry *entry;
+		size_t file_index;
+		struct file_item *file;
+		struct adddel *adddel;
+
+		entry = hashmap_get_from_hash(&s->file_map, hash, name);
+		if (entry)
+			file_index = entry->index;
+		else {
+			FLEX_ALLOC_STR(entry, pathname, name);
+			hashmap_entry_init(entry, hash);
+			entry->index = file_index = s->list->nr;
+			hashmap_add(&s->file_map, entry);
+
+			add_file_item(s->list, name);
+		}
+		file = s->list->file[file_index];
+
+		adddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;
+		adddel->seen = 1;
+		adddel->add = stat.files[i]->added;
+		adddel->del = stat.files[i]->deleted;
+		if (stat.files[i]->is_binary)
+			adddel->binary = 1;
+	}
+}
+
+static int get_modified_files(struct repository *r, struct file_list *list,
+			      const struct pathspec *ps)
+{
+	struct object_id head_oid;
+	int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+					     &head_oid, NULL);
+	struct collection_status s = { FROM_WORKTREE };
+
+	if (repo_read_index_preload(r, ps, 0) < 0)
+		return error(_("could not read index"));
+
+	s.list = list;
+	hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);
+
+	for (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {
+		struct rev_info rev;
+		struct setup_revision_opt opt = { 0 };
+
+		opt.def = is_initial ?
+			empty_tree_oid_hex() : oid_to_hex(&head_oid);
+
+		init_revisions(&rev, NULL);
+		setup_revisions(0, NULL, &rev, &opt);
+
+		rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+		rev.diffopt.format_callback = collect_changes_cb;
+		rev.diffopt.format_callback_data = &s;
+
+		if (ps)
+			copy_pathspec(&rev.prune_data, ps);
+
+		if (s.phase == FROM_INDEX)
+			run_diff_index(&rev, 1);
+		else {
+			rev.diffopt.flags.ignore_dirty_submodules = 1;
+			run_diff_files(&rev, 0);
+		}
+	}
+	hashmap_free(&s.file_map, 1);
+
+	/* While the diffs are ordered already, we ran *two* diffs... */
+	QSORT(list->file, list->nr, file_item_cmp);
+
+	return 0;
+}
+
+static void populate_wi_changes(struct strbuf *buf,
+				struct adddel *ad, const char *no_changes)
+{
+	if (ad->binary)
+		strbuf_addstr(buf, _("binary"));
+	else if (ad->seen)
+		strbuf_addf(buf, "+%"PRIuMAX"/-%"PRIuMAX,
+			    (uintmax_t)ad->add, (uintmax_t)ad->del);
+	else
+		strbuf_addstr(buf, no_changes);
+}
+
+struct print_file_item_data {
+	const char *modified_fmt;
+	struct strbuf buf, index, worktree;
+};
+
+static void print_file_item(int i, struct item *item,
+			    void *print_file_item_data)
+{
+	struct file_item *c = (struct file_item *)item;
+	struct print_file_item_data *d = print_file_item_data;
+
+	strbuf_reset(&d->index);
+	strbuf_reset(&d->worktree);
+	strbuf_reset(&d->buf);
+
+	populate_wi_changes(&d->worktree, &c->worktree, _("nothing"));
+	populate_wi_changes(&d->index, &c->index, _("unchanged"));
+	strbuf_addf(&d->buf, d->modified_fmt,
+		    d->index.buf, d->worktree.buf, item->name);
+
+	printf(" %2d: %s", i + 1, d->buf.buf);
+}
+
+static int run_status(struct repository *r, const struct pathspec *ps,
+		      struct file_list *files, struct list_options *opts)
+{
+	reset_file_list(files);
+
+	if (get_modified_files(r, files, ps) < 0)
+		return -1;
+
+	if (files->nr)
+		list((struct item **)files->file, files->nr, opts);
+	putchar('\n');
+
+	return 0;
+}
+
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
-	die(_("No commands are available in the built-in `git add -i` yet!"));
+	struct print_file_item_data print_file_item_data = {
+		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+	};
+	struct list_options opts = {
+		NULL, print_file_item, &print_file_item_data
+	};
+	struct strbuf header = STRBUF_INIT;
+	struct file_list files = { NULL };
+	int res = 0;
+
+	strbuf_addstr(&header, "      ");
+	strbuf_addf(&header, print_file_item_data.modified_fmt,
+		    _("staged"), _("unstaged"), _("path"));
+	opts.header = header.buf;
+
+	res = run_status(r, ps, &files, &opts);
+
+	release_file_list(&files);
+	strbuf_release(&print_file_item_data.buf);
+	strbuf_release(&print_file_item_data.index);
+	strbuf_release(&print_file_item_data.worktree);
+	strbuf_release(&header);
+
+	return res;
 }
-- 
gitgitgadget


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

* [PATCH 04/11] built-in add -i: refresh the index before running `status`
  2019-04-10 17:37 [PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                   ` (2 preceding siblings ...)
  2019-04-10 17:37 ` [PATCH 03/11] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
@ 2019-04-10 17:37 ` Johannes Schindelin via GitGitGadget
  2019-04-10 17:37 ` [PATCH 05/11] built-in add -i: color the header in the `status` command Johannes Schindelin via GitGitGadget
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-04-10 17:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

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

This is what the Perl version does, and therefore it is what the
built-in version should do, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c |  4 +++-
 repository.c      | 19 +++++++++++++++++++
 repository.h      |  7 +++++++
 3 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index f627a56eeb..d971b58552 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -264,7 +264,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 		    _("staged"), _("unstaged"), _("path"));
 	opts.header = header.buf;
 
-	res = run_status(r, ps, &files, &opts);
+	repo_refresh_and_write_index(r, REFRESH_QUIET, 1);
+	if (run_status(r, ps, &files, &opts) < 0)
+		res = -1;
 
 	release_file_list(&files);
 	strbuf_release(&print_file_item_data.buf);
diff --git a/repository.c b/repository.c
index 65e6f8b8fd..c90f310093 100644
--- a/repository.c
+++ b/repository.c
@@ -272,3 +272,22 @@ int repo_hold_locked_index(struct repository *repo,
 		BUG("the repo hasn't been setup");
 	return hold_lock_file_for_update(lf, repo->index_file, flags);
 }
+
+int repo_refresh_and_write_index(struct repository *r,
+				 unsigned int flags, int gentle)
+{
+	struct lock_file lock_file = LOCK_INIT;
+	int fd;
+
+	if (repo_read_index_preload(r, NULL, 0) < 0)
+		return error(_("could not read index"));
+	fd = repo_hold_locked_index(r, &lock_file, 0);
+	if (!gentle && fd < 0)
+		return error(_("could not lock index for writing"));
+	refresh_index(r->index, flags, NULL, NULL, NULL);
+	if (0 <= fd)
+		repo_update_index_if_able(r, &lock_file);
+	rollback_lock_file(&lock_file);
+
+	return 0;
+}
diff --git a/repository.h b/repository.h
index 8981649d43..fb49e0e328 100644
--- a/repository.h
+++ b/repository.h
@@ -154,5 +154,12 @@ int repo_read_index_unmerged(struct repository *);
  */
 void repo_update_index_if_able(struct repository *, struct lock_file *);
 
+/*
+ * Refresh the index and write it out. If the index file could not be
+ * locked, error out, except in gentle mode. The flags will be passed
+ * through to refresh_index().
+ */
+int repo_refresh_and_write_index(struct repository *r,
+				 unsigned int flags, int gentle);
 
 #endif /* REPOSITORY_H */
-- 
gitgitgadget


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

* [PATCH 05/11] built-in add -i: color the header in the `status` command
  2019-04-10 17:37 [PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                   ` (3 preceding siblings ...)
  2019-04-10 17:37 ` [PATCH 04/11] built-in add -i: refresh the index before running `status` Johannes Schindelin via GitGitGadget
@ 2019-04-10 17:37 ` Johannes Schindelin via GitGitGadget
  2019-04-10 17:37 ` [PATCH 06/11] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-04-10 17:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

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

For simplicity, we only implemented the `status` command without colors.
This patch starts adding color, matching what the Perl script
`git-add--interactive.perl` does.

Original-Patch-By: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 51 ++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 48 insertions(+), 3 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index d971b58552..79adc58321 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -1,13 +1,56 @@
 #include "cache.h"
 #include "add-interactive.h"
 #include "config.h"
+#include "color.h"
+#include "config.h"
 #include "diffcore.h"
 #include "revision.h"
 #include "refs.h"
 
+static int use_color = -1;
+
+enum color_add_i {
+	COLOR_HEADER = 0,
+};
+
+static char list_colors[][COLOR_MAXLEN] = {
+	GIT_COLOR_BOLD,      /* Header */
+};
+
+static const char *get_add_i_color(enum color_add_i ix)
+{
+	if (want_color(use_color))
+		return list_colors[ix];
+	return "";
+}
+
+static int parse_color_slot(const char *slot)
+{
+	if (!strcasecmp(slot, "header"))
+		return COLOR_HEADER;
+
+	return -1;
+}
+
 int add_i_config(const char *var, const char *value, void *cb)
 {
-	return git_default_config(var, value, cb);
+	const char *name;
+
+	if (!strcmp(var, "color.interactive")) {
+		use_color = git_config_colorbool(var, value);
+		return 0;
+	}
+
+	if (skip_prefix(var, "color.interactive.", &name)) {
+		int slot = parse_color_slot(name);
+		if (slot < 0)
+			return 0;
+		if (!value)
+			return config_error_nonbool(var);
+		return color_parse(value, list_colors[slot]);
+	}
+
+	return git_color_default_config(var, value, cb);
 }
 
 struct item {
@@ -27,8 +70,10 @@ static void list(struct item **list, size_t nr, struct list_options *opts)
 	if (!nr)
 		return;
 
-	if (opts->header)
-		printf("%s\n", opts->header);
+	if (opts->header) {
+		const char *header_color = get_add_i_color(COLOR_HEADER);
+		color_fprintf_ln(stdout, header_color, "%s", opts->header);
+	}
 
 	for (i = 0; i < nr; i++) {
 		opts->print_item(i, list[i], opts->print_item_data);
-- 
gitgitgadget


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

* [PATCH 06/11] built-in add -i: implement the main loop
  2019-04-10 17:37 [PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                   ` (4 preceding siblings ...)
  2019-04-10 17:37 ` [PATCH 05/11] built-in add -i: color the header in the `status` command Johannes Schindelin via GitGitGadget
@ 2019-04-10 17:37 ` Johannes Schindelin via GitGitGadget
  2019-04-18 16:49   ` Jeff Hostetler
  2019-04-10 17:37 ` [PATCH 07/11] Add a function to determine unique prefixes for a list of strings Slavica Djukic via GitGitGadget
                   ` (5 subsequent siblings)
  11 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-04-10 17:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

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

The reason why we did not start with the main loop to begin with is that
it is the first user of `list_and_choose()`, which uses the `list()`
function that we conveniently introduced for use by the `status`
command.

Apart from the "and choose" part, there are more differences between the
way the `status` command calls the `list_and_choose()` function in the
Perl version of `git add -i` compared to the other callers of said
function. The most important ones:

- The list is not only shown, but the user is also asked to make a
  choice, possibly selecting multiple entries.

- The list of items is prefixed with a marker indicating what items have
  been selected, if multi-selection is allowed.

- Initially, for each item a unique prefix (if there exists any within
  the given parameters) is determined, and shown in the list, and
  accepted as a shortcut for the selection.

These features will be implemented later, except the part where the user
can choose a command. At this stage, though, the built-in `git add -i`
still only supports the `status` command, with the remaining commands to
follow over the course of the next commits.

In addition, we also modify `list()` to support displaying the commands
in columns, even if there is currently only one.

The Perl script `git-add--interactive.perl` mixed the purposes of the
"list" and the "and choose" part into the same function. In the C
version, we will keep them separate instead, calling the `list()`
function from the `list_and_choose()` function.

Note that we only have a prompt ending in a single ">" at this stage;
later commits will add commands that display a double ">>" to indicate
that the user is in a different loop than the main one.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 122 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 120 insertions(+), 2 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 79adc58321..c8bd62369e 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -58,6 +58,7 @@ struct item {
 };
 
 struct list_options {
+	int columns;
 	const char *header;
 	void (*print_item)(int i, struct item *item, void *print_item_data);
 	void *print_item_data;
@@ -65,7 +66,7 @@ struct list_options {
 
 static void list(struct item **list, size_t nr, struct list_options *opts)
 {
-	int i;
+	int i, last_lf = 0;
 
 	if (!nr)
 		return;
@@ -77,8 +78,90 @@ static void list(struct item **list, size_t nr, struct list_options *opts)
 
 	for (i = 0; i < nr; i++) {
 		opts->print_item(i, list[i], opts->print_item_data);
+
+		if ((opts->columns) && ((i + 1) % (opts->columns))) {
+			putchar('\t');
+			last_lf = 0;
+		}
+		else {
+			putchar('\n');
+			last_lf = 1;
+		}
+	}
+
+	if (!last_lf)
 		putchar('\n');
+}
+struct list_and_choose_options {
+	struct list_options list_opts;
+
+	const char *prompt;
+};
+
+/*
+ * Returns the selected index.
+ */
+static ssize_t list_and_choose(struct item **items, size_t nr,
+			       struct list_and_choose_options *opts)
+{
+	struct strbuf input = STRBUF_INIT;
+	ssize_t res = -1;
+
+	for (;;) {
+		char *p, *endp;
+
+		strbuf_reset(&input);
+
+		list(items, nr, &opts->list_opts);
+
+		printf("%s%s", opts->prompt, "> ");
+		fflush(stdout);
+
+		if (strbuf_getline(&input, stdin) == EOF) {
+			putchar('\n');
+			res = -2;
+			break;
+		}
+		strbuf_trim(&input);
+
+		if (!input.len)
+			break;
+
+		p = input.buf;
+		for (;;) {
+			size_t sep = strcspn(p, " \t\r\n,");
+			ssize_t index = -1;
+
+			if (!sep) {
+				if (!*p)
+					break;
+				p++;
+				continue;
+			}
+
+			if (isdigit(*p)) {
+				index = strtoul(p, &endp, 10) - 1;
+				if (endp != p + sep)
+					index = -1;
+			}
+
+			p[sep] = '\0';
+			if (index < 0 || index >= nr)
+				printf(_("Huh (%s)?\n"), p);
+			else {
+				res = index;
+				break;
+			}
+
+			p += sep + 1;
+		}
+
+		if (res >= 0)
+			break;
 	}
+
+	strbuf_release(&input);
+	return res;
 }
 
 struct adddel {
@@ -292,16 +375,39 @@ static int run_status(struct repository *r, const struct pathspec *ps,
 	return 0;
 }
 
+static void print_command_item(int i, struct item *item,
+			       void *print_command_item_data)
+{
+	printf(" %2d: %s", i + 1, item->name);
+}
+
+struct command_item {
+	struct item item;
+	int (*command)(struct repository *r, const struct pathspec *ps,
+		       struct file_list *files, struct list_options *opts);
+};
+
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
+	struct list_and_choose_options main_loop_opts = {
+		{ 4, N_("*** Commands ***"), print_command_item, NULL },
+		N_("What now")
+	};
+	struct command_item
+		status = { { "status" }, run_status };
+	struct command_item *commands[] = {
+		&status
+	};
+
 	struct print_file_item_data print_file_item_data = {
 		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 	};
 	struct list_options opts = {
-		NULL, print_file_item, &print_file_item_data
+		0, NULL, print_file_item, &print_file_item_data
 	};
 	struct strbuf header = STRBUF_INIT;
 	struct file_list files = { NULL };
+	ssize_t i;
 	int res = 0;
 
 	strbuf_addstr(&header, "      ");
@@ -313,6 +419,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	if (run_status(r, ps, &files, &opts) < 0)
 		res = -1;
 
+	for (;;) {
+		i = list_and_choose((struct item **)commands,
+				    ARRAY_SIZE(commands), &main_loop_opts);
+		if (i < -1) {
+			printf(_("Bye.\n"));
+			res = 0;
+			break;
+		}
+		if (i >= 0)
+			res = commands[i]->command(r, ps, &files, &opts);
+	}
+
 	release_file_list(&files);
 	strbuf_release(&print_file_item_data.buf);
 	strbuf_release(&print_file_item_data.index);
-- 
gitgitgadget


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

* [PATCH 07/11] Add a function to determine unique prefixes for a list of strings
  2019-04-10 17:37 [PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                   ` (5 preceding siblings ...)
  2019-04-10 17:37 ` [PATCH 06/11] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
@ 2019-04-10 17:37 ` Slavica Djukic via GitGitGadget
  2019-04-18 17:57   ` Jeff Hostetler
  2019-04-10 17:37 ` [PATCH 08/11] built-in add -i: show unique prefixes of the commands Slavica Djukic via GitGitGadget
                   ` (4 subsequent siblings)
  11 siblings, 1 reply; 124+ messages in thread
From: Slavica Djukic via GitGitGadget @ 2019-04-10 17:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Slavica Djukic

From: Slavica Djukic <slawica92@hotmail.com>

In the `git add -i` command, we show unique prefixes of the commands and
files, to give an indication what prefix would select them.

Naturally, the C implementation looks a lot different than the Perl
implementation: in Perl, a trie is much easier implemented, while we
already have a pretty neat hashmap implementation in C that we use for
the purpose of storing (not necessarily unique) prefixes.

The idea: for each item that we add, we generate prefixes starting with
the first letter, then the first two letters, then three, etc, until we
find a prefix that is unique (or until the prefix length would be
longer than we want). If we encounter a previously-unique prefix on the
way, we adjust that item's prefix to make it unique again (or we mark it
as having no unique prefix if we failed to find one). These partial
prefixes are stored in a hash map (for quick lookup times).

To make sure that this function works as expected, we add a test using a
special-purpose test helper that was added for that purpose.

Note: We expect the list of prefix items to be passed in as a list of
pointers rather than as regular list to avoid having to copy information
(the actual items will most likely contain more information than just
the name and the length of the unique prefix, but passing in `struct
prefix_item *` would not allow for that).

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile                   |   2 +
 prefix-map.c               | 111 +++++++++++++++++++++++++++++++++++++
 prefix-map.h               |  40 +++++++++++++
 t/helper/test-prefix-map.c |  58 +++++++++++++++++++
 t/helper/test-tool.c       |   1 +
 t/helper/test-tool.h       |   1 +
 t/t0016-prefix-map.sh      |  10 ++++
 7 files changed, 223 insertions(+)
 create mode 100644 prefix-map.c
 create mode 100644 prefix-map.h
 create mode 100644 t/helper/test-prefix-map.c
 create mode 100755 t/t0016-prefix-map.sh

diff --git a/Makefile b/Makefile
index 18e656a32f..8299b3f17d 100644
--- a/Makefile
+++ b/Makefile
@@ -754,6 +754,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o
 TEST_BUILTINS_OBJS += test-parse-options.o
 TEST_BUILTINS_OBJS += test-path-utils.o
 TEST_BUILTINS_OBJS += test-pkt-line.o
+TEST_BUILTINS_OBJS += test-prefix-map.o
 TEST_BUILTINS_OBJS += test-prio-queue.o
 TEST_BUILTINS_OBJS += test-reach.o
 TEST_BUILTINS_OBJS += test-read-cache.o
@@ -967,6 +968,7 @@ LIB_OBJS += patch-ids.o
 LIB_OBJS += path.o
 LIB_OBJS += pathspec.o
 LIB_OBJS += pkt-line.o
+LIB_OBJS += prefix-map.o
 LIB_OBJS += preload-index.o
 LIB_OBJS += pretty.o
 LIB_OBJS += prio-queue.o
diff --git a/prefix-map.c b/prefix-map.c
new file mode 100644
index 0000000000..3c5ae4ae0a
--- /dev/null
+++ b/prefix-map.c
@@ -0,0 +1,111 @@
+#include "cache.h"
+#include "prefix-map.h"
+
+static int map_cmp(const void *unused_cmp_data,
+		   const void *entry,
+		   const void *entry_or_key,
+		   const void *unused_keydata)
+{
+	const struct prefix_map_entry *a = entry;
+	const struct prefix_map_entry *b = entry_or_key;
+
+	return a->prefix_length != b->prefix_length ||
+		strncmp(a->name, b->name, a->prefix_length);
+}
+
+static void add_prefix_entry(struct hashmap *map, const char *name,
+			     size_t prefix_length, struct prefix_item *item)
+{
+	struct prefix_map_entry *result = xmalloc(sizeof(*result));
+	result->name = name;
+	result->prefix_length = prefix_length;
+	result->item = item;
+	hashmap_entry_init(result, memhash(name, prefix_length));
+	hashmap_add(map, result);
+}
+
+static void init_prefix_map(struct prefix_map *prefix_map,
+			    int min_prefix_length, int max_prefix_length)
+{
+	hashmap_init(&prefix_map->map, map_cmp, NULL, 0);
+	prefix_map->min_length = min_prefix_length;
+	prefix_map->max_length = max_prefix_length;
+}
+
+static void add_prefix_item(struct prefix_map *prefix_map,
+			    struct prefix_item *item)
+{
+	struct prefix_map_entry *e = xmalloc(sizeof(*e)), *e2;
+	int j;
+
+	e->item = item;
+	e->name = e->item->name;
+
+	for (j = prefix_map->min_length; j <= prefix_map->max_length; j++) {
+		if (!isascii(e->name[j])) {
+			free(e);
+			break;
+		}
+
+		e->prefix_length = j;
+		hashmap_entry_init(e, memhash(e->name, j));
+		e2 = hashmap_get(&prefix_map->map, e, NULL);
+		if (!e2) {
+			/* prefix is unique so far */
+			e->item->prefix_length = j;
+			hashmap_add(&prefix_map->map, e);
+			break;
+		}
+
+		if (!e2->item)
+			continue; /* non-unique prefix */
+
+		if (j != e2->item->prefix_length)
+			BUG("unexpected prefix length: %d != %d",
+			    (int)j, (int)e2->item->prefix_length);
+
+		/* skip common prefix */
+		for (; j < prefix_map->max_length && e->name[j]; j++) {
+			if (e->item->name[j] != e2->item->name[j])
+				break;
+			add_prefix_entry(&prefix_map->map, e->name, j + 1,
+					 NULL);
+		}
+
+		/* e2 no longer refers to a unique prefix */
+		if (j < prefix_map->max_length && e2->name[j]) {
+			/* found a new unique prefix for e2's item */
+			e2->item->prefix_length = j + 1;
+			add_prefix_entry(&prefix_map->map, e2->name, j + 1,
+					 e2->item);
+		}
+		else
+			e2->item->prefix_length = 0;
+		e2->item = NULL;
+
+		if (j < prefix_map->max_length && e->name[j]) {
+			/* found a unique prefix for the item */
+			e->item->prefix_length = j + 1;
+			add_prefix_entry(&prefix_map->map, e->name, j + 1,
+					 e->item);
+		} else {
+			/* item has no (short enough) unique prefix */
+			e->item->prefix_length = 0;
+			free(e);
+		}
+
+		break;
+	}
+}
+
+void find_unique_prefixes(struct prefix_item **list, size_t nr,
+			  int min_length, int max_length)
+{
+	int i;
+	struct prefix_map prefix_map;
+
+	init_prefix_map(&prefix_map, min_length, max_length);
+	for (i = 0; i < nr; i++)
+		add_prefix_item(&prefix_map, list[i]);
+	hashmap_free(&prefix_map.map, 1);
+}
diff --git a/prefix-map.h b/prefix-map.h
new file mode 100644
index 0000000000..ce3b8a4a32
--- /dev/null
+++ b/prefix-map.h
@@ -0,0 +1,40 @@
+#ifndef PREFIX_MAP_H
+#define PREFIX_MAP_H
+
+#include "hashmap.h"
+
+struct prefix_item {
+	const char *name;
+	size_t prefix_length;
+};
+
+struct prefix_map_entry {
+	struct hashmap_entry e;
+	const char *name;
+	size_t prefix_length;
+	/* if item is NULL, the prefix is not unique */
+	struct prefix_item *item;
+};
+
+struct prefix_map {
+	struct hashmap map;
+	int min_length, max_length;
+};
+
+/*
+ * Find unique prefixes in a given list of strings.
+ *
+ * Typically, the `struct prefix_item` information will be but a field in the
+ * actual item struct; For this reason, the `list` parameter is specified as a
+ * list of pointers to the items.
+ *
+ * The `min_length`/`max_length` parameters define what length the unique
+ * prefixes should have.
+ *
+ * If no unique prefix could be found for a given item, its `prefix_length`
+ * will be set to 0.
+ */
+void find_unique_prefixes(struct prefix_item **list, size_t nr,
+			  int min_length, int max_length);
+
+#endif
diff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c
new file mode 100644
index 0000000000..3f1c90eaf0
--- /dev/null
+++ b/t/helper/test-prefix-map.c
@@ -0,0 +1,58 @@
+#include "test-tool.h"
+#include "cache.h"
+#include "prefix-map.h"
+
+static size_t test_count, failed_count;
+
+static void check(int succeeded, const char *file, size_t line_no,
+		  const char *fmt, ...)
+{
+	va_list ap;
+
+	test_count++;
+	if (succeeded)
+		return;
+
+	va_start(ap, fmt);
+	fprintf(stderr, "%s:%d: ", file, (int)line_no);
+	vfprintf(stderr, fmt, ap);
+	fputc('\n', stderr);
+	va_end(ap);
+
+	failed_count++;
+}
+
+#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \
+	check(expect == actual, __FILE__, __LINE__, \
+	      "size_t's do not match: %" \
+	      PRIdMAX " != %" PRIdMAX " (%s) (%s)", \
+	      (intmax_t)expect, (intmax_t)actual, #actual, hint)
+
+int cmd__prefix_map(int argc, const char **argv)
+{
+#define NR 5
+	struct prefix_item items[NR] = {
+		{ "unique" },
+		{ "hell" },
+		{ "hello" },
+		{ "wok" },
+		{ "world" },
+	};
+	struct prefix_item *list[NR] = {
+		items, items + 1, items + 2, items + 3, items + 4
+	};
+
+	find_unique_prefixes(list, NR, 1, 3);
+
+#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \
+	EXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \
+			     list[index]->name)
+
+	EXPECT_PREFIX_LENGTH_EQUALS(1, 0);
+	EXPECT_PREFIX_LENGTH_EQUALS(0, 1);
+	EXPECT_PREFIX_LENGTH_EQUALS(0, 2);
+	EXPECT_PREFIX_LENGTH_EQUALS(3, 3);
+	EXPECT_PREFIX_LENGTH_EQUALS(3, 4);
+
+	return !!failed_count;
+}
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index 99db7409b8..d6a92a8699 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -32,6 +32,7 @@ static struct test_cmd cmds[] = {
 	{ "parse-options", cmd__parse_options },
 	{ "path-utils", cmd__path_utils },
 	{ "pkt-line", cmd__pkt_line },
+	{ "prefix-map", cmd__prefix_map },
 	{ "prio-queue", cmd__prio_queue },
 	{ "reach", cmd__reach },
 	{ "read-cache", cmd__read_cache },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index 25abed1cf2..33a089ee4e 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -29,6 +29,7 @@ int cmd__online_cpus(int argc, const char **argv);
 int cmd__parse_options(int argc, const char **argv);
 int cmd__path_utils(int argc, const char **argv);
 int cmd__pkt_line(int argc, const char **argv);
+int cmd__prefix_map(int argc, const char **argv);
 int cmd__prio_queue(int argc, const char **argv);
 int cmd__reach(int argc, const char **argv);
 int cmd__read_cache(int argc, const char **argv);
diff --git a/t/t0016-prefix-map.sh b/t/t0016-prefix-map.sh
new file mode 100755
index 0000000000..187fa92aec
--- /dev/null
+++ b/t/t0016-prefix-map.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+test_description='basic tests for prefix map'
+. ./test-lib.sh
+
+test_expect_success 'prefix map' '
+	test-tool prefix-map
+'
+
+test_done
-- 
gitgitgadget


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

* [PATCH 08/11] built-in add -i: show unique prefixes of the commands
  2019-04-10 17:37 [PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                   ` (6 preceding siblings ...)
  2019-04-10 17:37 ` [PATCH 07/11] Add a function to determine unique prefixes for a list of strings Slavica Djukic via GitGitGadget
@ 2019-04-10 17:37 ` Slavica Djukic via GitGitGadget
  2019-04-10 17:37 ` [PATCH 09/11] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Slavica Djukic via GitGitGadget @ 2019-04-10 17:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Slavica Djukic

From: Slavica Djukic <slawica92@hotmail.com>

Just like in the Perl script `git-add--interactive.perl`, for each
command a unique prefix is determined (if there exists any within the
given parameters), and shown in the list, and accepted as a shortcut for
the command.

We use the prefix map implementation that we just added in the previous
commit for that purpose.

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
---
 add-interactive.c | 70 ++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 57 insertions(+), 13 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index c8bd62369e..62ce446dd9 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -6,6 +6,7 @@
 #include "diffcore.h"
 #include "revision.h"
 #include "refs.h"
+#include "prefix-map.h"
 
 static int use_color = -1;
 
@@ -53,18 +54,33 @@ int add_i_config(const char *var, const char *value, void *cb)
 	return git_color_default_config(var, value, cb);
 }
 
-struct item {
-	const char *name;
-};
+static ssize_t find_unique(const char *string,
+			   struct prefix_item **list, size_t nr)
+{
+	ssize_t found = -1, i;
+
+	for (i = 0; i < nr; i++) {
+		struct prefix_item *item = list[i];
+		if (!starts_with(item->name, string))
+			continue;
+		if (found >= 0)
+			return -1;
+		found = i;
+	}
+
+	return found;
+}
 
 struct list_options {
 	int columns;
 	const char *header;
-	void (*print_item)(int i, struct item *item, void *print_item_data);
+	void (*print_item)(int i, struct prefix_item *item,
+			   void *print_item_data);
 	void *print_item_data;
 };
 
-static void list(struct item **list, size_t nr, struct list_options *opts)
+static void list(struct prefix_item **list, size_t nr,
+		 struct list_options *opts)
 {
 	int i, last_lf = 0;
 
@@ -101,12 +117,14 @@ struct list_and_choose_options {
 /*
  * Returns the selected index.
  */
-static ssize_t list_and_choose(struct item **items, size_t nr,
+static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 			       struct list_and_choose_options *opts)
 {
 	struct strbuf input = STRBUF_INIT;
 	ssize_t res = -1;
 
+	find_unique_prefixes(items, nr, 1, 4);
+
 	for (;;) {
 		char *p, *endp;
 
@@ -146,6 +164,9 @@ static ssize_t list_and_choose(struct item **items, size_t nr,
 			}
 
 			p[sep] = '\0';
+			if (index < 0)
+				index = find_unique(p, items, nr);
+
 			if (index < 0 || index >= nr)
 				printf(_("Huh (%s)?\n"), p);
 			else {
@@ -171,7 +192,7 @@ struct adddel {
 
 struct file_list {
 	struct file_item {
-		struct item item;
+		struct prefix_item item;
 		struct adddel index, worktree;
 	} **file;
 	size_t nr, alloc;
@@ -337,12 +358,29 @@ static void populate_wi_changes(struct strbuf *buf,
 		strbuf_addstr(buf, no_changes);
 }
 
+/* filters out prefixes which have special meaning to list_and_choose() */
+static int is_valid_prefix(const char *prefix, size_t prefix_len)
+{
+	return prefix_len && prefix &&
+		/*
+		 * We expect `prefix` to be NUL terminated, therefore this
+		 * `strcspn()` call is okay, even if it might do much more
+		 * work than strictly necessary.
+		 */
+		strcspn(prefix, " \t\r\n,") >= prefix_len &&	/* separators */
+		*prefix != '-' &&				/* deselection */
+		!isdigit(*prefix) &&				/* selection */
+		(prefix_len != 1 ||
+		 (*prefix != '*' &&				/* "all" wildcard */
+		  *prefix != '?'));				/* prompt help */
+}
+
 struct print_file_item_data {
 	const char *modified_fmt;
 	struct strbuf buf, index, worktree;
 };
 
-static void print_file_item(int i, struct item *item,
+static void print_file_item(int i, struct prefix_item *item,
 			    void *print_file_item_data)
 {
 	struct file_item *c = (struct file_item *)item;
@@ -369,20 +407,26 @@ static int run_status(struct repository *r, const struct pathspec *ps,
 		return -1;
 
 	if (files->nr)
-		list((struct item **)files->file, files->nr, opts);
+		list((struct prefix_item **)files->file, files->nr, opts);
 	putchar('\n');
 
 	return 0;
 }
 
-static void print_command_item(int i, struct item *item,
+static void print_command_item(int i, struct prefix_item *item,
 			       void *print_command_item_data)
 {
-	printf(" %2d: %s", i + 1, item->name);
+	if (!item->prefix_length ||
+	    !is_valid_prefix(item->name, item->prefix_length))
+		printf(" %2d: %s", i + 1, item->name);
+	else
+		printf(" %3d: [%.*s]%s", i + 1,
+		       (int)item->prefix_length, item->name,
+		       item->name + item->prefix_length);
 }
 
 struct command_item {
-	struct item item;
+	struct prefix_item item;
 	int (*command)(struct repository *r, const struct pathspec *ps,
 		       struct file_list *files, struct list_options *opts);
 };
@@ -420,7 +464,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 		res = -1;
 
 	for (;;) {
-		i = list_and_choose((struct item **)commands,
+		i = list_and_choose((struct prefix_item **)commands,
 				    ARRAY_SIZE(commands), &main_loop_opts);
 		if (i < -1) {
 			printf(_("Bye.\n"));
-- 
gitgitgadget


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

* [PATCH 09/11] built-in add -i: support `?` (prompt help)
  2019-04-10 17:37 [PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                   ` (7 preceding siblings ...)
  2019-04-10 17:37 ` [PATCH 08/11] built-in add -i: show unique prefixes of the commands Slavica Djukic via GitGitGadget
@ 2019-04-10 17:37 ` Johannes Schindelin via GitGitGadget
  2019-04-10 17:37 ` [PATCH 10/11] built-in add -i: use color in the main loop Slavica Djukic via GitGitGadget
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-04-10 17:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

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

With this change, we print out the same colored help text that the
Perl-based `git add -i` prints in the main loop when question mark is
entered.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index 62ce446dd9..03d0770013 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -12,10 +12,12 @@ static int use_color = -1;
 
 enum color_add_i {
 	COLOR_HEADER = 0,
+	COLOR_HELP,
 };
 
 static char list_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_BOLD,      /* Header */
+	GIT_COLOR_BOLD_RED,  /* Help */
 };
 
 static const char *get_add_i_color(enum color_add_i ix)
@@ -29,6 +31,8 @@ static int parse_color_slot(const char *slot)
 {
 	if (!strcasecmp(slot, "header"))
 		return COLOR_HEADER;
+	if (!strcasecmp(slot, "help"))
+		return COLOR_HELP;
 
 	return -1;
 }
@@ -112,6 +116,7 @@ struct list_and_choose_options {
 	struct list_options list_opts;
 
 	const char *prompt;
+	void (*print_help)(void);
 };
 
 /*
@@ -145,6 +150,11 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 		if (!input.len)
 			break;
 
+		if (!strcmp(input.buf, "?")) {
+			opts->print_help();
+			continue;
+		}
+
 		p = input.buf;
 		for (;;) {
 			size_t sep = strcspn(p, " \t\r\n,");
@@ -431,11 +441,23 @@ struct command_item {
 		       struct file_list *files, struct list_options *opts);
 };
 
+static void command_prompt_help(void)
+{
+	const char *help_color = get_add_i_color(COLOR_HELP);
+	color_fprintf_ln(stdout, help_color, "%s", _("Prompt help:"));
+	color_fprintf_ln(stdout, help_color, "1          - %s",
+			 _("select a numbered item"));
+	color_fprintf_ln(stdout, help_color, "foo        - %s",
+			 _("select item based on unique prefix"));
+	color_fprintf_ln(stdout, help_color, "           - %s",
+			 _("(empty) select nothing"));
+}
+
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct list_and_choose_options main_loop_opts = {
 		{ 4, N_("*** Commands ***"), print_command_item, NULL },
-		N_("What now")
+		N_("What now"), command_prompt_help
 	};
 	struct command_item
 		status = { { "status" }, run_status };
-- 
gitgitgadget


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

* [PATCH 10/11] built-in add -i: use color in the main loop
  2019-04-10 17:37 [PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                   ` (8 preceding siblings ...)
  2019-04-10 17:37 ` [PATCH 09/11] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
@ 2019-04-10 17:37 ` Slavica Djukic via GitGitGadget
  2019-04-10 17:37 ` [PATCH 11/11] built-in add -i: implement the `help` command Johannes Schindelin via GitGitGadget
  2019-05-13 17:27 ` [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  11 siblings, 0 replies; 124+ messages in thread
From: Slavica Djukic via GitGitGadget @ 2019-04-10 17:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Slavica Djukic

From: Slavica Djukic <slawica92@hotmail.com>

The error messages as well as the unique prefixes are colored in `git
add -i` by default; We need to do the same in the built-in version.

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 40 +++++++++++++++++++++++++++++++++++-----
 1 file changed, 35 insertions(+), 5 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 03d0770013..a1550d9b9f 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -13,11 +13,17 @@ static int use_color = -1;
 enum color_add_i {
 	COLOR_HEADER = 0,
 	COLOR_HELP,
+	COLOR_PROMPT,
+	COLOR_ERROR,
+	COLOR_RESET,
 };
 
 static char list_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_BOLD,      /* Header */
 	GIT_COLOR_BOLD_RED,  /* Help */
+	GIT_COLOR_BOLD_BLUE, /* Prompt */
+	GIT_COLOR_BOLD_RED,  /* Error */
+	GIT_COLOR_RESET,     /* Reset */
 };
 
 static const char *get_add_i_color(enum color_add_i ix)
@@ -33,6 +39,12 @@ static int parse_color_slot(const char *slot)
 		return COLOR_HEADER;
 	if (!strcasecmp(slot, "help"))
 		return COLOR_HELP;
+	if (!strcasecmp(slot, "prompt"))
+		return COLOR_PROMPT;
+	if (!strcasecmp(slot, "error"))
+		return COLOR_ERROR;
+	if (!strcasecmp(slot, "reset"))
+		return COLOR_RESET;
 
 	return -1;
 }
@@ -125,6 +137,8 @@ struct list_and_choose_options {
 static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 			       struct list_and_choose_options *opts)
 {
+	const char *prompt_color = get_add_i_color(COLOR_PROMPT);
+	const char *error_color = get_add_i_color(COLOR_ERROR);
 	struct strbuf input = STRBUF_INIT;
 	ssize_t res = -1;
 
@@ -137,7 +151,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 
 		list(items, nr, &opts->list_opts);
 
-		printf("%s%s", opts->prompt, "> ");
+		color_fprintf(stdout, prompt_color, "%s", opts->prompt);
+		fputs("> ", stdout);
 		fflush(stdout);
 
 		if (strbuf_getline(&input, stdin) == EOF) {
@@ -178,7 +193,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 				index = find_unique(p, items, nr);
 
 			if (index < 0 || index >= nr)
-				printf(_("Huh (%s)?\n"), p);
+				color_fprintf_ln(stdout, error_color,
+						 _("Huh (%s)?"), p);
 			else {
 				res = index;
 				break;
@@ -423,15 +439,21 @@ static int run_status(struct repository *r, const struct pathspec *ps,
 	return 0;
 }
 
+struct print_command_item_data {
+	const char *color, *reset;
+};
+
 static void print_command_item(int i, struct prefix_item *item,
 			       void *print_command_item_data)
 {
+	struct print_command_item_data *d = print_command_item_data;
+
 	if (!item->prefix_length ||
 	    !is_valid_prefix(item->name, item->prefix_length))
 		printf(" %2d: %s", i + 1, item->name);
 	else
-		printf(" %3d: [%.*s]%s", i + 1,
-		       (int)item->prefix_length, item->name,
+		printf(" %2d: %s%.*s%s%s", i + 1,
+		       d->color, (int)item->prefix_length, item->name, d->reset,
 		       item->name + item->prefix_length);
 }
 
@@ -455,8 +477,16 @@ static void command_prompt_help(void)
 
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
+	struct print_command_item_data data = {
+		/*
+		 * When color was asked for, use the prompt color for
+		 * highlighting, otherwise use square brackets.
+		 */
+		want_color(use_color) ? get_add_i_color(COLOR_PROMPT) : "[",
+		want_color(use_color) ? get_add_i_color(COLOR_RESET) : "]"
+	};
 	struct list_and_choose_options main_loop_opts = {
-		{ 4, N_("*** Commands ***"), print_command_item, NULL },
+		{ 4, N_("*** Commands ***"), print_command_item, &data },
 		N_("What now"), command_prompt_help
 	};
 	struct command_item
-- 
gitgitgadget


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

* [PATCH 11/11] built-in add -i: implement the `help` command
  2019-04-10 17:37 [PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                   ` (9 preceding siblings ...)
  2019-04-10 17:37 ` [PATCH 10/11] built-in add -i: use color in the main loop Slavica Djukic via GitGitGadget
@ 2019-04-10 17:37 ` Johannes Schindelin via GitGitGadget
  2019-05-13 17:27 ` [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-04-10 17:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Schindelin

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

This imitates the code to show the help text from the Perl script
`git-add--interactive.perl` in the built-in version.

To make sure that it renders exactly like the Perl version of `git add
-i`, we also add a test case for that to `t3701-add-interactive.sh`.

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c          | 27 +++++++++++++++++++++++++--
 t/t3701-add-interactive.sh | 24 ++++++++++++++++++++++++
 2 files changed, 49 insertions(+), 2 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index a1550d9b9f..a6baf57e20 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -439,6 +439,27 @@ static int run_status(struct repository *r, const struct pathspec *ps,
 	return 0;
 }
 
+static int run_help(struct repository *r, const struct pathspec *ps,
+		    struct file_list *files, struct list_options *opts)
+{
+	const char *help_color = get_add_i_color(COLOR_HELP);
+
+	color_fprintf_ln(stdout, help_color, "status        - %s",
+			 _("show paths with changes"));
+	color_fprintf_ln(stdout, help_color, "update        - %s",
+			 _("add working tree state to the staged set of changes"));
+	color_fprintf_ln(stdout, help_color, "revert        - %s",
+			 _("revert staged set of changes back to the HEAD version"));
+	color_fprintf_ln(stdout, help_color, "patch         - %s",
+			 _("pick hunks and update selectively"));
+	color_fprintf_ln(stdout, help_color, "diff          - %s",
+			 _("view diff between HEAD and index"));
+	color_fprintf_ln(stdout, help_color, "add untracked - %s",
+			 _("add contents of untracked files to the staged set of changes"));
+
+	return 0;
+}
+
 struct print_command_item_data {
 	const char *color, *reset;
 };
@@ -490,9 +511,11 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 		N_("What now"), command_prompt_help
 	};
 	struct command_item
-		status = { { "status" }, run_status };
+		status = { { "status" }, run_status },
+		help = { { "help" }, run_help };
 	struct command_item *commands[] = {
-		&status
+		&status,
+		&help
 	};
 
 	struct print_file_item_data print_file_item_data = {
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 65dfbc033a..91aaef2932 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -639,4 +639,28 @@ test_expect_success 'add -p patch editing works with pathological context lines'
 	test_cmp expected-2 actual
 '
 
+test_expect_success 'show help from add--helper' '
+	git reset --hard &&
+	cat >expect <<-EOF &&
+
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>
+	<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>
+	<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>
+	<BOLD;RED>patch         - pick hunks and update selectively<RESET>
+	<BOLD;RED>diff          - view diff between HEAD and index<RESET>
+	<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>>$SP
+	Bye.
+	EOF
+	test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&
+	test_decode_color <actual.colored >actual &&
+	test_i18ncmp expect actual
+'
+
 test_done
-- 
gitgitgadget

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

* Re: [PATCH 01/11] Start to implement a built-in version of `git add --interactive`
  2019-04-10 17:37 ` [PATCH 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
@ 2019-04-18 14:31   ` Jeff Hostetler
  2019-04-18 16:06     ` Jeff King
  0 siblings, 1 reply; 124+ messages in thread
From: Jeff Hostetler @ 2019-04-18 14:31 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget, git
  Cc: Junio C Hamano, Johannes Schindelin



On 4/10/2019 1:37 PM, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
> 
> This is hardly the first conversion of a Git command that is implemented
> as a script to a built-in. So far, the most successful strategy for such
> conversions has been to add a built-in helper and call that for more and
> more functionality from the script, as more and more parts are
> converted.
> 
> With the interactive add, we choose a different strategy. The sole
> reason for this is that on Windows (where such a conversion has the most
> benefits in terms of speed and robustness) we face the very specific
> problem that a `system()` call in Perl seems to close `stdin` in the
> parent process when the spawned process consumes even one character from
> `stdin`. And that just does not work for us here, as it would stop the
> main loop as soon as any interactive command was performed by the
> helper. Which is almost all of the commands in `git add -i`.
> 
> It is almost as if Perl told us once again that it does not want us to
> use it on Windows.
> 
> Instead, we follow the opposite route where we start with a bare-bones
> version of the built-in interactive add, guarded by the new
> `add.interactive.useBuiltin` config variable, and then add more and more
> functionality to it, until it is feature complete.
> 
> At this point, the built-in version of `git add -i` only states that it
> cannot do anything yet ;-)
> 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>   Documentation/config/add.txt |  5 +++++
>   Makefile                     |  1 +
>   add-interactive.c            | 13 +++++++++++++
>   add-interactive.h            | 10 ++++++++++
>   builtin/add.c                | 16 +++++++++++++++-
>   t/README                     |  4 ++++
>   6 files changed, 48 insertions(+), 1 deletion(-)
>   create mode 100644 add-interactive.c
>   create mode 100644 add-interactive.h
> 
> diff --git a/Documentation/config/add.txt b/Documentation/config/add.txt
> index 4d753f006e..c9f748f81c 100644
> --- a/Documentation/config/add.txt
> +++ b/Documentation/config/add.txt
> @@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::
>   	option of linkgit:git-add[1].  `add.ignore-errors` is deprecated,
>   	as it does not follow the usual naming convention for configuration
>   	variables.
> +
> +add.interactive.useBuiltin::
> +	[EXPERIMENTAL] Set to `true` to use the experimental built-in
> +	implementation of the interactive version of linkgit:git-add[1]
> +	instead of the Perl script version. Is `false` by default.
> diff --git a/Makefile b/Makefile
> index c5240942f2..18e656a32f 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -848,6 +848,7 @@ LIB_H = $(shell $(FIND) . \
>   	-name '*.h' -print)
>   
>   LIB_OBJS += abspath.o
> +LIB_OBJS += add-interactive.o
>   LIB_OBJS += advice.o
>   LIB_OBJS += alias.o
>   LIB_OBJS += alloc.o
> diff --git a/add-interactive.c b/add-interactive.c
> new file mode 100644
> index 0000000000..540bf185d8
> --- /dev/null
> +++ b/add-interactive.c
> @@ -0,0 +1,13 @@
> +#include "cache.h"
> +#include "add-interactive.h"
> +#include "config.h"
> +
> +int add_i_config(const char *var, const char *value, void *cb)
> +{
> +	return git_default_config(var, value, cb);
> +}
[...]
> diff --git a/builtin/add.c b/builtin/add.c
> index db2dfa4350..5a32a755c8 100644
> --- a/builtin/add.c
> +++ b/builtin/add.c
[...]
> +static int use_builtin_add_i;
[...]
> @@ -319,7 +324,12 @@ static int add_config(const char *var, const char *value, void *cb)
>   		ignore_add_errors = git_config_bool(var, value);
>   		return 0;
>   	}
> -	return git_default_config(var, value, cb);
> +	if (!strcmp(var, "add.interactive.usebuiltin")) {
> +		use_builtin_add_i = git_config_bool(var, value);
> +		return 0;
> +	}
> +
> +	return add_i_config(var, value, cb);
 >   }

Something about this split between add_config() in this file
and add_i_config() in add-interactive.c bothers me.  I'm not
saying it is wrong, but it bothers me.

Perhaps it is that we moved the call to git_default_config()
to add-interactive.c -- so correct behavior of the non-interactive
case depends on add-interactive.c to do the right thing.

Also, since we can't control the order of observed the k/v pairs,
we can't do "if (use_builtin_add_i) return add_i_config(...);"
And that wouldn't allow both versions to pickup a common config
setting (in their own static variables).

Currently, neither function looks at any other k/v pairs, so
this is a bit of a moot point, but I'm wondering if this should
look like this:

     int add_config(...)
     {
         // give add-interactive.c a chance to look at k/v pair, but
         // do not short-cut because we don't know yet whether we
         // will be interactive or not yet.
         (void)add_i_config(...);

         ...ignore_add_errors...
         ...use_builtin_add_i...

         return git_default_config(...);
     }

     int add_i_config(...)
     {
         return 0;
     }

or just inline everything here in add_config() and be done with it.


Jeff


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

* Re: [PATCH 01/11] Start to implement a built-in version of `git add --interactive`
  2019-04-18 14:31   ` Jeff Hostetler
@ 2019-04-18 16:06     ` Jeff King
  2019-04-30 23:40       ` Johannes Schindelin
  0 siblings, 1 reply; 124+ messages in thread
From: Jeff King @ 2019-04-18 16:06 UTC (permalink / raw)
  To: Jeff Hostetler
  Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano,
	Johannes Schindelin

On Thu, Apr 18, 2019 at 10:31:30AM -0400, Jeff Hostetler wrote:

> Currently, neither function looks at any other k/v pairs, so
> this is a bit of a moot point, but I'm wondering if this should
> look like this:
> 
>     int add_config(...)
>     {
>         // give add-interactive.c a chance to look at k/v pair, but
>         // do not short-cut because we don't know yet whether we
>         // will be interactive or not yet.
>         (void)add_i_config(...);
> 
>         ...ignore_add_errors...
>         ...use_builtin_add_i...
> 
>         return git_default_config(...);
>     }

Yeah, I agree this split seems a bit more natural. It is worth
propagating errors from add_i_config(), though, like:

  if (add_i_config(var, value, data))
	return -1;

so that any key-specific errors (e.g., config_error_nonbool) stop the
parsing in the usual way.

-Peff

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

* Re: [PATCH 06/11] built-in add -i: implement the main loop
  2019-04-10 17:37 ` [PATCH 06/11] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
@ 2019-04-18 16:49   ` Jeff Hostetler
  2019-05-13 12:04     ` Johannes Schindelin
  0 siblings, 1 reply; 124+ messages in thread
From: Jeff Hostetler @ 2019-04-18 16:49 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget, git
  Cc: Junio C Hamano, Johannes Schindelin



On 4/10/2019 1:37 PM, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
> 
> The reason why we did not start with the main loop to begin with is that
> it is the first user of `list_and_choose()`, which uses the `list()`
> function that we conveniently introduced for use by the `status`
> command.
> 
> Apart from the "and choose" part, there are more differences between the
> way the `status` command calls the `list_and_choose()` function in the
> Perl version of `git add -i` compared to the other callers of said
> function. The most important ones:
> 
> - The list is not only shown, but the user is also asked to make a
>    choice, possibly selecting multiple entries.
> 
> - The list of items is prefixed with a marker indicating what items have
>    been selected, if multi-selection is allowed.
> 
> - Initially, for each item a unique prefix (if there exists any within
>    the given parameters) is determined, and shown in the list, and
>    accepted as a shortcut for the selection.
> 
> These features will be implemented later, except the part where the user
> can choose a command. At this stage, though, the built-in `git add -i`
> still only supports the `status` command, with the remaining commands to
> follow over the course of the next commits.
> 
> In addition, we also modify `list()` to support displaying the commands
> in columns, even if there is currently only one.
> 
> The Perl script `git-add--interactive.perl` mixed the purposes of the
> "list" and the "and choose" part into the same function. In the C
> version, we will keep them separate instead, calling the `list()`
> function from the `list_and_choose()` function.
> 
> Note that we only have a prompt ending in a single ">" at this stage;
> later commits will add commands that display a double ">>" to indicate
> that the user is in a different loop than the main one.
> 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>   add-interactive.c | 122 +++++++++++++++++++++++++++++++++++++++++++++-
>   1 file changed, 120 insertions(+), 2 deletions(-)
> 
> diff --git a/add-interactive.c b/add-interactive.c
> index 79adc58321..c8bd62369e 100644
> --- a/add-interactive.c
> +++ b/add-interactive.c
> @@ -58,6 +58,7 @@ struct item {
>   };
>   
>   struct list_options {
> +	int columns;
>   	const char *header;
>   	void (*print_item)(int i, struct item *item, void *print_item_data);
>   	void *print_item_data;
> @@ -65,7 +66,7 @@ struct list_options {
>   
>   static void list(struct item **list, size_t nr, struct list_options *opts)
>   {
> -	int i;
> +	int i, last_lf = 0;
>   
>   	if (!nr)
>   		return;
> @@ -77,8 +78,90 @@ static void list(struct item **list, size_t nr, struct list_options *opts)
>   
>   	for (i = 0; i < nr; i++) {
>   		opts->print_item(i, list[i], opts->print_item_data);
> +
> +		if ((opts->columns) && ((i + 1) % (opts->columns))) {
> +			putchar('\t');
> +			last_lf = 0;
> +		}
> +		else {
> +			putchar('\n');
> +			last_lf = 1;
> +		}
> +	}
> +
> +	if (!last_lf)
>   		putchar('\n');
> +}
> +struct list_and_choose_options {
> +	struct list_options list_opts;
> +
> +	const char *prompt;
> +};
> +
> +/*
> + * Returns the selected index.
> + */
> +static ssize_t list_and_choose(struct item **items, size_t nr,
> +			       struct list_and_choose_options *opts)
> +{
> +	struct strbuf input = STRBUF_INIT;
> +	ssize_t res = -1;
> +
> +	for (;;) {
> +		char *p, *endp;
> +
> +		strbuf_reset(&input);
> +
> +		list(items, nr, &opts->list_opts);
> +
> +		printf("%s%s", opts->prompt, "> ");
> +		fflush(stdout);
> +
> +		if (strbuf_getline(&input, stdin) == EOF) {
> +			putchar('\n');
> +			res = -2;

It would be nice to know what -1 and -2 mean if
they get returned to our caller.  Maybe a #define
for these??

> +			break;
> +		}
> +		strbuf_trim(&input);
> +
> +		if (!input.len)
> +			break;
> +
> +		p = input.buf;
> +		for (;;) {
> +			size_t sep = strcspn(p, " \t\r\n,");
> +			ssize_t index = -1;
> +
> +			if (!sep) {
> +				if (!*p)
> +					break;
> +				p++;
> +				continue;
> +			}
> +
> +			if (isdigit(*p)) {
> +				index = strtoul(p, &endp, 10) - 1;
> +				if (endp != p + sep)
> +					index = -1;
> +			}
> +
> +			p[sep] = '\0';
> +			if (index < 0 || index >= nr)
> +				printf(_("Huh (%s)?\n"), p);
> +			else {
> +				res = index;
> +				break;
> +			}
> +
> +			p += sep + 1;
> +		}
> +
> +		if (res >= 0)
> +			break;
>   	}
> +
> +	strbuf_release(&input);
> +	return res;
>   }
>   
>   struct adddel {
> @@ -292,16 +375,39 @@ static int run_status(struct repository *r, const struct pathspec *ps,
>   	return 0;
>   }
>   
> +static void print_command_item(int i, struct item *item,
> +			       void *print_command_item_data)
> +{
> +	printf(" %2d: %s", i + 1, item->name);
> +}
> +
> +struct command_item {
> +	struct item item;
> +	int (*command)(struct repository *r, const struct pathspec *ps,
> +		       struct file_list *files, struct list_options *opts);
> +};
> +
>   int run_add_i(struct repository *r, const struct pathspec *ps)
>   {
> +	struct list_and_choose_options main_loop_opts = {
> +		{ 4, N_("*** Commands ***"), print_command_item, NULL },
> +		N_("What now")
> +	};
> +	struct command_item
> +		status = { { "status" }, run_status };
> +	struct command_item *commands[] = {
> +		&status
> +	};
> +
>   	struct print_file_item_data print_file_item_data = {
>   		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
>   	};
>   	struct list_options opts = {
> -		NULL, print_file_item, &print_file_item_data
> +		0, NULL, print_file_item, &print_file_item_data
>   	};
>   	struct strbuf header = STRBUF_INIT;
>   	struct file_list files = { NULL };
> +	ssize_t i;
>   	int res = 0;
>   
>   	strbuf_addstr(&header, "      ");
> @@ -313,6 +419,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
>   	if (run_status(r, ps, &files, &opts) < 0)
>   		res = -1;
>   
> +	for (;;) {
> +		i = list_and_choose((struct item **)commands,
> +				    ARRAY_SIZE(commands), &main_loop_opts);
> +		if (i < -1) {
> +			printf(_("Bye.\n"));
> +			res = 0;
> +			break;
> +		}
> +		if (i >= 0)
> +			res = commands[i]->command(r, ps, &files, &opts);
> +	}
> +
>   	release_file_list(&files);
>   	strbuf_release(&print_file_item_data.buf);
>   	strbuf_release(&print_file_item_data.index);
> 

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

* Re: [PATCH 07/11] Add a function to determine unique prefixes for a list of strings
  2019-04-10 17:37 ` [PATCH 07/11] Add a function to determine unique prefixes for a list of strings Slavica Djukic via GitGitGadget
@ 2019-04-18 17:57   ` Jeff Hostetler
  2019-05-13 12:48     ` Johannes Schindelin
  0 siblings, 1 reply; 124+ messages in thread
From: Jeff Hostetler @ 2019-04-18 17:57 UTC (permalink / raw)
  To: Slavica Djukic via GitGitGadget, git; +Cc: Junio C Hamano, Slavica Djukic



On 4/10/2019 1:37 PM, Slavica Djukic via GitGitGadget wrote:
> From: Slavica Djukic <slawica92@hotmail.com>
> 
> In the `git add -i` command, we show unique prefixes of the commands and
> files, to give an indication what prefix would select them.
> 
> Naturally, the C implementation looks a lot different than the Perl
> implementation: in Perl, a trie is much easier implemented, while we
> already have a pretty neat hashmap implementation in C that we use for
> the purpose of storing (not necessarily unique) prefixes.
> 
> The idea: for each item that we add, we generate prefixes starting with
> the first letter, then the first two letters, then three, etc, until we
> find a prefix that is unique (or until the prefix length would be
> longer than we want). If we encounter a previously-unique prefix on the
> way, we adjust that item's prefix to make it unique again (or we mark it
> as having no unique prefix if we failed to find one). These partial
> prefixes are stored in a hash map (for quick lookup times).
> 
> To make sure that this function works as expected, we add a test using a
> special-purpose test helper that was added for that purpose.
> 
> Note: We expect the list of prefix items to be passed in as a list of
> pointers rather than as regular list to avoid having to copy information
> (the actual items will most likely contain more information than just
> the name and the length of the unique prefix, but passing in `struct
> prefix_item *` would not allow for that).
> 
> Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>   Makefile                   |   2 +
>   prefix-map.c               | 111 +++++++++++++++++++++++++++++++++++++
>   prefix-map.h               |  40 +++++++++++++
>   t/helper/test-prefix-map.c |  58 +++++++++++++++++++
>   t/helper/test-tool.c       |   1 +
>   t/helper/test-tool.h       |   1 +
>   t/t0016-prefix-map.sh      |  10 ++++
>   7 files changed, 223 insertions(+)
>   create mode 100644 prefix-map.c
>   create mode 100644 prefix-map.h
>   create mode 100644 t/helper/test-prefix-map.c
>   create mode 100755 t/t0016-prefix-map.sh
> 
> diff --git a/Makefile b/Makefile
> index 18e656a32f..8299b3f17d 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -754,6 +754,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o
>   TEST_BUILTINS_OBJS += test-parse-options.o
>   TEST_BUILTINS_OBJS += test-path-utils.o
>   TEST_BUILTINS_OBJS += test-pkt-line.o
> +TEST_BUILTINS_OBJS += test-prefix-map.o
>   TEST_BUILTINS_OBJS += test-prio-queue.o
>   TEST_BUILTINS_OBJS += test-reach.o
>   TEST_BUILTINS_OBJS += test-read-cache.o
> @@ -967,6 +968,7 @@ LIB_OBJS += patch-ids.o
>   LIB_OBJS += path.o
>   LIB_OBJS += pathspec.o
>   LIB_OBJS += pkt-line.o
> +LIB_OBJS += prefix-map.o
>   LIB_OBJS += preload-index.o
>   LIB_OBJS += pretty.o
>   LIB_OBJS += prio-queue.o
> diff --git a/prefix-map.c b/prefix-map.c
> new file mode 100644
> index 0000000000..3c5ae4ae0a
> --- /dev/null
> +++ b/prefix-map.c
> @@ -0,0 +1,111 @@
> +#include "cache.h"
> +#include "prefix-map.h"
> +
> +static int map_cmp(const void *unused_cmp_data,
> +		   const void *entry,
> +		   const void *entry_or_key,
> +		   const void *unused_keydata)
> +{
> +	const struct prefix_map_entry *a = entry;
> +	const struct prefix_map_entry *b = entry_or_key;
> +
> +	return a->prefix_length != b->prefix_length ||
> +		strncmp(a->name, b->name, a->prefix_length);
> +}
> +
> +static void add_prefix_entry(struct hashmap *map, const char *name,
> +			     size_t prefix_length, struct prefix_item *item)
> +{
> +	struct prefix_map_entry *result = xmalloc(sizeof(*result));
> +	result->name = name;
> +	result->prefix_length = prefix_length;
> +	result->item = item;
> +	hashmap_entry_init(result, memhash(name, prefix_length));
> +	hashmap_add(map, result);
> +}
> +
> +static void init_prefix_map(struct prefix_map *prefix_map,
> +			    int min_prefix_length, int max_prefix_length)
> +{
> +	hashmap_init(&prefix_map->map, map_cmp, NULL, 0);
> +	prefix_map->min_length = min_prefix_length;
> +	prefix_map->max_length = max_prefix_length;
> +}
> +
> +static void add_prefix_item(struct prefix_map *prefix_map,
> +			    struct prefix_item *item)
> +{
> +	struct prefix_map_entry *e = xmalloc(sizeof(*e)), *e2;
> +	int j;
> +
> +	e->item = item;
> +	e->name = e->item->name;
> +
> +	for (j = prefix_map->min_length; j <= prefix_map->max_length; j++) {
> +		if (!isascii(e->name[j])) {

This feels odd, if I understand the intent.

First, why "isascii()" rather than just non-zero?

But mainly, can we walk off the end of the array and read
potentially uninitialized memory?  Shouldn't we have something
at the top of the function like:

     len = strlen(item->name);
     if (len < prefix_map->min_length)
         return;

(And maybe avoid the xmalloc() too?)

And maybe do " j <= min(len, max_length) " in the loop?
But I see you're modifying "j" down in the body of the loop,
so I'll wait on suggesting that.

> +			free(e);
> +			break;
> +		}
> +
> +		e->prefix_length = j;
> +		hashmap_entry_init(e, memhash(e->name, j));
> +		e2 = hashmap_get(&prefix_map->map, e, NULL);
> +		if (!e2) {
> +			/* prefix is unique so far */
> +			e->item->prefix_length = j;
> +			hashmap_add(&prefix_map->map, e);
> +			break;
> +		}
> +
> +		if (!e2->item)
> +			continue; /* non-unique prefix */
> +
> +		if (j != e2->item->prefix_length)
> +			BUG("unexpected prefix length: %d != %d",
> +			    (int)j, (int)e2->item->prefix_length);

IIUC, this assurance comes directly from map_cmp(), right?
We could strengthen this to
      (j != e2->item->prefix_length || strncmp(...))
if we wanted to, right?

> +
> +		/* skip common prefix */
> +		for (; j < prefix_map->max_length && e->name[j]; j++) {
> +			if (e->item->name[j] != e2->item->name[j])
> +				break;

Same comment here about walking off of the defined end of both arrays.

I'm going to stop here.  I'm getting confused.

> +			add_prefix_entry(&prefix_map->map, e->name, j + 1,
> +					 NULL);
> +		}
> +
> +		/* e2 no longer refers to a unique prefix */
> +		if (j < prefix_map->max_length && e2->name[j]) {
> +			/* found a new unique prefix for e2's item */
> +			e2->item->prefix_length = j + 1;
> +			add_prefix_entry(&prefix_map->map, e2->name, j + 1,
> +					 e2->item);
> +		}
> +		else
> +			e2->item->prefix_length = 0;
> +		e2->item = NULL;
> +
> +		if (j < prefix_map->max_length && e->name[j]) {
> +			/* found a unique prefix for the item */
> +			e->item->prefix_length = j + 1;
> +			add_prefix_entry(&prefix_map->map, e->name, j + 1,
> +					 e->item);
> +		} else {
> +			/* item has no (short enough) unique prefix */
> +			e->item->prefix_length = 0;
> +			free(e);
> +		}
> +
> +		break;
> +	}
> +}
> +
> +void find_unique_prefixes(struct prefix_item **list, size_t nr,
> +			  int min_length, int max_length)
> +{
> +	int i;
> +	struct prefix_map prefix_map;
> +
> +	init_prefix_map(&prefix_map, min_length, max_length);
> +	for (i = 0; i < nr; i++)
> +		add_prefix_item(&prefix_map, list[i]);
> +	hashmap_free(&prefix_map.map, 1);
> +}
> diff --git a/prefix-map.h b/prefix-map.h
> new file mode 100644
> index 0000000000..ce3b8a4a32
> --- /dev/null
> +++ b/prefix-map.h
> @@ -0,0 +1,40 @@
> +#ifndef PREFIX_MAP_H
> +#define PREFIX_MAP_H
> +
> +#include "hashmap.h"
> +
> +struct prefix_item {
> +	const char *name;
> +	size_t prefix_length;
> +};
> +
> +struct prefix_map_entry {
> +	struct hashmap_entry e;
> +	const char *name;
> +	size_t prefix_length;
> +	/* if item is NULL, the prefix is not unique */
> +	struct prefix_item *item;
> +};
> +
> +struct prefix_map {
> +	struct hashmap map;
> +	int min_length, max_length;
> +};
> +
> +/*
> + * Find unique prefixes in a given list of strings.
> + *
> + * Typically, the `struct prefix_item` information will be but a field in the
> + * actual item struct; For this reason, the `list` parameter is specified as a
> + * list of pointers to the items.
> + *
> + * The `min_length`/`max_length` parameters define what length the unique
> + * prefixes should have.
> + *
> + * If no unique prefix could be found for a given item, its `prefix_length`
> + * will be set to 0.
> + */
> +void find_unique_prefixes(struct prefix_item **list, size_t nr,
> +			  int min_length, int max_length);
> +
> +#endif
> diff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c
> new file mode 100644
> index 0000000000..3f1c90eaf0
> --- /dev/null
> +++ b/t/helper/test-prefix-map.c
> @@ -0,0 +1,58 @@
> +#include "test-tool.h"
> +#include "cache.h"
> +#include "prefix-map.h"
> +
> +static size_t test_count, failed_count;
> +
> +static void check(int succeeded, const char *file, size_t line_no,
> +		  const char *fmt, ...)
> +{
> +	va_list ap;
> +
> +	test_count++;
> +	if (succeeded)
> +		return;
> +
> +	va_start(ap, fmt);
> +	fprintf(stderr, "%s:%d: ", file, (int)line_no);
> +	vfprintf(stderr, fmt, ap);
> +	fputc('\n', stderr);
> +	va_end(ap);
> +
> +	failed_count++;
> +}
> +
> +#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \
> +	check(expect == actual, __FILE__, __LINE__, \
> +	      "size_t's do not match: %" \
> +	      PRIdMAX " != %" PRIdMAX " (%s) (%s)", \
> +	      (intmax_t)expect, (intmax_t)actual, #actual, hint)
> +
> +int cmd__prefix_map(int argc, const char **argv)
> +{
> +#define NR 5
> +	struct prefix_item items[NR] = {
> +		{ "unique" },
> +		{ "hell" },
> +		{ "hello" },
> +		{ "wok" },
> +		{ "world" },
> +	};
> +	struct prefix_item *list[NR] = {
> +		items, items + 1, items + 2, items + 3, items + 4
> +	};
> +
> +	find_unique_prefixes(list, NR, 1, 3);
> +
> +#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \
> +	EXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \
> +			     list[index]->name)
> +
> +	EXPECT_PREFIX_LENGTH_EQUALS(1, 0);
> +	EXPECT_PREFIX_LENGTH_EQUALS(0, 1);
> +	EXPECT_PREFIX_LENGTH_EQUALS(0, 2);
> +	EXPECT_PREFIX_LENGTH_EQUALS(3, 3);
> +	EXPECT_PREFIX_LENGTH_EQUALS(3, 4);
> +
> +	return !!failed_count;
> +}
> diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
> index 99db7409b8..d6a92a8699 100644
> --- a/t/helper/test-tool.c
> +++ b/t/helper/test-tool.c
> @@ -32,6 +32,7 @@ static struct test_cmd cmds[] = {
>   	{ "parse-options", cmd__parse_options },
>   	{ "path-utils", cmd__path_utils },
>   	{ "pkt-line", cmd__pkt_line },
> +	{ "prefix-map", cmd__prefix_map },
>   	{ "prio-queue", cmd__prio_queue },
>   	{ "reach", cmd__reach },
>   	{ "read-cache", cmd__read_cache },
> diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
> index 25abed1cf2..33a089ee4e 100644
> --- a/t/helper/test-tool.h
> +++ b/t/helper/test-tool.h
> @@ -29,6 +29,7 @@ int cmd__online_cpus(int argc, const char **argv);
>   int cmd__parse_options(int argc, const char **argv);
>   int cmd__path_utils(int argc, const char **argv);
>   int cmd__pkt_line(int argc, const char **argv);
> +int cmd__prefix_map(int argc, const char **argv);
>   int cmd__prio_queue(int argc, const char **argv);
>   int cmd__reach(int argc, const char **argv);
>   int cmd__read_cache(int argc, const char **argv);
> diff --git a/t/t0016-prefix-map.sh b/t/t0016-prefix-map.sh
> new file mode 100755
> index 0000000000..187fa92aec
> --- /dev/null
> +++ b/t/t0016-prefix-map.sh
> @@ -0,0 +1,10 @@
> +#!/bin/sh
> +
> +test_description='basic tests for prefix map'
> +. ./test-lib.sh
> +
> +test_expect_success 'prefix map' '
> +	test-tool prefix-map
> +'
> +
> +test_done
> 

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

* Re: [PATCH 01/11] Start to implement a built-in version of `git add --interactive`
  2019-04-18 16:06     ` Jeff King
@ 2019-04-30 23:40       ` Johannes Schindelin
  2019-05-01  2:21         ` Jeff King
  0 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin @ 2019-04-30 23:40 UTC (permalink / raw)
  To: Jeff King
  Cc: Jeff Hostetler, Johannes Schindelin via GitGitGadget, git,
	Junio C Hamano

Hi Jeff & Jeff,

On Thu, 18 Apr 2019, Jeff King wrote:

> On Thu, Apr 18, 2019 at 10:31:30AM -0400, Jeff Hostetler wrote:
>
> > Currently, neither function looks at any other k/v pairs, so
> > this is a bit of a moot point, but I'm wondering if this should
> > look like this:
> >
> >     int add_config(...)
> >     {
> >         // give add-interactive.c a chance to look at k/v pair, but
> >         // do not short-cut because we don't know yet whether we
> >         // will be interactive or not yet.
> >         (void)add_i_config(...);
> >
> >         ...ignore_add_errors...
> >         ...use_builtin_add_i...
> >
> >         return git_default_config(...);
> >     }
>
> Yeah, I agree this split seems a bit more natural. It is worth
> propagating errors from add_i_config(), though, like:
>
>   if (add_i_config(var, value, data))
> 	return -1;
>
> so that any key-specific errors (e.g., config_error_nonbool) stop the
> parsing in the usual way.

The only problem there is that `add_i_config()` (like all the other
`git_config()` callbacks) does not report whether it consumed the
key/value pair or not. I tried to avoid deviating from the standard
practice to avoid calling `git_default_config()` when we already consumed
the config setting.

And I also tried pretty hard to *not* bleed any internal state of
`add-interactive` into `builtin/add`, as I wanted the new code to be as
libified as possible (in a nearby thread, somebody wished for a new `-p`
mode that would essentially be a combined `git stash -p` and `git add -p`,
and with properly libified code such a beast is a lot more feasible).

Any idea how to deal with that?

I guess I could invert the order, where `add_config()` would be called
as a fall-back from `add_i_config()`...

Or I invent a new convention where `add_i_config()` returns 1 when it
consumed the key/value pair. But that would set a precedent that is
inconsistent with the entire existing code base, something I am
uncomfortable to do for the sake of `add -i`...


Ciao,
Dscho

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

* Re: [PATCH 01/11] Start to implement a built-in version of `git add --interactive`
  2019-04-30 23:40       ` Johannes Schindelin
@ 2019-05-01  2:21         ` Jeff King
  2019-05-13 11:14           ` Johannes Schindelin
  0 siblings, 1 reply; 124+ messages in thread
From: Jeff King @ 2019-05-01  2:21 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Jeff Hostetler, Johannes Schindelin via GitGitGadget, git,
	Junio C Hamano

On Tue, Apr 30, 2019 at 07:40:06PM -0400, Johannes Schindelin wrote:

> > Yeah, I agree this split seems a bit more natural. It is worth
> > propagating errors from add_i_config(), though, like:
> >
> >   if (add_i_config(var, value, data))
> > 	return -1;
> >
> > so that any key-specific errors (e.g., config_error_nonbool) stop the
> > parsing in the usual way.
> 
> The only problem there is that `add_i_config()` (like all the other
> `git_config()` callbacks) does not report whether it consumed the
> key/value pair or not. I tried to avoid deviating from the standard
> practice to avoid calling `git_default_config()` when we already consumed
> the config setting.

I don't think it's worth worrying too much about that. We wouldn't match
the keys in multiple places anyway (and even if we did, it would
arguably be the right thing to give every callback a chance to see
them).

The only thing it does is short-circuit the rest of the checks that we
know won't match. But that doesn't really change the performance
substantially; the worst case is already that we have to hit every
possible strcmp().

And most of our config code does not worry about this, and is OK with
branching (it just needs to propagate errors, as above).  For some more
discussion, see 6680a0874f (drop odd return value semantics from
userdiff_config, 2012-02-07).

All that said...

> And I also tried pretty hard to *not* bleed any internal state of
> `add-interactive` into `builtin/add`, as I wanted the new code to be as
> libified as possible (in a nearby thread, somebody wished for a new `-p`
> mode that would essentially be a combined `git stash -p` and `git add -p`,
> and with properly libified code such a beast is a lot more feasible).
> 
> Any idea how to deal with that?

The most lib-ified thing is to just use the configset code. I.e.,
wherever you need the config, just load it on demand via
git_config_get_int or whatever.

> Or I invent a new convention where `add_i_config()` returns 1 when it
> consumed the key/value pair. But that would set a precedent that is
> inconsistent with the entire existing code base, something I am
> uncomfortable to do for the sake of `add -i`...

Yes, don't do that. :) That was the same thing we finally got rid of for
userdiff_config().

-Peff

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

* Re: [PATCH 01/11] Start to implement a built-in version of `git add --interactive`
  2019-05-01  2:21         ` Jeff King
@ 2019-05-13 11:14           ` Johannes Schindelin
  0 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin @ 2019-05-13 11:14 UTC (permalink / raw)
  To: Jeff King
  Cc: Jeff Hostetler, Johannes Schindelin via GitGitGadget, git,
	Junio C Hamano

Hi Peff,

On Tue, 30 Apr 2019, Jeff King wrote:

> On Tue, Apr 30, 2019 at 07:40:06PM -0400, Johannes Schindelin wrote:
>
> > And I also tried pretty hard to *not* bleed any internal state of
> > `add-interactive` into `builtin/add`, as I wanted the new code to be
> > as libified as possible (in a nearby thread, somebody wished for a new
> > `-p` mode that would essentially be a combined `git stash -p` and `git
> > add -p`, and with properly libified code such a beast is a lot more
> > feasible).
> >
> > Any idea how to deal with that?
>
> The most lib-ified thing is to just use the configset code. I.e.,
> wherever you need the config, just load it on demand via
> git_config_get_int or whatever.

True.

And it cost me *quite* a few days to implement the changes. But the result
is definitely a lot better, in my opinion.

> > Or I invent a new convention where `add_i_config()` returns 1 when it
> > consumed the key/value pair. But that would set a precedent that is
> > inconsistent with the entire existing code base, something I am
> > uncomfortable to do for the sake of `add -i`...
>
> Yes, don't do that. :) That was the same thing we finally got rid of for
> userdiff_config().

Thanks for stopping me. I did not remember about the userdiff_config()
thing.

Ciao,
Dscho

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

* Re: [PATCH 06/11] built-in add -i: implement the main loop
  2019-04-18 16:49   ` Jeff Hostetler
@ 2019-05-13 12:04     ` Johannes Schindelin
  0 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin @ 2019-05-13 12:04 UTC (permalink / raw)
  To: Jeff Hostetler; +Cc: Johannes Schindelin via GitGitGadget, git, Junio C Hamano

Hi Jeff,

On Thu, 18 Apr 2019, Jeff Hostetler wrote:

> On 4/10/2019 1:37 PM, Johannes Schindelin via GitGitGadget wrote:
>
> > [...]
> > +
> > +/*
> > + * Returns the selected index.
> > + */
> > +static ssize_t list_and_choose(struct item **items, size_t nr,
> > +			       struct list_and_choose_options *opts)
> > +{
> > +	struct strbuf input = STRBUF_INIT;
> > +	ssize_t res = -1;
> > +
> > +	for (;;) {
> > +		char *p, *endp;
> > +
> > +		strbuf_reset(&input);
> > +
> > +		list(items, nr, &opts->list_opts);
> > +
> > +		printf("%s%s", opts->prompt, "> ");
> > +		fflush(stdout);
> > +
> > +		if (strbuf_getline(&input, stdin) == EOF) {
> > +			putchar('\n');
> > +			res = -2;
>
> It would be nice to know what -1 and -2 mean if
> they get returned to our caller.  Maybe a #define
> for these??

Makes a total lot of sense. I changed that.

Thanks!
Dscho

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

* Re: [PATCH 07/11] Add a function to determine unique prefixes for a list of strings
  2019-04-18 17:57   ` Jeff Hostetler
@ 2019-05-13 12:48     ` Johannes Schindelin
  0 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin @ 2019-05-13 12:48 UTC (permalink / raw)
  To: Jeff Hostetler
  Cc: Slavica Djukic via GitGitGadget, git, Junio C Hamano,
	Slavica Djukic

Hi Jeff,

On Thu, 18 Apr 2019, Jeff Hostetler wrote:

> On 4/10/2019 1:37 PM, Slavica Djukic via GitGitGadget wrote:
> > From: Slavica Djukic <slawica92@hotmail.com>
> >
> > In the `git add -i` command, we show unique prefixes of the commands and
> > files, to give an indication what prefix would select them.
> >
> > Naturally, the C implementation looks a lot different than the Perl
> > implementation: in Perl, a trie is much easier implemented, while we
> > already have a pretty neat hashmap implementation in C that we use for
> > the purpose of storing (not necessarily unique) prefixes.
> >
> > The idea: for each item that we add, we generate prefixes starting with
> > the first letter, then the first two letters, then three, etc, until we
> > find a prefix that is unique (or until the prefix length would be
> > longer than we want). If we encounter a previously-unique prefix on the
> > way, we adjust that item's prefix to make it unique again (or we mark it
> > as having no unique prefix if we failed to find one). These partial
> > prefixes are stored in a hash map (for quick lookup times).
> >
> > To make sure that this function works as expected, we add a test using a
> > special-purpose test helper that was added for that purpose.
> >
> > Note: We expect the list of prefix items to be passed in as a list of
> > pointers rather than as regular list to avoid having to copy information
> > (the actual items will most likely contain more information than just
> > the name and the length of the unique prefix, but passing in `struct
> > prefix_item *` would not allow for that).
> >
> > Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> > diff --git a/prefix-map.c b/prefix-map.c
> > new file mode 100644
> > index 0000000000..3c5ae4ae0a
> > --- /dev/null
> > +++ b/prefix-map.c
> > @@ -0,0 +1,111 @@
> > +#include "cache.h"
> > +#include "prefix-map.h"
> > +
> > +static int map_cmp(const void *unused_cmp_data,
> > +		   const void *entry,
> > +		   const void *entry_or_key,
> > +		   const void *unused_keydata)
> > +{
> > +	const struct prefix_map_entry *a = entry;
> > +	const struct prefix_map_entry *b = entry_or_key;
> > +
> > +	return a->prefix_length != b->prefix_length ||
> > +		strncmp(a->name, b->name, a->prefix_length);
> > +}
> > +
> > +static void add_prefix_entry(struct hashmap *map, const char *name,
> > +			     size_t prefix_length, struct prefix_item *item)
> > +{
> > +	struct prefix_map_entry *result = xmalloc(sizeof(*result));
> > +	result->name = name;
> > +	result->prefix_length = prefix_length;
> > +	result->item = item;
> > +	hashmap_entry_init(result, memhash(name, prefix_length));
> > +	hashmap_add(map, result);
> > +}
> > +
> > +static void init_prefix_map(struct prefix_map *prefix_map,
> > +			    int min_prefix_length, int max_prefix_length)
> > +{
> > +	hashmap_init(&prefix_map->map, map_cmp, NULL, 0);
> > +	prefix_map->min_length = min_prefix_length;
> > +	prefix_map->max_length = max_prefix_length;
> > +}
> > +
> > +static void add_prefix_item(struct prefix_map *prefix_map,
> > +			    struct prefix_item *item)
> > +{
> > +	struct prefix_map_entry *e = xmalloc(sizeof(*e)), *e2;
> > +	int j;
> > +
> > +	e->item = item;
> > +	e->name = e->item->name;
> > +
> > +	for (j = prefix_map->min_length; j <= prefix_map->max_length; j++) {
> > +		if (!isascii(e->name[j])) {
>
> This feels odd, if I understand the intent.
>
> First, why "isascii()" rather than just non-zero?

That's to imitate `git-add--interactive.perl`'s

	if (ord($letters[0]) > 127 ||
	    ($soft_limit && $j + 1 > $soft_limit))

See https://github.com/git/git/blob/v2.21.0/git-add--interactive.perl#L410
for more complete context.

I think the main benefit here is that we avoid running into the trap of
using incomplete UTF-8 multi-byte sequences in prefixes.

I guess we could throw in an extra safety on the C side by excluding
control characters, too. But that would be a deviation from Perl, and I
actually do not even feel strongly about excluding, say, a HT (horizontal
tab) from the prefixes.

> But mainly, can we walk off the end of the array and read
> potentially uninitialized memory?  Shouldn't we have something
> at the top of the function like:
>
>     len = strlen(item->name);
>     if (len < prefix_map->min_length)
>         return;

Ooops, you're right. But I would not use `strlen() here, we can easily
just add `&& e->name[j]` to the loop condition.

> (And maybe avoid the xmalloc() too?)

Hmm. At first, I thought: no, we use `*e` *both* for lookup and for adding
a new item once we did not find any existing for the current prefix
length.

But it does indeed become a lot clearer when I separate those. It's not
even performance or memory critical a code path.

> And maybe do " j <= min(len, max_length) " in the loop?
> But I see you're modifying "j" down in the body of the loop,
> so I'll wait on suggesting that.
>
> > +			free(e);
> > +			break;
> > +		}
> > +
> > +		e->prefix_length = j;
> > +		hashmap_entry_init(e, memhash(e->name, j));
> > +		e2 = hashmap_get(&prefix_map->map, e, NULL);
> > +		if (!e2) {
> > +			/* prefix is unique so far */
> > +			e->item->prefix_length = j;
> > +			hashmap_add(&prefix_map->map, e);
> > +			break;
> > +		}
> > +
> > +		if (!e2->item)
> > +			continue; /* non-unique prefix */
> > +
> > +		if (j != e2->item->prefix_length)
> > +			BUG("unexpected prefix length: %d != %d",
> > +			    (int)j, (int)e2->item->prefix_length);
>
> IIUC, this assurance comes directly from map_cmp(), right?
> We could strengthen this to
>      (j != e2->item->prefix_length || strncmp(...))
> if we wanted to, right?

Right, I'll actually go for `memcmp()` here, but the idea is the same.

> > +
> > +		/* skip common prefix */
> > +		for (; j < prefix_map->max_length && e->name[j]; j++) {
> > +			if (e->item->name[j] != e2->item->name[j])
> > +				break;
>
> Same comment here about walking off of the defined end of both arrays.

Actually, no, not here, as I already test for `e->name[j]` in the loop
condition. If we reach the end of `e2->item->name`, the inner condition
will break out of the loop.

> I'm going to stop here.  I'm getting confused.

Oh no ;-)

Thank you for your helpful comments!

Ciao,
Dscho

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

* [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help  so far)
  2019-04-10 17:37 [PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                   ` (10 preceding siblings ...)
  2019-04-10 17:37 ` [PATCH 11/11] built-in add -i: implement the `help` command Johannes Schindelin via GitGitGadget
@ 2019-05-13 17:27 ` Johannes Schindelin via GitGitGadget
  2019-05-13 17:27   ` [PATCH v2 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
                     ` (11 more replies)
  11 siblings, 12 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-05-13 17:27 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano

This is the first leg on the long journey to a fully built-in git add -i 
(next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3
[https://github.com/gitgitgadget/git/pull/172], 4
[https://github.com/gitgitgadget/git/pull/173], 5
[https://github.com/gitgitgadget/git/pull/174], and 6
[https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are
not necessarily up to date, and will be re-targeted to the appropriate
branches in https://github.com/gitster/git as soon as Junio picks them up.

This here patch series reflects the part that was submitted a couple of
times (see https://github.com/gitgitgadget/git/pull/103) during the
Outreachy project by Slavica Ðukic that continued the journey based on an
initial patch series by Daniel Ferreira.

It only implements the status and the help part, in the interest of making
the review remotely more reviewable.

As I am a heavy user of git add -p myself and use a patched version for
weeks already (it is so nice to not suffer over one second startup until the
MSYS2 Perl finally shows me anything, instead it feels instantaneous), I
integrated these patch series into Git for Windows' master already, as an
opt-in feature guarded by the config variable add.interactive.useBuiltin 
(and Git for Windows' installer is prepared to detect this version and offer
the option in the graphical user interface).

I had planned on submitting this before v2.22.0-rc0, but there was such a
backlog of builds from a big pushout that I had to wait ;-)

Changes since v1:

 * The config machinery was reworked completely, to not use a callback to 
   git_config(), but instead to query the config via the repo_config_get_*() 
   functions. This also prevents a future "Huh???" moment: the internal add
   --interactive API accepts a parameter of type struct repository *r, but
   the previous configuration did not use that to query the config (and
   could in the future be a repository other than the_repository).
   
   
 * As a consequence, the color sequences are no longer stored in file-local
   variables, but passed around via a struct.
   
   
 * Instead of using the magical constant -2 to quit the main loop, it is now
   defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is
   defined as -1 and used where appropriate).
   
   
 * Improved the add_prefix_item() function by avoiding buffer overruns, not
   reusing the struct that is used for lookup also for adding the new item,
   and by strengthening the bug check.

Daniel Ferreira (2):
  diff: export diffstat interface
  built-in add -i: implement the `status` command

Johannes Schindelin (6):
  Start to implement a built-in version of `git add --interactive`
  built-in add -i: refresh the index before running `status`
  built-in add -i: color the header in the `status` command
  built-in add -i: implement the main loop
  built-in add -i: support `?` (prompt help)
  built-in add -i: implement the `help` command

Slavica Djukic (3):
  Add a function to determine unique prefixes for a list of strings
  built-in add -i: show unique prefixes of the commands
  built-in add -i: use color in the main loop

 Documentation/config/add.txt |   5 +
 Makefile                     |   3 +
 add-interactive.c            | 558 +++++++++++++++++++++++++++++++++++
 add-interactive.h            |   8 +
 builtin/add.c                |  10 +
 diff.c                       |  37 +--
 diff.h                       |  19 ++
 prefix-map.c                 | 109 +++++++
 prefix-map.h                 |  40 +++
 repository.c                 |  19 ++
 repository.h                 |   7 +
 t/README                     |   4 +
 t/helper/test-prefix-map.c   |  58 ++++
 t/helper/test-tool.c         |   1 +
 t/helper/test-tool.h         |   1 +
 t/t0016-prefix-map.sh        |  10 +
 t/t3701-add-interactive.sh   |  24 ++
 17 files changed, 891 insertions(+), 22 deletions(-)
 create mode 100644 add-interactive.c
 create mode 100644 add-interactive.h
 create mode 100644 prefix-map.c
 create mode 100644 prefix-map.h
 create mode 100644 t/helper/test-prefix-map.c
 create mode 100755 t/t0016-prefix-map.sh


base-commit: 8104ec994ea3849a968b4667d072fedd1e688642
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/170

Range-diff vs v1:

  1:  12978dc248 !  1:  ed53346b92 Start to implement a built-in version of `git add --interactive`
     @@ -62,12 +62,6 @@
      @@
      +#include "cache.h"
      +#include "add-interactive.h"
     -+#include "config.h"
     -+
     -+int add_i_config(const char *var, const char *value, void *cb)
     -+{
     -+	return git_default_config(var, value, cb);
     -+}
      +
      +int run_add_i(struct repository *r, const struct pathspec *ps)
      +{
     @@ -82,8 +76,6 @@
      +#ifndef ADD_INTERACTIVE_H
      +#define ADD_INTERACTIVE_H
      +
     -+int add_i_config(const char *var, const char *value, void *cb);
     -+
      +struct repository;
      +struct pathspec;
      +int run_add_i(struct repository *r, const struct pathspec *ps);
     @@ -102,50 +94,28 @@
       static const char * const builtin_add_usage[] = {
       	N_("git add [<options>] [--] <pathspec>..."),
      @@
     - static int patch_interactive, add_interactive, edit_interactive;
     - static int take_worktree_changes;
     - static int add_renormalize;
     -+static int use_builtin_add_i;
     - 
     - struct update_callback_data {
     - 	int flags;
     -@@
     + {
       	int status, i;
       	struct argv_array argv = ARGV_ARRAY_INIT;
     - 
     -+	if (use_builtin_add_i && !patch_mode)
     -+		return !!run_add_i(the_repository, pathspec);
     ++	int use_builtin_add_i =
     ++		git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
     ++	if (use_builtin_add_i < 0)
     ++		git_config_get_bool("add.interactive.usebuiltin",
     ++				    &use_builtin_add_i);
      +
     ++	if (use_builtin_add_i == 1 && !patch_mode)
     ++		return !!run_add_i(the_repository, pathspec);
     + 
       	argv_array_push(&argv, "add--interactive");
       	if (patch_mode)
     - 		argv_array_push(&argv, patch_mode);
      @@
       		ignore_add_errors = git_config_bool(var, value);
       		return 0;
       	}
     --	return git_default_config(var, value, cb);
     -+	if (!strcmp(var, "add.interactive.usebuiltin")) {
     -+		use_builtin_add_i = git_config_bool(var, value);
     -+		return 0;
     -+	}
      +
     -+	return add_i_config(var, value, cb);
     + 	return git_default_config(var, value, cb);
       }
       
     - static const char embedded_advice[] = N_(
     -@@
     - 	int require_pathspec;
     - 	char *seen = NULL;
     - 	struct lock_file lock_file = LOCK_INIT;
     -+	int use_builtin_add_i_env =
     -+		git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
     - 
     - 	git_config(add_config, NULL);
     -+	if (use_builtin_add_i_env >= 0)
     -+		use_builtin_add_i = use_builtin_add_i_env;
     - 
     - 	argc = parse_options(argc, argv, prefix, builtin_add_options,
     - 			  builtin_add_usage, PARSE_OPT_KEEP_ARGV0);
      
       diff --git a/t/README b/t/README
       --- a/t/README
  2:  06ba1ae344 =  2:  bc99009fbf diff: export diffstat interface
  3:  be9ab904d1 !  3:  5e23c0756b built-in add -i: implement the `status` command
     @@ -30,16 +30,10 @@
      @@
       #include "cache.h"
       #include "add-interactive.h"
     - #include "config.h"
      +#include "diffcore.h"
      +#include "revision.h"
      +#include "refs.h"
     - 
     - int add_i_config(const char *var, const char *value, void *cb)
     - {
     - 	return git_default_config(var, value, cb);
     - }
     - 
     ++
      +struct item {
      +	const char *name;
      +};
     @@ -276,7 +270,7 @@
      +
      +	return 0;
      +}
     -+
     + 
       int run_add_i(struct repository *r, const struct pathspec *ps)
       {
      -	die(_("No commands are available in the built-in `git add -i` yet!"));
  4:  a512e14609 =  4:  8cafc6ae8d built-in add -i: refresh the index before running `status`
  5:  f7c8df058c <  -:  ---------- built-in add -i: color the header in the `status` command
  -:  ---------- >  5:  83d92a9762 built-in add -i: color the header in the `status` command
  6:  93b3151b6c !  6:  3eec219124 built-in add -i: implement the main loop
     @@ -53,8 +53,8 @@
       	void (*print_item)(int i, struct item *item, void *print_item_data);
       	void *print_item_data;
      @@
     - 
     - static void list(struct item **list, size_t nr, struct list_options *opts)
     + static void list(struct item **list, size_t nr,
     + 		 struct add_i_state *s, struct list_options *opts)
       {
      -	int i;
      +	int i, last_lf = 0;
     @@ -85,28 +85,35 @@
      +	const char *prompt;
      +};
      +
     ++#define LIST_AND_CHOOSE_ERROR (-1)
     ++#define LIST_AND_CHOOSE_QUIT  (-2)
     ++
      +/*
      + * Returns the selected index.
     ++ *
     ++ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
     ++ * `LIST_AND_CHOOSE_QUIT` is returned.
      + */
      +static ssize_t list_and_choose(struct item **items, size_t nr,
     ++			       struct add_i_state *s,
      +			       struct list_and_choose_options *opts)
      +{
      +	struct strbuf input = STRBUF_INIT;
     -+	ssize_t res = -1;
     ++	ssize_t res = LIST_AND_CHOOSE_ERROR;
      +
      +	for (;;) {
      +		char *p, *endp;
      +
      +		strbuf_reset(&input);
      +
     -+		list(items, nr, &opts->list_opts);
     ++		list(items, nr, s, &opts->list_opts);
      +
      +		printf("%s%s", opts->prompt, "> ");
      +		fflush(stdout);
      +
      +		if (strbuf_getline(&input, stdin) == EOF) {
      +			putchar('\n');
     -+			res = -2;
     ++			res = LIST_AND_CHOOSE_QUIT;
      +			break;
      +		}
      +		strbuf_trim(&input);
     @@ -143,7 +150,7 @@
      +			p += sep + 1;
      +		}
      +
     -+		if (res >= 0)
     ++		if (res != LIST_AND_CHOOSE_ERROR)
      +			break;
       	}
      +
     @@ -164,12 +171,13 @@
      +
      +struct command_item {
      +	struct item item;
     -+	int (*command)(struct repository *r, const struct pathspec *ps,
     ++	int (*command)(struct add_i_state *s, const struct pathspec *ps,
      +		       struct file_list *files, struct list_options *opts);
      +};
      +
       int run_add_i(struct repository *r, const struct pathspec *ps)
       {
     + 	struct add_i_state s = { NULL };
      +	struct list_and_choose_options main_loop_opts = {
      +		{ 4, N_("*** Commands ***"), print_command_item, NULL },
      +		N_("What now")
     @@ -192,21 +200,21 @@
      +	ssize_t i;
       	int res = 0;
       
     - 	strbuf_addstr(&header, "      ");
     + 	if (init_add_i_state(r, &s))
      @@
     - 	if (run_status(r, ps, &files, &opts) < 0)
     + 	if (run_status(&s, ps, &files, &opts) < 0)
       		res = -1;
       
      +	for (;;) {
      +		i = list_and_choose((struct item **)commands,
     -+				    ARRAY_SIZE(commands), &main_loop_opts);
     -+		if (i < -1) {
     ++				    ARRAY_SIZE(commands), &s, &main_loop_opts);
     ++		if (i == LIST_AND_CHOOSE_QUIT) {
      +			printf(_("Bye.\n"));
      +			res = 0;
      +			break;
      +		}
     -+		if (i >= 0)
     -+			res = commands[i]->command(r, ps, &files, &opts);
     ++		if (i != LIST_AND_CHOOSE_ERROR)
     ++			res = commands[i]->command(&s, ps, &files, &opts);
      +	}
      +
       	release_file_list(&files);
  7:  db1ede3636 !  7:  e02a52c3ac Add a function to determine unique prefixes for a list of strings
     @@ -92,40 +92,40 @@
      +static void add_prefix_item(struct prefix_map *prefix_map,
      +			    struct prefix_item *item)
      +{
     -+	struct prefix_map_entry *e = xmalloc(sizeof(*e)), *e2;
     ++	struct prefix_map_entry e = { { NULL } }, *e2;
      +	int j;
      +
     -+	e->item = item;
     -+	e->name = e->item->name;
     ++	e.item = item;
     ++	e.name = item->name;
      +
     -+	for (j = prefix_map->min_length; j <= prefix_map->max_length; j++) {
     -+		if (!isascii(e->name[j])) {
     -+			free(e);
     ++	for (j = prefix_map->min_length;
     ++	     j <= prefix_map->max_length && e.name[j]; j++) {
     ++		/* Avoid breaking UTF-8 multi-byte sequences */
     ++		if (!isascii(e.name[j]))
      +			break;
     -+		}
      +
     -+		e->prefix_length = j;
     -+		hashmap_entry_init(e, memhash(e->name, j));
     -+		e2 = hashmap_get(&prefix_map->map, e, NULL);
     ++		e.prefix_length = j;
     ++		hashmap_entry_init(&e, memhash(e.name, j));
     ++		e2 = hashmap_get(&prefix_map->map, &e, NULL);
      +		if (!e2) {
     -+			/* prefix is unique so far */
     -+			e->item->prefix_length = j;
     -+			hashmap_add(&prefix_map->map, e);
     ++			/* prefix is unique at this stage */
     ++			item->prefix_length = j;
     ++			add_prefix_entry(&prefix_map->map, e.name, j, item);
      +			break;
      +		}
      +
      +		if (!e2->item)
      +			continue; /* non-unique prefix */
      +
     -+		if (j != e2->item->prefix_length)
     -+			BUG("unexpected prefix length: %d != %d",
     -+			    (int)j, (int)e2->item->prefix_length);
     ++		if (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))
     ++			BUG("unexpected prefix length: %d != %d (%s != %s)",
     ++			    j, (int)e2->item->prefix_length, e.name, e2->name);
      +
      +		/* skip common prefix */
     -+		for (; j < prefix_map->max_length && e->name[j]; j++) {
     -+			if (e->item->name[j] != e2->item->name[j])
     ++		for (; j < prefix_map->max_length && e.name[j]; j++) {
     ++			if (e.item->name[j] != e2->item->name[j])
      +				break;
     -+			add_prefix_entry(&prefix_map->map, e->name, j + 1,
     ++			add_prefix_entry(&prefix_map->map, e.name, j + 1,
      +					 NULL);
      +		}
      +
     @@ -140,16 +140,14 @@
      +			e2->item->prefix_length = 0;
      +		e2->item = NULL;
      +
     -+		if (j < prefix_map->max_length && e->name[j]) {
     ++		if (j < prefix_map->max_length && e.name[j]) {
      +			/* found a unique prefix for the item */
     -+			e->item->prefix_length = j + 1;
     -+			add_prefix_entry(&prefix_map->map, e->name, j + 1,
     -+					 e->item);
     -+		} else {
     ++			e.item->prefix_length = j + 1;
     ++			add_prefix_entry(&prefix_map->map, e.name, j + 1,
     ++					 e.item);
     ++		} else
      +			/* item has no (short enough) unique prefix */
     -+			e->item->prefix_length = 0;
     -+			free(e);
     -+		}
     ++			e.item->prefix_length = 0;
      +
      +		break;
      +	}
  8:  56acc31e96 !  8:  ced9b6aced built-in add -i: show unique prefixes of the commands
     @@ -22,10 +22,10 @@
       #include "refs.h"
      +#include "prefix-map.h"
       
     - static int use_color = -1;
     - 
     + struct add_i_state {
     + 	struct repository *r;
      @@
     - 	return git_color_default_config(var, value, cb);
     + 	return 0;
       }
       
      -struct item {
     @@ -57,22 +57,22 @@
       	void *print_item_data;
       };
       
     --static void list(struct item **list, size_t nr, struct list_options *opts)
     +-static void list(struct item **list, size_t nr,
      +static void list(struct prefix_item **list, size_t nr,
     -+		 struct list_options *opts)
     + 		 struct add_i_state *s, struct list_options *opts)
       {
       	int i, last_lf = 0;
     - 
      @@
     - /*
     -  * Returns the selected index.
     +  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
     +  * `LIST_AND_CHOOSE_QUIT` is returned.
        */
      -static ssize_t list_and_choose(struct item **items, size_t nr,
      +static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
     + 			       struct add_i_state *s,
       			       struct list_and_choose_options *opts)
       {
       	struct strbuf input = STRBUF_INIT;
     - 	ssize_t res = -1;
     + 	ssize_t res = LIST_AND_CHOOSE_ERROR;
       
      +	find_unique_prefixes(items, nr, 1, 4);
      +
     @@ -133,8 +133,8 @@
       		return -1;
       
       	if (files->nr)
     --		list((struct item **)files->file, files->nr, opts);
     -+		list((struct prefix_item **)files->file, files->nr, opts);
     +-		list((struct item **)files->file, files->nr, s, opts);
     ++		list((struct prefix_item **)files->file, files->nr, s, opts);
       	putchar('\n');
       
       	return 0;
     @@ -157,7 +157,7 @@
       struct command_item {
      -	struct item item;
      +	struct prefix_item item;
     - 	int (*command)(struct repository *r, const struct pathspec *ps,
     + 	int (*command)(struct add_i_state *s, const struct pathspec *ps,
       		       struct file_list *files, struct list_options *opts);
       };
      @@
     @@ -166,6 +166,6 @@
       	for (;;) {
      -		i = list_and_choose((struct item **)commands,
      +		i = list_and_choose((struct prefix_item **)commands,
     - 				    ARRAY_SIZE(commands), &main_loop_opts);
     - 		if (i < -1) {
     + 				    ARRAY_SIZE(commands), &s, &main_loop_opts);
     + 		if (i == LIST_AND_CHOOSE_QUIT) {
       			printf(_("Bye.\n"));
  9:  ddd0ee51f6 !  9:  7378af60ad built-in add -i: support `?` (prompt help)
     @@ -12,41 +12,35 @@
       --- a/add-interactive.c
       +++ b/add-interactive.c
      @@
     - 
     - enum color_add_i {
     - 	COLOR_HEADER = 0,
     -+	COLOR_HELP,
     - };
     - 
     - static char list_colors[][COLOR_MAXLEN] = {
     - 	GIT_COLOR_BOLD,      /* Header */
     -+	GIT_COLOR_BOLD_RED,  /* Help */
     + 	struct repository *r;
     + 	int use_color;
     + 	char header_color[COLOR_MAXLEN];
     ++	char help_color[COLOR_MAXLEN];
       };
       
     - static const char *get_add_i_color(enum color_add_i ix)
     + static void init_color(struct repository *r, struct add_i_state *s,
      @@
     - {
     - 	if (!strcasecmp(slot, "header"))
     - 		return COLOR_HEADER;
     -+	if (!strcasecmp(slot, "help"))
     -+		return COLOR_HELP;
     + 	s->use_color = want_color(s->use_color);
     + 
     + 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
     ++	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
       
     - 	return -1;
     + 	return 0;
       }
      @@
       	struct list_options list_opts;
       
       	const char *prompt;
     -+	void (*print_help)(void);
     ++	void (*print_help)(struct add_i_state *s);
       };
       
     - /*
     + #define LIST_AND_CHOOSE_ERROR (-1)
      @@
       		if (!input.len)
       			break;
       
      +		if (!strcmp(input.buf, "?")) {
     -+			opts->print_help();
     ++			opts->print_help(s);
      +			continue;
      +		}
      +
     @@ -57,9 +51,9 @@
       		       struct file_list *files, struct list_options *opts);
       };
       
     -+static void command_prompt_help(void)
     ++static void command_prompt_help(struct add_i_state *s)
      +{
     -+	const char *help_color = get_add_i_color(COLOR_HELP);
     ++	const char *help_color = s->help_color;
      +	color_fprintf_ln(stdout, help_color, "%s", _("Prompt help:"));
      +	color_fprintf_ln(stdout, help_color, "1          - %s",
      +			 _("select a numbered item"));
     @@ -71,6 +65,7 @@
      +
       int run_add_i(struct repository *r, const struct pathspec *ps)
       {
     + 	struct add_i_state s = { NULL };
       	struct list_and_choose_options main_loop_opts = {
       		{ 4, N_("*** Commands ***"), print_command_item, NULL },
      -		N_("What now")
 10:  3e1a88d9c0 ! 10:  36edef85e6 built-in add -i: use color in the main loop
     @@ -12,51 +12,31 @@
       --- a/add-interactive.c
       +++ b/add-interactive.c
      @@
     - enum color_add_i {
     - 	COLOR_HEADER = 0,
     - 	COLOR_HELP,
     -+	COLOR_PROMPT,
     -+	COLOR_ERROR,
     -+	COLOR_RESET,
     + 	int use_color;
     + 	char header_color[COLOR_MAXLEN];
     + 	char help_color[COLOR_MAXLEN];
     ++	char prompt_color[COLOR_MAXLEN];
     ++	char error_color[COLOR_MAXLEN];
     ++	char reset_color[COLOR_MAXLEN];
       };
       
     - static char list_colors[][COLOR_MAXLEN] = {
     - 	GIT_COLOR_BOLD,      /* Header */
     - 	GIT_COLOR_BOLD_RED,  /* Help */
     -+	GIT_COLOR_BOLD_BLUE, /* Prompt */
     -+	GIT_COLOR_BOLD_RED,  /* Error */
     -+	GIT_COLOR_RESET,     /* Reset */
     - };
     - 
     - static const char *get_add_i_color(enum color_add_i ix)
     + static void init_color(struct repository *r, struct add_i_state *s,
      @@
     - 		return COLOR_HEADER;
     - 	if (!strcasecmp(slot, "help"))
     - 		return COLOR_HELP;
     -+	if (!strcasecmp(slot, "prompt"))
     -+		return COLOR_PROMPT;
     -+	if (!strcasecmp(slot, "error"))
     -+		return COLOR_ERROR;
     -+	if (!strcasecmp(slot, "reset"))
     -+		return COLOR_RESET;
       
     - 	return -1;
     - }
     -@@
     - static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
     - 			       struct list_and_choose_options *opts)
     - {
     -+	const char *prompt_color = get_add_i_color(COLOR_PROMPT);
     -+	const char *error_color = get_add_i_color(COLOR_ERROR);
     - 	struct strbuf input = STRBUF_INIT;
     - 	ssize_t res = -1;
     + 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
     + 	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
     ++	init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE);
     ++	init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED);
     ++	init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET);
       
     + 	return 0;
     + }
      @@
       
     - 		list(items, nr, &opts->list_opts);
     + 		list(items, nr, s, &opts->list_opts);
       
      -		printf("%s%s", opts->prompt, "> ");
     -+		color_fprintf(stdout, prompt_color, "%s", opts->prompt);
     ++		color_fprintf(stdout, s->prompt_color, "%s", opts->prompt);
      +		fputs("> ", stdout);
       		fflush(stdout);
       
     @@ -66,7 +46,7 @@
       
       			if (index < 0 || index >= nr)
      -				printf(_("Huh (%s)?\n"), p);
     -+				color_fprintf_ln(stdout, error_color,
     ++				color_fprintf_ln(stdout, s->error_color,
      +						 _("Huh (%s)?"), p);
       			else {
       				res = index;
     @@ -96,20 +76,32 @@
       }
       
      @@
     - 
       int run_add_i(struct repository *r, const struct pathspec *ps)
       {
     -+	struct print_command_item_data data = {
     -+		/*
     -+		 * When color was asked for, use the prompt color for
     -+		 * highlighting, otherwise use square brackets.
     -+		 */
     -+		want_color(use_color) ? get_add_i_color(COLOR_PROMPT) : "[",
     -+		want_color(use_color) ? get_add_i_color(COLOR_RESET) : "]"
     -+	};
     + 	struct add_i_state s = { NULL };
     ++	struct print_command_item_data data;
       	struct list_and_choose_options main_loop_opts = {
      -		{ 4, N_("*** Commands ***"), print_command_item, NULL },
      +		{ 4, N_("*** Commands ***"), print_command_item, &data },
       		N_("What now"), command_prompt_help
       	};
       	struct command_item
     +@@
     + 	if (init_add_i_state(r, &s))
     + 		return error("could not parse `add -i` config");
     + 
     ++	/*
     ++	 * When color was asked for, use the prompt color for
     ++	 * highlighting, otherwise use square brackets.
     ++	 */
     ++	if (s.use_color) {
     ++		data.color = s.prompt_color;
     ++		data.reset = s.reset_color;
     ++	} else {
     ++		data.color = "[";
     ++		data.reset = "]";
     ++	}
     ++
     + 	strbuf_addstr(&header, "      ");
     + 	strbuf_addf(&header, print_file_item_data.modified_fmt,
     + 		    _("staged"), _("unstaged"), _("path"));
 11:  481e3316d5 ! 11:  266dbf2a6b built-in add -i: implement the `help` command
     @@ -18,10 +18,10 @@
       	return 0;
       }
       
     -+static int run_help(struct repository *r, const struct pathspec *ps,
     ++static int run_help(struct add_i_state *s, const struct pathspec *ps,
      +		    struct file_list *files, struct list_options *opts)
      +{
     -+	const char *help_color = get_add_i_color(COLOR_HELP);
     ++	const char *help_color = s->help_color;
      +
      +	color_fprintf_ln(stdout, help_color, "status        - %s",
      +			 _("show paths with changes"));

-- 
gitgitgadget

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

* [PATCH v2 01/11] Start to implement a built-in version of `git add --interactive`
  2019-05-13 17:27 ` [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
@ 2019-05-13 17:27   ` Johannes Schindelin via GitGitGadget
  2019-05-13 17:27   ` [PATCH v2 02/11] diff: export diffstat interface Daniel Ferreira via GitGitGadget
                     ` (10 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-05-13 17:27 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

This is hardly the first conversion of a Git command that is implemented
as a script to a built-in. So far, the most successful strategy for such
conversions has been to add a built-in helper and call that for more and
more functionality from the script, as more and more parts are
converted.

With the interactive add, we choose a different strategy. The sole
reason for this is that on Windows (where such a conversion has the most
benefits in terms of speed and robustness) we face the very specific
problem that a `system()` call in Perl seems to close `stdin` in the
parent process when the spawned process consumes even one character from
`stdin`. And that just does not work for us here, as it would stop the
main loop as soon as any interactive command was performed by the
helper. Which is almost all of the commands in `git add -i`.

It is almost as if Perl told us once again that it does not want us to
use it on Windows.

Instead, we follow the opposite route where we start with a bare-bones
version of the built-in interactive add, guarded by the new
`add.interactive.useBuiltin` config variable, and then add more and more
functionality to it, until it is feature complete.

At this point, the built-in version of `git add -i` only states that it
cannot do anything yet ;-)

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/config/add.txt |  5 +++++
 Makefile                     |  1 +
 add-interactive.c            |  7 +++++++
 add-interactive.h            |  8 ++++++++
 builtin/add.c                | 10 ++++++++++
 t/README                     |  4 ++++
 6 files changed, 35 insertions(+)
 create mode 100644 add-interactive.c
 create mode 100644 add-interactive.h

diff --git a/Documentation/config/add.txt b/Documentation/config/add.txt
index 4d753f006e..c9f748f81c 100644
--- a/Documentation/config/add.txt
+++ b/Documentation/config/add.txt
@@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::
 	option of linkgit:git-add[1].  `add.ignore-errors` is deprecated,
 	as it does not follow the usual naming convention for configuration
 	variables.
+
+add.interactive.useBuiltin::
+	[EXPERIMENTAL] Set to `true` to use the experimental built-in
+	implementation of the interactive version of linkgit:git-add[1]
+	instead of the Perl script version. Is `false` by default.
diff --git a/Makefile b/Makefile
index c5240942f2..18e656a32f 100644
--- a/Makefile
+++ b/Makefile
@@ -848,6 +848,7 @@ LIB_H = $(shell $(FIND) . \
 	-name '*.h' -print)
 
 LIB_OBJS += abspath.o
+LIB_OBJS += add-interactive.o
 LIB_OBJS += advice.o
 LIB_OBJS += alias.o
 LIB_OBJS += alloc.o
diff --git a/add-interactive.c b/add-interactive.c
new file mode 100644
index 0000000000..482e458dc6
--- /dev/null
+++ b/add-interactive.c
@@ -0,0 +1,7 @@
+#include "cache.h"
+#include "add-interactive.h"
+
+int run_add_i(struct repository *r, const struct pathspec *ps)
+{
+	die(_("No commands are available in the built-in `git add -i` yet!"));
+}
diff --git a/add-interactive.h b/add-interactive.h
new file mode 100644
index 0000000000..7043b8741d
--- /dev/null
+++ b/add-interactive.h
@@ -0,0 +1,8 @@
+#ifndef ADD_INTERACTIVE_H
+#define ADD_INTERACTIVE_H
+
+struct repository;
+struct pathspec;
+int run_add_i(struct repository *r, const struct pathspec *ps);
+
+#endif
diff --git a/builtin/add.c b/builtin/add.c
index db2dfa4350..e19d66e894 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -20,6 +20,7 @@
 #include "bulk-checkin.h"
 #include "argv-array.h"
 #include "submodule.h"
+#include "add-interactive.h"
 
 static const char * const builtin_add_usage[] = {
 	N_("git add [<options>] [--] <pathspec>..."),
@@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,
 {
 	int status, i;
 	struct argv_array argv = ARGV_ARRAY_INIT;
+	int use_builtin_add_i =
+		git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
+	if (use_builtin_add_i < 0)
+		git_config_get_bool("add.interactive.usebuiltin",
+				    &use_builtin_add_i);
+
+	if (use_builtin_add_i == 1 && !patch_mode)
+		return !!run_add_i(the_repository, pathspec);
 
 	argv_array_push(&argv, "add--interactive");
 	if (patch_mode)
@@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb)
 		ignore_add_errors = git_config_bool(var, value);
 		return 0;
 	}
+
 	return git_default_config(var, value, cb);
 }
 
diff --git a/t/README b/t/README
index 886bbec5bc..6408a1847e 100644
--- a/t/README
+++ b/t/README
@@ -383,6 +383,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_ADD_I_USE_BUILTIN=<boolean>, when true, enables the
+builtin version of git add -i. See 'add.interactive.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
-- 
gitgitgadget


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

* [PATCH v2 02/11] diff: export diffstat interface
  2019-05-13 17:27 ` [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  2019-05-13 17:27   ` [PATCH v2 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
@ 2019-05-13 17:27   ` Daniel Ferreira via GitGitGadget
  2019-05-13 17:27   ` [PATCH v2 03/11] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
                     ` (9 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Daniel Ferreira via GitGitGadget @ 2019-05-13 17:27 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Daniel Ferreira

From: Daniel Ferreira <bnmvco@gmail.com>

Make the diffstat interface (namely, the diffstat_t struct and
compute_diffstat) no longer be internal to diff.c and allow it to be used
by other parts of git.

This is helpful for code that may want to easily extract information
from files using the diff machinery, while flushing it differently from
how the show_* functions used by diff_flush() do it. One example is the
builtin implementation of git-add--interactive's status.

Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 37 +++++++++++++++----------------------
 diff.h | 19 +++++++++++++++++++
 2 files changed, 34 insertions(+), 22 deletions(-)

diff --git a/diff.c b/diff.c
index 5306c48652..daa5f3a736 100644
--- a/diff.c
+++ b/diff.c
@@ -2489,22 +2489,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)
 	}
 }
 
-struct diffstat_t {
-	int nr;
-	int alloc;
-	struct diffstat_file {
-		char *from_name;
-		char *name;
-		char *print_name;
-		const char *comments;
-		unsigned is_unmerged:1;
-		unsigned is_binary:1;
-		unsigned is_renamed:1;
-		unsigned is_interesting:1;
-		uintmax_t added, deleted;
-	} **files;
-};
-
 static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
 					  const char *name_a,
 					  const char *name_b)
@@ -6001,12 +5985,7 @@ void diff_flush(struct diff_options *options)
 	    dirstat_by_line) {
 		struct diffstat_t diffstat;
 
-		memset(&diffstat, 0, sizeof(struct diffstat_t));
-		for (i = 0; i < q->nr; i++) {
-			struct diff_filepair *p = q->queue[i];
-			if (check_pair_status(p))
-				diff_flush_stat(p, options, &diffstat);
-		}
+		compute_diffstat(options, &diffstat, q);
 		if (output_format & DIFF_FORMAT_NUMSTAT)
 			show_numstat(&diffstat, options);
 		if (output_format & DIFF_FORMAT_DIFFSTAT)
@@ -6306,6 +6285,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)
 	return ignored;
 }
 
+void compute_diffstat(struct diff_options *options,
+		      struct diffstat_t *diffstat,
+		      struct diff_queue_struct *q)
+{
+	int i;
+
+	memset(diffstat, 0, sizeof(struct diffstat_t));
+	for (i = 0; i < q->nr; i++) {
+		struct diff_filepair *p = q->queue[i];
+		if (check_pair_status(p))
+			diff_flush_stat(p, options, diffstat);
+	}
+}
+
 void diff_addremove(struct diff_options *options,
 		    int addremove, unsigned mode,
 		    const struct object_id *oid,
diff --git a/diff.h b/diff.h
index b512d0477a..ae9bedfab8 100644
--- a/diff.h
+++ b/diff.h
@@ -240,6 +240,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);
 void diff_emit_submodule_pipethrough(struct diff_options *o,
 				     const char *line, int len);
 
+struct diffstat_t {
+	int nr;
+	int alloc;
+	struct diffstat_file {
+		char *from_name;
+		char *name;
+		char *print_name;
+		const char *comments;
+		unsigned is_unmerged:1;
+		unsigned is_binary:1;
+		unsigned is_renamed:1;
+		unsigned is_interesting:1;
+		uintmax_t added, deleted;
+	} **files;
+};
+
 enum color_diff {
 	DIFF_RESET = 0,
 	DIFF_CONTEXT = 1,
@@ -328,6 +344,9 @@ void diff_change(struct diff_options *,
 
 struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);
 
+void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
+		      struct diff_queue_struct *q);
+
 #define DIFF_SETUP_REVERSE      	1
 #define DIFF_SETUP_USE_SIZE_CACHE	4
 
-- 
gitgitgadget


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

* [PATCH v2 03/11] built-in add -i: implement the `status` command
  2019-05-13 17:27 ` [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  2019-05-13 17:27   ` [PATCH v2 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
  2019-05-13 17:27   ` [PATCH v2 02/11] diff: export diffstat interface Daniel Ferreira via GitGitGadget
@ 2019-05-13 17:27   ` Daniel Ferreira via GitGitGadget
  2019-05-13 17:27   ` [PATCH v2 04/11] built-in add -i: refresh the index before running `status` Johannes Schindelin via GitGitGadget
                     ` (8 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Daniel Ferreira via GitGitGadget @ 2019-05-13 17:27 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Daniel Ferreira

From: Daniel Ferreira <bnmvco@gmail.com>

This implements the `status` command of `git add -i`. The data
structures introduced in this commit will be extended as needed later.

At this point, we re-implement only part of the `list_and_choose()`
function of the Perl script `git-add--interactive.perl` and call it
`list()`. It does not yet color anything, or do columns, or allow user
input.

Over the course of the next commits, we will introduce a
`list_and_choose()` function that uses `list()` to display the list of
options and let the user choose one or more of the displayed items. This
will be used to implement the main loop of the built-in `git add -i`, at
which point the new `status` command can actually be used.

Note that we pass the list of items as a `struct item **` as opposed to
a `struct item *`, to allow for the actual items to contain much more
information than merely the name.

Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 265 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 264 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index 482e458dc6..59b28011f7 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -1,7 +1,270 @@
 #include "cache.h"
 #include "add-interactive.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "refs.h"
+
+struct item {
+	const char *name;
+};
+
+struct list_options {
+	const char *header;
+	void (*print_item)(int i, struct item *item, void *print_item_data);
+	void *print_item_data;
+};
+
+static void list(struct item **list, size_t nr, struct list_options *opts)
+{
+	int i;
+
+	if (!nr)
+		return;
+
+	if (opts->header)
+		printf("%s\n", opts->header);
+
+	for (i = 0; i < nr; i++) {
+		opts->print_item(i, list[i], opts->print_item_data);
+		putchar('\n');
+	}
+}
+
+struct adddel {
+	uintmax_t add, del;
+	unsigned seen:1, binary:1;
+};
+
+struct file_list {
+	struct file_item {
+		struct item item;
+		struct adddel index, worktree;
+	} **file;
+	size_t nr, alloc;
+};
+
+static void add_file_item(struct file_list *list, const char *name)
+{
+	struct file_item *item;
+
+	FLEXPTR_ALLOC_STR(item, item.name, name);
+
+	ALLOC_GROW(list->file, list->nr + 1, list->alloc);
+	list->file[list->nr++] = item;
+}
+
+static void reset_file_list(struct file_list *list)
+{
+	size_t i;
+
+	for (i = 0; i < list->nr; i++)
+		free(list->file[i]);
+	list->nr = 0;
+}
+
+static void release_file_list(struct file_list *list)
+{
+	reset_file_list(list);
+	FREE_AND_NULL(list->file);
+	list->alloc = 0;
+}
+
+static int file_item_cmp(const void *a, const void *b)
+{
+	const struct file_item * const *f1 = a;
+	const struct file_item * const *f2 = b;
+
+	return strcmp((*f1)->item.name, (*f2)->item.name);
+}
+
+struct pathname_entry {
+	struct hashmap_entry ent;
+	size_t index;
+	char pathname[FLEX_ARRAY];
+};
+
+static int pathname_entry_cmp(const void *unused_cmp_data,
+			      const void *entry, const void *entry_or_key,
+			      const void *pathname)
+{
+	const struct pathname_entry *e1 = entry, *e2 = entry_or_key;
+
+	return strcmp(e1->pathname,
+		      pathname ? (const char *)pathname : e2->pathname);
+}
+
+struct collection_status {
+	enum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase;
+
+	const char *reference;
+
+	struct file_list *list;
+	struct hashmap file_map;
+};
+
+static void collect_changes_cb(struct diff_queue_struct *q,
+			       struct diff_options *options,
+			       void *data)
+{
+	struct collection_status *s = data;
+	struct diffstat_t stat = { 0 };
+	int i;
+
+	if (!q->nr)
+		return;
+
+	compute_diffstat(options, &stat, q);
+
+	for (i = 0; i < stat.nr; i++) {
+		const char *name = stat.files[i]->name;
+		int hash = strhash(name);
+		struct pathname_entry *entry;
+		size_t file_index;
+		struct file_item *file;
+		struct adddel *adddel;
+
+		entry = hashmap_get_from_hash(&s->file_map, hash, name);
+		if (entry)
+			file_index = entry->index;
+		else {
+			FLEX_ALLOC_STR(entry, pathname, name);
+			hashmap_entry_init(entry, hash);
+			entry->index = file_index = s->list->nr;
+			hashmap_add(&s->file_map, entry);
+
+			add_file_item(s->list, name);
+		}
+		file = s->list->file[file_index];
+
+		adddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;
+		adddel->seen = 1;
+		adddel->add = stat.files[i]->added;
+		adddel->del = stat.files[i]->deleted;
+		if (stat.files[i]->is_binary)
+			adddel->binary = 1;
+	}
+}
+
+static int get_modified_files(struct repository *r, struct file_list *list,
+			      const struct pathspec *ps)
+{
+	struct object_id head_oid;
+	int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+					     &head_oid, NULL);
+	struct collection_status s = { FROM_WORKTREE };
+
+	if (repo_read_index_preload(r, ps, 0) < 0)
+		return error(_("could not read index"));
+
+	s.list = list;
+	hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);
+
+	for (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {
+		struct rev_info rev;
+		struct setup_revision_opt opt = { 0 };
+
+		opt.def = is_initial ?
+			empty_tree_oid_hex() : oid_to_hex(&head_oid);
+
+		init_revisions(&rev, NULL);
+		setup_revisions(0, NULL, &rev, &opt);
+
+		rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+		rev.diffopt.format_callback = collect_changes_cb;
+		rev.diffopt.format_callback_data = &s;
+
+		if (ps)
+			copy_pathspec(&rev.prune_data, ps);
+
+		if (s.phase == FROM_INDEX)
+			run_diff_index(&rev, 1);
+		else {
+			rev.diffopt.flags.ignore_dirty_submodules = 1;
+			run_diff_files(&rev, 0);
+		}
+	}
+	hashmap_free(&s.file_map, 1);
+
+	/* While the diffs are ordered already, we ran *two* diffs... */
+	QSORT(list->file, list->nr, file_item_cmp);
+
+	return 0;
+}
+
+static void populate_wi_changes(struct strbuf *buf,
+				struct adddel *ad, const char *no_changes)
+{
+	if (ad->binary)
+		strbuf_addstr(buf, _("binary"));
+	else if (ad->seen)
+		strbuf_addf(buf, "+%"PRIuMAX"/-%"PRIuMAX,
+			    (uintmax_t)ad->add, (uintmax_t)ad->del);
+	else
+		strbuf_addstr(buf, no_changes);
+}
+
+struct print_file_item_data {
+	const char *modified_fmt;
+	struct strbuf buf, index, worktree;
+};
+
+static void print_file_item(int i, struct item *item,
+			    void *print_file_item_data)
+{
+	struct file_item *c = (struct file_item *)item;
+	struct print_file_item_data *d = print_file_item_data;
+
+	strbuf_reset(&d->index);
+	strbuf_reset(&d->worktree);
+	strbuf_reset(&d->buf);
+
+	populate_wi_changes(&d->worktree, &c->worktree, _("nothing"));
+	populate_wi_changes(&d->index, &c->index, _("unchanged"));
+	strbuf_addf(&d->buf, d->modified_fmt,
+		    d->index.buf, d->worktree.buf, item->name);
+
+	printf(" %2d: %s", i + 1, d->buf.buf);
+}
+
+static int run_status(struct repository *r, const struct pathspec *ps,
+		      struct file_list *files, struct list_options *opts)
+{
+	reset_file_list(files);
+
+	if (get_modified_files(r, files, ps) < 0)
+		return -1;
+
+	if (files->nr)
+		list((struct item **)files->file, files->nr, opts);
+	putchar('\n');
+
+	return 0;
+}
 
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
-	die(_("No commands are available in the built-in `git add -i` yet!"));
+	struct print_file_item_data print_file_item_data = {
+		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+	};
+	struct list_options opts = {
+		NULL, print_file_item, &print_file_item_data
+	};
+	struct strbuf header = STRBUF_INIT;
+	struct file_list files = { NULL };
+	int res = 0;
+
+	strbuf_addstr(&header, "      ");
+	strbuf_addf(&header, print_file_item_data.modified_fmt,
+		    _("staged"), _("unstaged"), _("path"));
+	opts.header = header.buf;
+
+	res = run_status(r, ps, &files, &opts);
+
+	release_file_list(&files);
+	strbuf_release(&print_file_item_data.buf);
+	strbuf_release(&print_file_item_data.index);
+	strbuf_release(&print_file_item_data.worktree);
+	strbuf_release(&header);
+
+	return res;
 }
-- 
gitgitgadget


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

* [PATCH v2 04/11] built-in add -i: refresh the index before running `status`
  2019-05-13 17:27 ` [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                     ` (2 preceding siblings ...)
  2019-05-13 17:27   ` [PATCH v2 03/11] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
@ 2019-05-13 17:27   ` Johannes Schindelin via GitGitGadget
  2019-05-13 17:27   ` [PATCH v2 05/11] built-in add -i: color the header in the `status` command Johannes Schindelin via GitGitGadget
                     ` (7 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-05-13 17:27 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

This is what the Perl version does, and therefore it is what the
built-in version should do, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c |  4 +++-
 repository.c      | 19 +++++++++++++++++++
 repository.h      |  7 +++++++
 3 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index 59b28011f7..2dbf29dee2 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -258,7 +258,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 		    _("staged"), _("unstaged"), _("path"));
 	opts.header = header.buf;
 
-	res = run_status(r, ps, &files, &opts);
+	repo_refresh_and_write_index(r, REFRESH_QUIET, 1);
+	if (run_status(r, ps, &files, &opts) < 0)
+		res = -1;
 
 	release_file_list(&files);
 	strbuf_release(&print_file_item_data.buf);
diff --git a/repository.c b/repository.c
index 65e6f8b8fd..c90f310093 100644
--- a/repository.c
+++ b/repository.c
@@ -272,3 +272,22 @@ int repo_hold_locked_index(struct repository *repo,
 		BUG("the repo hasn't been setup");
 	return hold_lock_file_for_update(lf, repo->index_file, flags);
 }
+
+int repo_refresh_and_write_index(struct repository *r,
+				 unsigned int flags, int gentle)
+{
+	struct lock_file lock_file = LOCK_INIT;
+	int fd;
+
+	if (repo_read_index_preload(r, NULL, 0) < 0)
+		return error(_("could not read index"));
+	fd = repo_hold_locked_index(r, &lock_file, 0);
+	if (!gentle && fd < 0)
+		return error(_("could not lock index for writing"));
+	refresh_index(r->index, flags, NULL, NULL, NULL);
+	if (0 <= fd)
+		repo_update_index_if_able(r, &lock_file);
+	rollback_lock_file(&lock_file);
+
+	return 0;
+}
diff --git a/repository.h b/repository.h
index 8981649d43..fb49e0e328 100644
--- a/repository.h
+++ b/repository.h
@@ -154,5 +154,12 @@ int repo_read_index_unmerged(struct repository *);
  */
 void repo_update_index_if_able(struct repository *, struct lock_file *);
 
+/*
+ * Refresh the index and write it out. If the index file could not be
+ * locked, error out, except in gentle mode. The flags will be passed
+ * through to refresh_index().
+ */
+int repo_refresh_and_write_index(struct repository *r,
+				 unsigned int flags, int gentle);
 
 #endif /* REPOSITORY_H */
-- 
gitgitgadget


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

* [PATCH v2 06/11] built-in add -i: implement the main loop
  2019-05-13 17:27 ` [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                     ` (4 preceding siblings ...)
  2019-05-13 17:27   ` [PATCH v2 05/11] built-in add -i: color the header in the `status` command Johannes Schindelin via GitGitGadget
@ 2019-05-13 17:27   ` Johannes Schindelin via GitGitGadget
  2019-05-13 17:27   ` [PATCH v2 07/11] Add a function to determine unique prefixes for a list of strings Slavica Djukic via GitGitGadget
                     ` (5 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-05-13 17:27 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

The reason why we did not start with the main loop to begin with is that
it is the first user of `list_and_choose()`, which uses the `list()`
function that we conveniently introduced for use by the `status`
command.

Apart from the "and choose" part, there are more differences between the
way the `status` command calls the `list_and_choose()` function in the
Perl version of `git add -i` compared to the other callers of said
function. The most important ones:

- The list is not only shown, but the user is also asked to make a
  choice, possibly selecting multiple entries.

- The list of items is prefixed with a marker indicating what items have
  been selected, if multi-selection is allowed.

- Initially, for each item a unique prefix (if there exists any within
  the given parameters) is determined, and shown in the list, and
  accepted as a shortcut for the selection.

These features will be implemented later, except the part where the user
can choose a command. At this stage, though, the built-in `git add -i`
still only supports the `status` command, with the remaining commands to
follow over the course of the next commits.

In addition, we also modify `list()` to support displaying the commands
in columns, even if there is currently only one.

The Perl script `git-add--interactive.perl` mixed the purposes of the
"list" and the "and choose" part into the same function. In the C
version, we will keep them separate instead, calling the `list()`
function from the `list_and_choose()` function.

Note that we only have a prompt ending in a single ">" at this stage;
later commits will add commands that display a double ">>" to indicate
that the user is in a different loop than the main one.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 129 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 127 insertions(+), 2 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 6c2fca12c1..0ea4f3edb8 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -51,6 +51,7 @@ struct item {
 };
 
 struct list_options {
+	int columns;
 	const char *header;
 	void (*print_item)(int i, struct item *item, void *print_item_data);
 	void *print_item_data;
@@ -59,7 +60,7 @@ struct list_options {
 static void list(struct item **list, size_t nr,
 		 struct add_i_state *s, struct list_options *opts)
 {
-	int i;
+	int i, last_lf = 0;
 
 	if (!nr)
 		return;
@@ -70,8 +71,97 @@ static void list(struct item **list, size_t nr,
 
 	for (i = 0; i < nr; i++) {
 		opts->print_item(i, list[i], opts->print_item_data);
+
+		if ((opts->columns) && ((i + 1) % (opts->columns))) {
+			putchar('\t');
+			last_lf = 0;
+		}
+		else {
+			putchar('\n');
+			last_lf = 1;
+		}
+	}
+
+	if (!last_lf)
 		putchar('\n');
+}
+struct list_and_choose_options {
+	struct list_options list_opts;
+
+	const char *prompt;
+};
+
+#define LIST_AND_CHOOSE_ERROR (-1)
+#define LIST_AND_CHOOSE_QUIT  (-2)
+
+/*
+ * Returns the selected index.
+ *
+ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
+ * `LIST_AND_CHOOSE_QUIT` is returned.
+ */
+static ssize_t list_and_choose(struct item **items, size_t nr,
+			       struct add_i_state *s,
+			       struct list_and_choose_options *opts)
+{
+	struct strbuf input = STRBUF_INIT;
+	ssize_t res = LIST_AND_CHOOSE_ERROR;
+
+	for (;;) {
+		char *p, *endp;
+
+		strbuf_reset(&input);
+
+		list(items, nr, s, &opts->list_opts);
+
+		printf("%s%s", opts->prompt, "> ");
+		fflush(stdout);
+
+		if (strbuf_getline(&input, stdin) == EOF) {
+			putchar('\n');
+			res = LIST_AND_CHOOSE_QUIT;
+			break;
+		}
+		strbuf_trim(&input);
+
+		if (!input.len)
+			break;
+
+		p = input.buf;
+		for (;;) {
+			size_t sep = strcspn(p, " \t\r\n,");
+			ssize_t index = -1;
+
+			if (!sep) {
+				if (!*p)
+					break;
+				p++;
+				continue;
+			}
+
+			if (isdigit(*p)) {
+				index = strtoul(p, &endp, 10) - 1;
+				if (endp != p + sep)
+					index = -1;
+			}
+
+			p[sep] = '\0';
+			if (index < 0 || index >= nr)
+				printf(_("Huh (%s)?\n"), p);
+			else {
+				res = index;
+				break;
+			}
+
+			p += sep + 1;
+		}
+
+		if (res != LIST_AND_CHOOSE_ERROR)
+			break;
 	}
+
+	strbuf_release(&input);
+	return res;
 }
 
 struct adddel {
@@ -285,17 +375,40 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	return 0;
 }
 
+static void print_command_item(int i, struct item *item,
+			       void *print_command_item_data)
+{
+	printf(" %2d: %s", i + 1, item->name);
+}
+
+struct command_item {
+	struct item item;
+	int (*command)(struct add_i_state *s, const struct pathspec *ps,
+		       struct file_list *files, struct list_options *opts);
+};
+
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
+	struct list_and_choose_options main_loop_opts = {
+		{ 4, N_("*** Commands ***"), print_command_item, NULL },
+		N_("What now")
+	};
+	struct command_item
+		status = { { "status" }, run_status };
+	struct command_item *commands[] = {
+		&status
+	};
+
 	struct print_file_item_data print_file_item_data = {
 		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 	};
 	struct list_options opts = {
-		NULL, print_file_item, &print_file_item_data
+		0, NULL, print_file_item, &print_file_item_data
 	};
 	struct strbuf header = STRBUF_INIT;
 	struct file_list files = { NULL };
+	ssize_t i;
 	int res = 0;
 
 	if (init_add_i_state(r, &s))
@@ -310,6 +423,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	if (run_status(&s, ps, &files, &opts) < 0)
 		res = -1;
 
+	for (;;) {
+		i = list_and_choose((struct item **)commands,
+				    ARRAY_SIZE(commands), &s, &main_loop_opts);
+		if (i == LIST_AND_CHOOSE_QUIT) {
+			printf(_("Bye.\n"));
+			res = 0;
+			break;
+		}
+		if (i != LIST_AND_CHOOSE_ERROR)
+			res = commands[i]->command(&s, ps, &files, &opts);
+	}
+
 	release_file_list(&files);
 	strbuf_release(&print_file_item_data.buf);
 	strbuf_release(&print_file_item_data.index);
-- 
gitgitgadget


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

* [PATCH v2 05/11] built-in add -i: color the header in the `status` command
  2019-05-13 17:27 ` [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                     ` (3 preceding siblings ...)
  2019-05-13 17:27   ` [PATCH v2 04/11] built-in add -i: refresh the index before running `status` Johannes Schindelin via GitGitGadget
@ 2019-05-13 17:27   ` Johannes Schindelin via GitGitGadget
  2019-05-13 17:27   ` [PATCH v2 06/11] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
                     ` (6 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-05-13 17:27 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

For simplicity, we only implemented the `status` command without colors.
This patch starts adding color, matching what the Perl script
`git-add--interactive.perl` does.

Original-Patch-By: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 60 ++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 54 insertions(+), 6 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 2dbf29dee2..6c2fca12c1 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -1,9 +1,51 @@
 #include "cache.h"
 #include "add-interactive.h"
+#include "color.h"
+#include "config.h"
 #include "diffcore.h"
 #include "revision.h"
 #include "refs.h"
 
+struct add_i_state {
+	struct repository *r;
+	int use_color;
+	char header_color[COLOR_MAXLEN];
+};
+
+static void init_color(struct repository *r, struct add_i_state *s,
+		       const char *slot_name, char *dst,
+		       const char *default_color)
+{
+	char *key = xstrfmt("color.interactive.%s", slot_name);
+	const char *value;
+
+	if (!s->use_color)
+		dst[0] = '\0';
+	else if (repo_config_get_value(r, key, &value) ||
+		 color_parse(value, dst))
+		strlcpy(dst, default_color, COLOR_MAXLEN);
+
+	free(key);
+}
+
+static int init_add_i_state(struct repository *r, struct add_i_state *s)
+{
+	const char *value;
+
+	s->r = r;
+
+	if (repo_config_get_value(r, "color.interactive", &value))
+		s->use_color = -1;
+	else
+		s->use_color =
+			git_config_colorbool("color.interactive", value);
+	s->use_color = want_color(s->use_color);
+
+	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
+
+	return 0;
+}
+
 struct item {
 	const char *name;
 };
@@ -14,7 +56,8 @@ struct list_options {
 	void *print_item_data;
 };
 
-static void list(struct item **list, size_t nr, struct list_options *opts)
+static void list(struct item **list, size_t nr,
+		 struct add_i_state *s, struct list_options *opts)
 {
 	int i;
 
@@ -22,7 +65,8 @@ static void list(struct item **list, size_t nr, struct list_options *opts)
 		return;
 
 	if (opts->header)
-		printf("%s\n", opts->header);
+		color_fprintf_ln(stdout, s->header_color,
+				 "%s", opts->header);
 
 	for (i = 0; i < nr; i++) {
 		opts->print_item(i, list[i], opts->print_item_data);
@@ -226,16 +270,16 @@ static void print_file_item(int i, struct item *item,
 	printf(" %2d: %s", i + 1, d->buf.buf);
 }
 
-static int run_status(struct repository *r, const struct pathspec *ps,
+static int run_status(struct add_i_state *s, const struct pathspec *ps,
 		      struct file_list *files, struct list_options *opts)
 {
 	reset_file_list(files);
 
-	if (get_modified_files(r, files, ps) < 0)
+	if (get_modified_files(s->r, files, ps) < 0)
 		return -1;
 
 	if (files->nr)
-		list((struct item **)files->file, files->nr, opts);
+		list((struct item **)files->file, files->nr, s, opts);
 	putchar('\n');
 
 	return 0;
@@ -243,6 +287,7 @@ static int run_status(struct repository *r, const struct pathspec *ps,
 
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
+	struct add_i_state s = { NULL };
 	struct print_file_item_data print_file_item_data = {
 		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 	};
@@ -253,13 +298,16 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	struct file_list files = { NULL };
 	int res = 0;
 
+	if (init_add_i_state(r, &s))
+		return error("could not parse `add -i` config");
+
 	strbuf_addstr(&header, "      ");
 	strbuf_addf(&header, print_file_item_data.modified_fmt,
 		    _("staged"), _("unstaged"), _("path"));
 	opts.header = header.buf;
 
 	repo_refresh_and_write_index(r, REFRESH_QUIET, 1);
-	if (run_status(r, ps, &files, &opts) < 0)
+	if (run_status(&s, ps, &files, &opts) < 0)
 		res = -1;
 
 	release_file_list(&files);
-- 
gitgitgadget


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

* [PATCH v2 07/11] Add a function to determine unique prefixes for a list of strings
  2019-05-13 17:27 ` [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                     ` (5 preceding siblings ...)
  2019-05-13 17:27   ` [PATCH v2 06/11] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
@ 2019-05-13 17:27   ` Slavica Djukic via GitGitGadget
  2019-05-13 17:27   ` [PATCH v2 08/11] built-in add -i: show unique prefixes of the commands Slavica Djukic via GitGitGadget
                     ` (4 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Slavica Djukic via GitGitGadget @ 2019-05-13 17:27 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Slavica Djukic

From: Slavica Djukic <slawica92@hotmail.com>

In the `git add -i` command, we show unique prefixes of the commands and
files, to give an indication what prefix would select them.

Naturally, the C implementation looks a lot different than the Perl
implementation: in Perl, a trie is much easier implemented, while we
already have a pretty neat hashmap implementation in C that we use for
the purpose of storing (not necessarily unique) prefixes.

The idea: for each item that we add, we generate prefixes starting with
the first letter, then the first two letters, then three, etc, until we
find a prefix that is unique (or until the prefix length would be
longer than we want). If we encounter a previously-unique prefix on the
way, we adjust that item's prefix to make it unique again (or we mark it
as having no unique prefix if we failed to find one). These partial
prefixes are stored in a hash map (for quick lookup times).

To make sure that this function works as expected, we add a test using a
special-purpose test helper that was added for that purpose.

Note: We expect the list of prefix items to be passed in as a list of
pointers rather than as regular list to avoid having to copy information
(the actual items will most likely contain more information than just
the name and the length of the unique prefix, but passing in `struct
prefix_item *` would not allow for that).

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile                   |   2 +
 prefix-map.c               | 109 +++++++++++++++++++++++++++++++++++++
 prefix-map.h               |  40 ++++++++++++++
 t/helper/test-prefix-map.c |  58 ++++++++++++++++++++
 t/helper/test-tool.c       |   1 +
 t/helper/test-tool.h       |   1 +
 t/t0016-prefix-map.sh      |  10 ++++
 7 files changed, 221 insertions(+)
 create mode 100644 prefix-map.c
 create mode 100644 prefix-map.h
 create mode 100644 t/helper/test-prefix-map.c
 create mode 100755 t/t0016-prefix-map.sh

diff --git a/Makefile b/Makefile
index 18e656a32f..8299b3f17d 100644
--- a/Makefile
+++ b/Makefile
@@ -754,6 +754,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o
 TEST_BUILTINS_OBJS += test-parse-options.o
 TEST_BUILTINS_OBJS += test-path-utils.o
 TEST_BUILTINS_OBJS += test-pkt-line.o
+TEST_BUILTINS_OBJS += test-prefix-map.o
 TEST_BUILTINS_OBJS += test-prio-queue.o
 TEST_BUILTINS_OBJS += test-reach.o
 TEST_BUILTINS_OBJS += test-read-cache.o
@@ -967,6 +968,7 @@ LIB_OBJS += patch-ids.o
 LIB_OBJS += path.o
 LIB_OBJS += pathspec.o
 LIB_OBJS += pkt-line.o
+LIB_OBJS += prefix-map.o
 LIB_OBJS += preload-index.o
 LIB_OBJS += pretty.o
 LIB_OBJS += prio-queue.o
diff --git a/prefix-map.c b/prefix-map.c
new file mode 100644
index 0000000000..747ddb4ebc
--- /dev/null
+++ b/prefix-map.c
@@ -0,0 +1,109 @@
+#include "cache.h"
+#include "prefix-map.h"
+
+static int map_cmp(const void *unused_cmp_data,
+		   const void *entry,
+		   const void *entry_or_key,
+		   const void *unused_keydata)
+{
+	const struct prefix_map_entry *a = entry;
+	const struct prefix_map_entry *b = entry_or_key;
+
+	return a->prefix_length != b->prefix_length ||
+		strncmp(a->name, b->name, a->prefix_length);
+}
+
+static void add_prefix_entry(struct hashmap *map, const char *name,
+			     size_t prefix_length, struct prefix_item *item)
+{
+	struct prefix_map_entry *result = xmalloc(sizeof(*result));
+	result->name = name;
+	result->prefix_length = prefix_length;
+	result->item = item;
+	hashmap_entry_init(result, memhash(name, prefix_length));
+	hashmap_add(map, result);
+}
+
+static void init_prefix_map(struct prefix_map *prefix_map,
+			    int min_prefix_length, int max_prefix_length)
+{
+	hashmap_init(&prefix_map->map, map_cmp, NULL, 0);
+	prefix_map->min_length = min_prefix_length;
+	prefix_map->max_length = max_prefix_length;
+}
+
+static void add_prefix_item(struct prefix_map *prefix_map,
+			    struct prefix_item *item)
+{
+	struct prefix_map_entry e = { { NULL } }, *e2;
+	int j;
+
+	e.item = item;
+	e.name = item->name;
+
+	for (j = prefix_map->min_length;
+	     j <= prefix_map->max_length && e.name[j]; j++) {
+		/* Avoid breaking UTF-8 multi-byte sequences */
+		if (!isascii(e.name[j]))
+			break;
+
+		e.prefix_length = j;
+		hashmap_entry_init(&e, memhash(e.name, j));
+		e2 = hashmap_get(&prefix_map->map, &e, NULL);
+		if (!e2) {
+			/* prefix is unique at this stage */
+			item->prefix_length = j;
+			add_prefix_entry(&prefix_map->map, e.name, j, item);
+			break;
+		}
+
+		if (!e2->item)
+			continue; /* non-unique prefix */
+
+		if (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))
+			BUG("unexpected prefix length: %d != %d (%s != %s)",
+			    j, (int)e2->item->prefix_length, e.name, e2->name);
+
+		/* skip common prefix */
+		for (; j < prefix_map->max_length && e.name[j]; j++) {
+			if (e.item->name[j] != e2->item->name[j])
+				break;
+			add_prefix_entry(&prefix_map->map, e.name, j + 1,
+					 NULL);
+		}
+
+		/* e2 no longer refers to a unique prefix */
+		if (j < prefix_map->max_length && e2->name[j]) {
+			/* found a new unique prefix for e2's item */
+			e2->item->prefix_length = j + 1;
+			add_prefix_entry(&prefix_map->map, e2->name, j + 1,
+					 e2->item);
+		}
+		else
+			e2->item->prefix_length = 0;
+		e2->item = NULL;
+
+		if (j < prefix_map->max_length && e.name[j]) {
+			/* found a unique prefix for the item */
+			e.item->prefix_length = j + 1;
+			add_prefix_entry(&prefix_map->map, e.name, j + 1,
+					 e.item);
+		} else
+			/* item has no (short enough) unique prefix */
+			e.item->prefix_length = 0;
+
+		break;
+	}
+}
+
+void find_unique_prefixes(struct prefix_item **list, size_t nr,
+			  int min_length, int max_length)
+{
+	int i;
+	struct prefix_map prefix_map;
+
+	init_prefix_map(&prefix_map, min_length, max_length);
+	for (i = 0; i < nr; i++)
+		add_prefix_item(&prefix_map, list[i]);
+	hashmap_free(&prefix_map.map, 1);
+}
diff --git a/prefix-map.h b/prefix-map.h
new file mode 100644
index 0000000000..ce3b8a4a32
--- /dev/null
+++ b/prefix-map.h
@@ -0,0 +1,40 @@
+#ifndef PREFIX_MAP_H
+#define PREFIX_MAP_H
+
+#include "hashmap.h"
+
+struct prefix_item {
+	const char *name;
+	size_t prefix_length;
+};
+
+struct prefix_map_entry {
+	struct hashmap_entry e;
+	const char *name;
+	size_t prefix_length;
+	/* if item is NULL, the prefix is not unique */
+	struct prefix_item *item;
+};
+
+struct prefix_map {
+	struct hashmap map;
+	int min_length, max_length;
+};
+
+/*
+ * Find unique prefixes in a given list of strings.
+ *
+ * Typically, the `struct prefix_item` information will be but a field in the
+ * actual item struct; For this reason, the `list` parameter is specified as a
+ * list of pointers to the items.
+ *
+ * The `min_length`/`max_length` parameters define what length the unique
+ * prefixes should have.
+ *
+ * If no unique prefix could be found for a given item, its `prefix_length`
+ * will be set to 0.
+ */
+void find_unique_prefixes(struct prefix_item **list, size_t nr,
+			  int min_length, int max_length);
+
+#endif
diff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c
new file mode 100644
index 0000000000..3f1c90eaf0
--- /dev/null
+++ b/t/helper/test-prefix-map.c
@@ -0,0 +1,58 @@
+#include "test-tool.h"
+#include "cache.h"
+#include "prefix-map.h"
+
+static size_t test_count, failed_count;
+
+static void check(int succeeded, const char *file, size_t line_no,
+		  const char *fmt, ...)
+{
+	va_list ap;
+
+	test_count++;
+	if (succeeded)
+		return;
+
+	va_start(ap, fmt);
+	fprintf(stderr, "%s:%d: ", file, (int)line_no);
+	vfprintf(stderr, fmt, ap);
+	fputc('\n', stderr);
+	va_end(ap);
+
+	failed_count++;
+}
+
+#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \
+	check(expect == actual, __FILE__, __LINE__, \
+	      "size_t's do not match: %" \
+	      PRIdMAX " != %" PRIdMAX " (%s) (%s)", \
+	      (intmax_t)expect, (intmax_t)actual, #actual, hint)
+
+int cmd__prefix_map(int argc, const char **argv)
+{
+#define NR 5
+	struct prefix_item items[NR] = {
+		{ "unique" },
+		{ "hell" },
+		{ "hello" },
+		{ "wok" },
+		{ "world" },
+	};
+	struct prefix_item *list[NR] = {
+		items, items + 1, items + 2, items + 3, items + 4
+	};
+
+	find_unique_prefixes(list, NR, 1, 3);
+
+#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \
+	EXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \
+			     list[index]->name)
+
+	EXPECT_PREFIX_LENGTH_EQUALS(1, 0);
+	EXPECT_PREFIX_LENGTH_EQUALS(0, 1);
+	EXPECT_PREFIX_LENGTH_EQUALS(0, 2);
+	EXPECT_PREFIX_LENGTH_EQUALS(3, 3);
+	EXPECT_PREFIX_LENGTH_EQUALS(3, 4);
+
+	return !!failed_count;
+}
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index 99db7409b8..d6a92a8699 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -32,6 +32,7 @@ static struct test_cmd cmds[] = {
 	{ "parse-options", cmd__parse_options },
 	{ "path-utils", cmd__path_utils },
 	{ "pkt-line", cmd__pkt_line },
+	{ "prefix-map", cmd__prefix_map },
 	{ "prio-queue", cmd__prio_queue },
 	{ "reach", cmd__reach },
 	{ "read-cache", cmd__read_cache },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index 25abed1cf2..33a089ee4e 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -29,6 +29,7 @@ int cmd__online_cpus(int argc, const char **argv);
 int cmd__parse_options(int argc, const char **argv);
 int cmd__path_utils(int argc, const char **argv);
 int cmd__pkt_line(int argc, const char **argv);
+int cmd__prefix_map(int argc, const char **argv);
 int cmd__prio_queue(int argc, const char **argv);
 int cmd__reach(int argc, const char **argv);
 int cmd__read_cache(int argc, const char **argv);
diff --git a/t/t0016-prefix-map.sh b/t/t0016-prefix-map.sh
new file mode 100755
index 0000000000..187fa92aec
--- /dev/null
+++ b/t/t0016-prefix-map.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+test_description='basic tests for prefix map'
+. ./test-lib.sh
+
+test_expect_success 'prefix map' '
+	test-tool prefix-map
+'
+
+test_done
-- 
gitgitgadget


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

* [PATCH v2 08/11] built-in add -i: show unique prefixes of the commands
  2019-05-13 17:27 ` [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                     ` (6 preceding siblings ...)
  2019-05-13 17:27   ` [PATCH v2 07/11] Add a function to determine unique prefixes for a list of strings Slavica Djukic via GitGitGadget
@ 2019-05-13 17:27   ` Slavica Djukic via GitGitGadget
  2019-05-13 17:28   ` [PATCH v2 09/11] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
                     ` (3 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Slavica Djukic via GitGitGadget @ 2019-05-13 17:27 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Slavica Djukic

From: Slavica Djukic <slawica92@hotmail.com>

Just like in the Perl script `git-add--interactive.perl`, for each
command a unique prefix is determined (if there exists any within the
given parameters), and shown in the list, and accepted as a shortcut for
the command.

We use the prefix map implementation that we just added in the previous
commit for that purpose.

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
---
 add-interactive.c | 69 ++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 56 insertions(+), 13 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 0ea4f3edb8..7ff87bae1b 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -5,6 +5,7 @@
 #include "diffcore.h"
 #include "revision.h"
 #include "refs.h"
+#include "prefix-map.h"
 
 struct add_i_state {
 	struct repository *r;
@@ -46,18 +47,32 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)
 	return 0;
 }
 
-struct item {
-	const char *name;
-};
+static ssize_t find_unique(const char *string,
+			   struct prefix_item **list, size_t nr)
+{
+	ssize_t found = -1, i;
+
+	for (i = 0; i < nr; i++) {
+		struct prefix_item *item = list[i];
+		if (!starts_with(item->name, string))
+			continue;
+		if (found >= 0)
+			return -1;
+		found = i;
+	}
+
+	return found;
+}
 
 struct list_options {
 	int columns;
 	const char *header;
-	void (*print_item)(int i, struct item *item, void *print_item_data);
+	void (*print_item)(int i, struct prefix_item *item,
+			   void *print_item_data);
 	void *print_item_data;
 };
 
-static void list(struct item **list, size_t nr,
+static void list(struct prefix_item **list, size_t nr,
 		 struct add_i_state *s, struct list_options *opts)
 {
 	int i, last_lf = 0;
@@ -100,13 +115,15 @@ struct list_and_choose_options {
  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
  * `LIST_AND_CHOOSE_QUIT` is returned.
  */
-static ssize_t list_and_choose(struct item **items, size_t nr,
+static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 			       struct add_i_state *s,
 			       struct list_and_choose_options *opts)
 {
 	struct strbuf input = STRBUF_INIT;
 	ssize_t res = LIST_AND_CHOOSE_ERROR;
 
+	find_unique_prefixes(items, nr, 1, 4);
+
 	for (;;) {
 		char *p, *endp;
 
@@ -146,6 +163,9 @@ static ssize_t list_and_choose(struct item **items, size_t nr,
 			}
 
 			p[sep] = '\0';
+			if (index < 0)
+				index = find_unique(p, items, nr);
+
 			if (index < 0 || index >= nr)
 				printf(_("Huh (%s)?\n"), p);
 			else {
@@ -171,7 +191,7 @@ struct adddel {
 
 struct file_list {
 	struct file_item {
-		struct item item;
+		struct prefix_item item;
 		struct adddel index, worktree;
 	} **file;
 	size_t nr, alloc;
@@ -337,12 +357,29 @@ static void populate_wi_changes(struct strbuf *buf,
 		strbuf_addstr(buf, no_changes);
 }
 
+/* filters out prefixes which have special meaning to list_and_choose() */
+static int is_valid_prefix(const char *prefix, size_t prefix_len)
+{
+	return prefix_len && prefix &&
+		/*
+		 * We expect `prefix` to be NUL terminated, therefore this
+		 * `strcspn()` call is okay, even if it might do much more
+		 * work than strictly necessary.
+		 */
+		strcspn(prefix, " \t\r\n,") >= prefix_len &&	/* separators */
+		*prefix != '-' &&				/* deselection */
+		!isdigit(*prefix) &&				/* selection */
+		(prefix_len != 1 ||
+		 (*prefix != '*' &&				/* "all" wildcard */
+		  *prefix != '?'));				/* prompt help */
+}
+
 struct print_file_item_data {
 	const char *modified_fmt;
 	struct strbuf buf, index, worktree;
 };
 
-static void print_file_item(int i, struct item *item,
+static void print_file_item(int i, struct prefix_item *item,
 			    void *print_file_item_data)
 {
 	struct file_item *c = (struct file_item *)item;
@@ -369,20 +406,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 		return -1;
 
 	if (files->nr)
-		list((struct item **)files->file, files->nr, s, opts);
+		list((struct prefix_item **)files->file, files->nr, s, opts);
 	putchar('\n');
 
 	return 0;
 }
 
-static void print_command_item(int i, struct item *item,
+static void print_command_item(int i, struct prefix_item *item,
 			       void *print_command_item_data)
 {
-	printf(" %2d: %s", i + 1, item->name);
+	if (!item->prefix_length ||
+	    !is_valid_prefix(item->name, item->prefix_length))
+		printf(" %2d: %s", i + 1, item->name);
+	else
+		printf(" %3d: [%.*s]%s", i + 1,
+		       (int)item->prefix_length, item->name,
+		       item->name + item->prefix_length);
 }
 
 struct command_item {
-	struct item item;
+	struct prefix_item item;
 	int (*command)(struct add_i_state *s, const struct pathspec *ps,
 		       struct file_list *files, struct list_options *opts);
 };
@@ -424,7 +467,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 		res = -1;
 
 	for (;;) {
-		i = list_and_choose((struct item **)commands,
+		i = list_and_choose((struct prefix_item **)commands,
 				    ARRAY_SIZE(commands), &s, &main_loop_opts);
 		if (i == LIST_AND_CHOOSE_QUIT) {
 			printf(_("Bye.\n"));
-- 
gitgitgadget


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

* [PATCH v2 09/11] built-in add -i: support `?` (prompt help)
  2019-05-13 17:27 ` [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                     ` (7 preceding siblings ...)
  2019-05-13 17:27   ` [PATCH v2 08/11] built-in add -i: show unique prefixes of the commands Slavica Djukic via GitGitGadget
@ 2019-05-13 17:28   ` Johannes Schindelin via GitGitGadget
  2019-05-13 17:28   ` [PATCH v2 10/11] built-in add -i: use color in the main loop Slavica Djukic via GitGitGadget
                     ` (2 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-05-13 17:28 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

With this change, we print out the same colored help text that the
Perl-based `git add -i` prints in the main loop when question mark is
entered.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index 7ff87bae1b..1435c12be9 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -11,6 +11,7 @@ struct add_i_state {
 	struct repository *r;
 	int use_color;
 	char header_color[COLOR_MAXLEN];
+	char help_color[COLOR_MAXLEN];
 };
 
 static void init_color(struct repository *r, struct add_i_state *s,
@@ -43,6 +44,7 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)
 	s->use_color = want_color(s->use_color);
 
 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
+	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
 
 	return 0;
 }
@@ -104,6 +106,7 @@ struct list_and_choose_options {
 	struct list_options list_opts;
 
 	const char *prompt;
+	void (*print_help)(struct add_i_state *s);
 };
 
 #define LIST_AND_CHOOSE_ERROR (-1)
@@ -144,6 +147,11 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 		if (!input.len)
 			break;
 
+		if (!strcmp(input.buf, "?")) {
+			opts->print_help(s);
+			continue;
+		}
+
 		p = input.buf;
 		for (;;) {
 			size_t sep = strcspn(p, " \t\r\n,");
@@ -430,12 +438,24 @@ struct command_item {
 		       struct file_list *files, struct list_options *opts);
 };
 
+static void command_prompt_help(struct add_i_state *s)
+{
+	const char *help_color = s->help_color;
+	color_fprintf_ln(stdout, help_color, "%s", _("Prompt help:"));
+	color_fprintf_ln(stdout, help_color, "1          - %s",
+			 _("select a numbered item"));
+	color_fprintf_ln(stdout, help_color, "foo        - %s",
+			 _("select item based on unique prefix"));
+	color_fprintf_ln(stdout, help_color, "           - %s",
+			 _("(empty) select nothing"));
+}
+
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
 	struct list_and_choose_options main_loop_opts = {
 		{ 4, N_("*** Commands ***"), print_command_item, NULL },
-		N_("What now")
+		N_("What now"), command_prompt_help
 	};
 	struct command_item
 		status = { { "status" }, run_status };
-- 
gitgitgadget


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

* [PATCH v2 10/11] built-in add -i: use color in the main loop
  2019-05-13 17:27 ` [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                     ` (8 preceding siblings ...)
  2019-05-13 17:28   ` [PATCH v2 09/11] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
@ 2019-05-13 17:28   ` Slavica Djukic via GitGitGadget
  2019-05-13 17:28   ` [PATCH v2 11/11] built-in add -i: implement the `help` command Johannes Schindelin via GitGitGadget
  2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  11 siblings, 0 replies; 124+ messages in thread
From: Slavica Djukic via GitGitGadget @ 2019-05-13 17:28 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Slavica Djukic

From: Slavica Djukic <slawica92@hotmail.com>

The error messages as well as the unique prefixes are colored in `git
add -i` by default; We need to do the same in the built-in version.

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 37 ++++++++++++++++++++++++++++++++-----
 1 file changed, 32 insertions(+), 5 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 1435c12be9..538658dfa7 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -12,6 +12,9 @@ struct add_i_state {
 	int use_color;
 	char header_color[COLOR_MAXLEN];
 	char help_color[COLOR_MAXLEN];
+	char prompt_color[COLOR_MAXLEN];
+	char error_color[COLOR_MAXLEN];
+	char reset_color[COLOR_MAXLEN];
 };
 
 static void init_color(struct repository *r, struct add_i_state *s,
@@ -45,6 +48,9 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)
 
 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
 	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
+	init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE);
+	init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED);
+	init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET);
 
 	return 0;
 }
@@ -134,7 +140,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 
 		list(items, nr, s, &opts->list_opts);
 
-		printf("%s%s", opts->prompt, "> ");
+		color_fprintf(stdout, s->prompt_color, "%s", opts->prompt);
+		fputs("> ", stdout);
 		fflush(stdout);
 
 		if (strbuf_getline(&input, stdin) == EOF) {
@@ -175,7 +182,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 				index = find_unique(p, items, nr);
 
 			if (index < 0 || index >= nr)
-				printf(_("Huh (%s)?\n"), p);
+				color_fprintf_ln(stdout, s->error_color,
+						 _("Huh (%s)?"), p);
 			else {
 				res = index;
 				break;
@@ -420,15 +428,21 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	return 0;
 }
 
+struct print_command_item_data {
+	const char *color, *reset;
+};
+
 static void print_command_item(int i, struct prefix_item *item,
 			       void *print_command_item_data)
 {
+	struct print_command_item_data *d = print_command_item_data;
+
 	if (!item->prefix_length ||
 	    !is_valid_prefix(item->name, item->prefix_length))
 		printf(" %2d: %s", i + 1, item->name);
 	else
-		printf(" %3d: [%.*s]%s", i + 1,
-		       (int)item->prefix_length, item->name,
+		printf(" %2d: %s%.*s%s%s", i + 1,
+		       d->color, (int)item->prefix_length, item->name, d->reset,
 		       item->name + item->prefix_length);
 }
 
@@ -453,8 +467,9 @@ static void command_prompt_help(struct add_i_state *s)
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
+	struct print_command_item_data data;
 	struct list_and_choose_options main_loop_opts = {
-		{ 4, N_("*** Commands ***"), print_command_item, NULL },
+		{ 4, N_("*** Commands ***"), print_command_item, &data },
 		N_("What now"), command_prompt_help
 	};
 	struct command_item
@@ -477,6 +492,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	if (init_add_i_state(r, &s))
 		return error("could not parse `add -i` config");
 
+	/*
+	 * When color was asked for, use the prompt color for
+	 * highlighting, otherwise use square brackets.
+	 */
+	if (s.use_color) {
+		data.color = s.prompt_color;
+		data.reset = s.reset_color;
+	} else {
+		data.color = "[";
+		data.reset = "]";
+	}
+
 	strbuf_addstr(&header, "      ");
 	strbuf_addf(&header, print_file_item_data.modified_fmt,
 		    _("staged"), _("unstaged"), _("path"));
-- 
gitgitgadget


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

* [PATCH v2 11/11] built-in add -i: implement the `help` command
  2019-05-13 17:27 ` [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                     ` (9 preceding siblings ...)
  2019-05-13 17:28   ` [PATCH v2 10/11] built-in add -i: use color in the main loop Slavica Djukic via GitGitGadget
@ 2019-05-13 17:28   ` Johannes Schindelin via GitGitGadget
  2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-05-13 17:28 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

This imitates the code to show the help text from the Perl script
`git-add--interactive.perl` in the built-in version.

To make sure that it renders exactly like the Perl version of `git add
-i`, we also add a test case for that to `t3701-add-interactive.sh`.

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c          | 27 +++++++++++++++++++++++++--
 t/t3701-add-interactive.sh | 24 ++++++++++++++++++++++++
 2 files changed, 49 insertions(+), 2 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 538658dfa7..c431c72e3f 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -428,6 +428,27 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	return 0;
 }
 
+static int run_help(struct add_i_state *s, const struct pathspec *ps,
+		    struct file_list *files, struct list_options *opts)
+{
+	const char *help_color = s->help_color;
+
+	color_fprintf_ln(stdout, help_color, "status        - %s",
+			 _("show paths with changes"));
+	color_fprintf_ln(stdout, help_color, "update        - %s",
+			 _("add working tree state to the staged set of changes"));
+	color_fprintf_ln(stdout, help_color, "revert        - %s",
+			 _("revert staged set of changes back to the HEAD version"));
+	color_fprintf_ln(stdout, help_color, "patch         - %s",
+			 _("pick hunks and update selectively"));
+	color_fprintf_ln(stdout, help_color, "diff          - %s",
+			 _("view diff between HEAD and index"));
+	color_fprintf_ln(stdout, help_color, "add untracked - %s",
+			 _("add contents of untracked files to the staged set of changes"));
+
+	return 0;
+}
+
 struct print_command_item_data {
 	const char *color, *reset;
 };
@@ -473,9 +494,11 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 		N_("What now"), command_prompt_help
 	};
 	struct command_item
-		status = { { "status" }, run_status };
+		status = { { "status" }, run_status },
+		help = { { "help" }, run_help };
 	struct command_item *commands[] = {
-		&status
+		&status,
+		&help
 	};
 
 	struct print_file_item_data print_file_item_data = {
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 65dfbc033a..91aaef2932 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -639,4 +639,28 @@ test_expect_success 'add -p patch editing works with pathological context lines'
 	test_cmp expected-2 actual
 '
 
+test_expect_success 'show help from add--helper' '
+	git reset --hard &&
+	cat >expect <<-EOF &&
+
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>
+	<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>
+	<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>
+	<BOLD;RED>patch         - pick hunks and update selectively<RESET>
+	<BOLD;RED>diff          - view diff between HEAD and index<RESET>
+	<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>>$SP
+	Bye.
+	EOF
+	test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&
+	test_decode_color <actual.colored >actual &&
+	test_i18ncmp expect actual
+'
+
 test_done
-- 
gitgitgadget

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

* [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help  so far)
  2019-05-13 17:27 ` [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                     ` (10 preceding siblings ...)
  2019-05-13 17:28   ` [PATCH v2 11/11] built-in add -i: implement the `help` command Johannes Schindelin via GitGitGadget
@ 2019-07-16 14:58   ` Johannes Schindelin via GitGitGadget
  2019-07-16 14:58     ` [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
                       ` (12 more replies)
  11 siblings, 13 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-07-16 14:58 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano

This is the first leg on the long journey to a fully built-in git add -i 
(next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3
[https://github.com/gitgitgadget/git/pull/172], 4
[https://github.com/gitgitgadget/git/pull/173], 5
[https://github.com/gitgitgadget/git/pull/174], and 6
[https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are
not necessarily up to date, and will be re-targeted to the appropriate
branches in https://github.com/gitster/git as soon as Junio picks them up.

This here patch series reflects the part that was submitted a couple of
times (see https://github.com/gitgitgadget/git/pull/103) during the
Outreachy project by Slavica Ðukic that continued the journey based on an
initial patch series by Daniel Ferreira.

It only implements the status and the help part, in the interest of making
the review remotely more reviewable.

As I am a heavy user of git add -p myself and use a patched version for
weeks already (it is so nice to not suffer over one second startup until the
MSYS2 Perl finally shows me anything, instead it feels instantaneous), I
integrated these patch series into Git for Windows' master already, as an
opt-in feature guarded by the config variable add.interactive.useBuiltin 
(and Git for Windows' installer is prepared to detect this version and offer
the option in the graphical user interface).

I had planned on submitting this before v2.22.0-rc0, but there was such a
backlog of builds from a big pushout that I had to wait ;-)

Changes since v2:

 * Rebased to master to avoid merge conflicts.
 * Renumbered the prefix-map test to avoid conflicts with two patch series
   that are currently in-flight in pu.

Changes since v1:

 * The config machinery was reworked completely, to not use a callback to 
   git_config(), but instead to query the config via the repo_config_get_*() 
   functions. This also prevents a future "Huh???" moment: the internal add
   --interactive API accepts a parameter of type struct repository *r, but
   the previous configuration did not use that to query the config (and
   could in the future be a repository other than the_repository).
   
   
 * As a consequence, the color sequences are no longer stored in file-local
   variables, but passed around via a struct.
   
   
 * Instead of using the magical constant -2 to quit the main loop, it is now
   defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is
   defined as -1 and used where appropriate).
   
   
 * Improved the add_prefix_item() function by avoiding buffer overruns, not
   reusing the struct that is used for lookup also for adding the new item,
   and by strengthening the bug check.

Daniel Ferreira (2):
  diff: export diffstat interface
  built-in add -i: implement the `status` command

Johannes Schindelin (6):
  Start to implement a built-in version of `git add --interactive`
  built-in add -i: refresh the index before running `status`
  built-in add -i: color the header in the `status` command
  built-in add -i: implement the main loop
  built-in add -i: support `?` (prompt help)
  built-in add -i: implement the `help` command

Slavica Djukic (3):
  Add a function to determine unique prefixes for a list of strings
  built-in add -i: show unique prefixes of the commands
  built-in add -i: use color in the main loop

 Documentation/config/add.txt |   5 +
 Makefile                     |   3 +
 add-interactive.c            | 558 +++++++++++++++++++++++++++++++++++
 add-interactive.h            |   8 +
 builtin/add.c                |  10 +
 diff.c                       |  37 +--
 diff.h                       |  19 ++
 prefix-map.c                 | 109 +++++++
 prefix-map.h                 |  40 +++
 repository.c                 |  19 ++
 repository.h                 |   7 +
 t/README                     |   4 +
 t/helper/test-prefix-map.c   |  58 ++++
 t/helper/test-tool.c         |   1 +
 t/helper/test-tool.h         |   1 +
 t/t0018-prefix-map.sh        |  10 +
 t/t3701-add-interactive.sh   |  25 ++
 17 files changed, 892 insertions(+), 22 deletions(-)
 create mode 100644 add-interactive.c
 create mode 100644 add-interactive.h
 create mode 100644 prefix-map.c
 create mode 100644 prefix-map.h
 create mode 100644 t/helper/test-prefix-map.c
 create mode 100755 t/t0018-prefix-map.sh


base-commit: 9d418600f4d10dcbbfb0b5fdbc71d509e03ba719
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/170

Range-diff vs v2:

  1:  ed53346b92 !  1:  0a5ec9345d Start to implement a built-in version of `git add --interactive`
     @@ -47,7 +47,7 @@
       --- a/Makefile
       +++ b/Makefile
      @@
     - 	-name '*.h' -print)
     + 	-name '*.h' -print))
       
       LIB_OBJS += abspath.o
      +LIB_OBJS += add-interactive.o
     @@ -121,7 +121,7 @@
       --- a/t/README
       +++ b/t/README
      @@
     - builtin version of git-rebase. See 'rebase.useBuiltin' in
     + built-in version of git-stash. See 'stash.useBuiltin' in
       git-config(1).
       
      +GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the
  2:  bc99009fbf =  2:  c7a377890d diff: export diffstat interface
  3:  5e23c0756b =  3:  b93b055ebe built-in add -i: implement the `status` command
  4:  8cafc6ae8d =  4:  daff24074a built-in add -i: refresh the index before running `status`
  5:  83d92a9762 =  5:  15f18f5b3e built-in add -i: color the header in the `status` command
  6:  3eec219124 =  6:  175409aaae built-in add -i: implement the main loop
  7:  e02a52c3ac !  7:  3000d7d08d Add a function to determine unique prefixes for a list of strings
     @@ -299,10 +299,10 @@
       int cmd__reach(int argc, const char **argv);
       int cmd__read_cache(int argc, const char **argv);
      
     - diff --git a/t/t0016-prefix-map.sh b/t/t0016-prefix-map.sh
     + diff --git a/t/t0018-prefix-map.sh b/t/t0018-prefix-map.sh
       new file mode 100755
       --- /dev/null
     - +++ b/t/t0016-prefix-map.sh
     + +++ b/t/t0018-prefix-map.sh
      @@
      +#!/bin/sh
      +
  8:  ced9b6aced =  8:  e23ddebfbf built-in add -i: show unique prefixes of the commands
  9:  7378af60ad =  9:  d8c012fce8 built-in add -i: support `?` (prompt help)
 10:  36edef85e6 = 10:  8121a3ca1b built-in add -i: use color in the main loop
 11:  266dbf2a6b ! 11:  db70c6475d built-in add -i: implement the `help` command
     @@ -61,9 +61,10 @@
       --- a/t/t3701-add-interactive.sh
       +++ b/t/t3701-add-interactive.sh
      @@
     - 	test_cmp expected-2 actual
     + 	test_write_lines a b a b a a b a b a >expect &&
     + 	test_cmp expect a
       '
     - 
     ++
      +test_expect_success 'show help from add--helper' '
      +	git reset --hard &&
      +	cat >expect <<-EOF &&

-- 
gitgitgadget

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

* [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`
  2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
@ 2019-07-16 14:58     ` Johannes Schindelin via GitGitGadget
  2019-07-31 17:52       ` Junio C Hamano
  2019-07-16 14:58     ` [PATCH v3 02/11] diff: export diffstat interface Daniel Ferreira via GitGitGadget
                       ` (11 subsequent siblings)
  12 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-07-16 14:58 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

This is hardly the first conversion of a Git command that is implemented
as a script to a built-in. So far, the most successful strategy for such
conversions has been to add a built-in helper and call that for more and
more functionality from the script, as more and more parts are
converted.

With the interactive add, we choose a different strategy. The sole
reason for this is that on Windows (where such a conversion has the most
benefits in terms of speed and robustness) we face the very specific
problem that a `system()` call in Perl seems to close `stdin` in the
parent process when the spawned process consumes even one character from
`stdin`. And that just does not work for us here, as it would stop the
main loop as soon as any interactive command was performed by the
helper. Which is almost all of the commands in `git add -i`.

It is almost as if Perl told us once again that it does not want us to
use it on Windows.

Instead, we follow the opposite route where we start with a bare-bones
version of the built-in interactive add, guarded by the new
`add.interactive.useBuiltin` config variable, and then add more and more
functionality to it, until it is feature complete.

At this point, the built-in version of `git add -i` only states that it
cannot do anything yet ;-)

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/config/add.txt |  5 +++++
 Makefile                     |  1 +
 add-interactive.c            |  7 +++++++
 add-interactive.h            |  8 ++++++++
 builtin/add.c                | 10 ++++++++++
 t/README                     |  4 ++++
 6 files changed, 35 insertions(+)
 create mode 100644 add-interactive.c
 create mode 100644 add-interactive.h

diff --git a/Documentation/config/add.txt b/Documentation/config/add.txt
index 4d753f006e..c9f748f81c 100644
--- a/Documentation/config/add.txt
+++ b/Documentation/config/add.txt
@@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::
 	option of linkgit:git-add[1].  `add.ignore-errors` is deprecated,
 	as it does not follow the usual naming convention for configuration
 	variables.
+
+add.interactive.useBuiltin::
+	[EXPERIMENTAL] Set to `true` to use the experimental built-in
+	implementation of the interactive version of linkgit:git-add[1]
+	instead of the Perl script version. Is `false` by default.
diff --git a/Makefile b/Makefile
index 98a0588416..0a47200f47 100644
--- a/Makefile
+++ b/Makefile
@@ -824,6 +824,7 @@ LIB_H := $(sort $(shell git ls-files '*.h' ':!t/' ':!Documentation/' 2>/dev/null
 	-name '*.h' -print))
 
 LIB_OBJS += abspath.o
+LIB_OBJS += add-interactive.o
 LIB_OBJS += advice.o
 LIB_OBJS += alias.o
 LIB_OBJS += alloc.o
diff --git a/add-interactive.c b/add-interactive.c
new file mode 100644
index 0000000000..482e458dc6
--- /dev/null
+++ b/add-interactive.c
@@ -0,0 +1,7 @@
+#include "cache.h"
+#include "add-interactive.h"
+
+int run_add_i(struct repository *r, const struct pathspec *ps)
+{
+	die(_("No commands are available in the built-in `git add -i` yet!"));
+}
diff --git a/add-interactive.h b/add-interactive.h
new file mode 100644
index 0000000000..7043b8741d
--- /dev/null
+++ b/add-interactive.h
@@ -0,0 +1,8 @@
+#ifndef ADD_INTERACTIVE_H
+#define ADD_INTERACTIVE_H
+
+struct repository;
+struct pathspec;
+int run_add_i(struct repository *r, const struct pathspec *ps);
+
+#endif
diff --git a/builtin/add.c b/builtin/add.c
index dd18e5c9b6..4f625691b5 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -20,6 +20,7 @@
 #include "bulk-checkin.h"
 #include "argv-array.h"
 #include "submodule.h"
+#include "add-interactive.h"
 
 static const char * const builtin_add_usage[] = {
 	N_("git add [<options>] [--] <pathspec>..."),
@@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,
 {
 	int status, i;
 	struct argv_array argv = ARGV_ARRAY_INIT;
+	int use_builtin_add_i =
+		git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
+	if (use_builtin_add_i < 0)
+		git_config_get_bool("add.interactive.usebuiltin",
+				    &use_builtin_add_i);
+
+	if (use_builtin_add_i == 1 && !patch_mode)
+		return !!run_add_i(the_repository, pathspec);
 
 	argv_array_push(&argv, "add--interactive");
 	if (patch_mode)
@@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb)
 		ignore_add_errors = git_config_bool(var, value);
 		return 0;
 	}
+
 	return git_default_config(var, value, cb);
 }
 
diff --git a/t/README b/t/README
index 9747971d58..28999cebd3 100644
--- a/t/README
+++ b/t/README
@@ -397,6 +397,10 @@ 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_ADD_I_USE_BUILTIN=<boolean>, when true, enables the
+builtin version of git add -i. See 'add.interactive.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
-- 
gitgitgadget


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

* [PATCH v3 02/11] diff: export diffstat interface
  2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  2019-07-16 14:58     ` [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
@ 2019-07-16 14:58     ` Daniel Ferreira via GitGitGadget
  2019-07-31 17:59       ` Junio C Hamano
  2019-07-16 14:58     ` [PATCH v3 03/11] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
                       ` (10 subsequent siblings)
  12 siblings, 1 reply; 124+ messages in thread
From: Daniel Ferreira via GitGitGadget @ 2019-07-16 14:58 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Daniel Ferreira

From: Daniel Ferreira <bnmvco@gmail.com>

Make the diffstat interface (namely, the diffstat_t struct and
compute_diffstat) no longer be internal to diff.c and allow it to be used
by other parts of git.

This is helpful for code that may want to easily extract information
from files using the diff machinery, while flushing it differently from
how the show_* functions used by diff_flush() do it. One example is the
builtin implementation of git-add--interactive's status.

Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 37 +++++++++++++++----------------------
 diff.h | 19 +++++++++++++++++++
 2 files changed, 34 insertions(+), 22 deletions(-)

diff --git a/diff.c b/diff.c
index 1ee04e321b..03d6f00d97 100644
--- a/diff.c
+++ b/diff.c
@@ -2489,22 +2489,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)
 	}
 }
 
-struct diffstat_t {
-	int nr;
-	int alloc;
-	struct diffstat_file {
-		char *from_name;
-		char *name;
-		char *print_name;
-		const char *comments;
-		unsigned is_unmerged:1;
-		unsigned is_binary:1;
-		unsigned is_renamed:1;
-		unsigned is_interesting:1;
-		uintmax_t added, deleted;
-	} **files;
-};
-
 static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
 					  const char *name_a,
 					  const char *name_b)
@@ -6273,12 +6257,7 @@ void diff_flush(struct diff_options *options)
 	    dirstat_by_line) {
 		struct diffstat_t diffstat;
 
-		memset(&diffstat, 0, sizeof(struct diffstat_t));
-		for (i = 0; i < q->nr; i++) {
-			struct diff_filepair *p = q->queue[i];
-			if (check_pair_status(p))
-				diff_flush_stat(p, options, &diffstat);
-		}
+		compute_diffstat(options, &diffstat, q);
 		if (output_format & DIFF_FORMAT_NUMSTAT)
 			show_numstat(&diffstat, options);
 		if (output_format & DIFF_FORMAT_DIFFSTAT)
@@ -6611,6 +6590,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)
 	return ignored;
 }
 
+void compute_diffstat(struct diff_options *options,
+		      struct diffstat_t *diffstat,
+		      struct diff_queue_struct *q)
+{
+	int i;
+
+	memset(diffstat, 0, sizeof(struct diffstat_t));
+	for (i = 0; i < q->nr; i++) {
+		struct diff_filepair *p = q->queue[i];
+		if (check_pair_status(p))
+			diff_flush_stat(p, options, diffstat);
+	}
+}
+
 void diff_addremove(struct diff_options *options,
 		    int addremove, unsigned mode,
 		    const struct object_id *oid,
diff --git a/diff.h b/diff.h
index b680b377b2..34fc658946 100644
--- a/diff.h
+++ b/diff.h
@@ -244,6 +244,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);
 void diff_emit_submodule_pipethrough(struct diff_options *o,
 				     const char *line, int len);
 
+struct diffstat_t {
+	int nr;
+	int alloc;
+	struct diffstat_file {
+		char *from_name;
+		char *name;
+		char *print_name;
+		const char *comments;
+		unsigned is_unmerged:1;
+		unsigned is_binary:1;
+		unsigned is_renamed:1;
+		unsigned is_interesting:1;
+		uintmax_t added, deleted;
+	} **files;
+};
+
 enum color_diff {
 	DIFF_RESET = 0,
 	DIFF_CONTEXT = 1,
@@ -333,6 +349,9 @@ void diff_change(struct diff_options *,
 
 struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);
 
+void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
+		      struct diff_queue_struct *q);
+
 #define DIFF_SETUP_REVERSE      	1
 #define DIFF_SETUP_USE_SIZE_CACHE	4
 
-- 
gitgitgadget


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

* [PATCH v3 03/11] built-in add -i: implement the `status` command
  2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  2019-07-16 14:58     ` [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
  2019-07-16 14:58     ` [PATCH v3 02/11] diff: export diffstat interface Daniel Ferreira via GitGitGadget
@ 2019-07-16 14:58     ` Daniel Ferreira via GitGitGadget
  2019-07-31 18:12       ` Junio C Hamano
  2019-07-16 14:58     ` [PATCH v3 04/11] built-in add -i: refresh the index before running `status` Johannes Schindelin via GitGitGadget
                       ` (9 subsequent siblings)
  12 siblings, 1 reply; 124+ messages in thread
From: Daniel Ferreira via GitGitGadget @ 2019-07-16 14:58 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Daniel Ferreira

From: Daniel Ferreira <bnmvco@gmail.com>

This implements the `status` command of `git add -i`. The data
structures introduced in this commit will be extended as needed later.

At this point, we re-implement only part of the `list_and_choose()`
function of the Perl script `git-add--interactive.perl` and call it
`list()`. It does not yet color anything, or do columns, or allow user
input.

Over the course of the next commits, we will introduce a
`list_and_choose()` function that uses `list()` to display the list of
options and let the user choose one or more of the displayed items. This
will be used to implement the main loop of the built-in `git add -i`, at
which point the new `status` command can actually be used.

Note that we pass the list of items as a `struct item **` as opposed to
a `struct item *`, to allow for the actual items to contain much more
information than merely the name.

Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 265 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 264 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index 482e458dc6..59b28011f7 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -1,7 +1,270 @@
 #include "cache.h"
 #include "add-interactive.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "refs.h"
+
+struct item {
+	const char *name;
+};
+
+struct list_options {
+	const char *header;
+	void (*print_item)(int i, struct item *item, void *print_item_data);
+	void *print_item_data;
+};
+
+static void list(struct item **list, size_t nr, struct list_options *opts)
+{
+	int i;
+
+	if (!nr)
+		return;
+
+	if (opts->header)
+		printf("%s\n", opts->header);
+
+	for (i = 0; i < nr; i++) {
+		opts->print_item(i, list[i], opts->print_item_data);
+		putchar('\n');
+	}
+}
+
+struct adddel {
+	uintmax_t add, del;
+	unsigned seen:1, binary:1;
+};
+
+struct file_list {
+	struct file_item {
+		struct item item;
+		struct adddel index, worktree;
+	} **file;
+	size_t nr, alloc;
+};
+
+static void add_file_item(struct file_list *list, const char *name)
+{
+	struct file_item *item;
+
+	FLEXPTR_ALLOC_STR(item, item.name, name);
+
+	ALLOC_GROW(list->file, list->nr + 1, list->alloc);
+	list->file[list->nr++] = item;
+}
+
+static void reset_file_list(struct file_list *list)
+{
+	size_t i;
+
+	for (i = 0; i < list->nr; i++)
+		free(list->file[i]);
+	list->nr = 0;
+}
+
+static void release_file_list(struct file_list *list)
+{
+	reset_file_list(list);
+	FREE_AND_NULL(list->file);
+	list->alloc = 0;
+}
+
+static int file_item_cmp(const void *a, const void *b)
+{
+	const struct file_item * const *f1 = a;
+	const struct file_item * const *f2 = b;
+
+	return strcmp((*f1)->item.name, (*f2)->item.name);
+}
+
+struct pathname_entry {
+	struct hashmap_entry ent;
+	size_t index;
+	char pathname[FLEX_ARRAY];
+};
+
+static int pathname_entry_cmp(const void *unused_cmp_data,
+			      const void *entry, const void *entry_or_key,
+			      const void *pathname)
+{
+	const struct pathname_entry *e1 = entry, *e2 = entry_or_key;
+
+	return strcmp(e1->pathname,
+		      pathname ? (const char *)pathname : e2->pathname);
+}
+
+struct collection_status {
+	enum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase;
+
+	const char *reference;
+
+	struct file_list *list;
+	struct hashmap file_map;
+};
+
+static void collect_changes_cb(struct diff_queue_struct *q,
+			       struct diff_options *options,
+			       void *data)
+{
+	struct collection_status *s = data;
+	struct diffstat_t stat = { 0 };
+	int i;
+
+	if (!q->nr)
+		return;
+
+	compute_diffstat(options, &stat, q);
+
+	for (i = 0; i < stat.nr; i++) {
+		const char *name = stat.files[i]->name;
+		int hash = strhash(name);
+		struct pathname_entry *entry;
+		size_t file_index;
+		struct file_item *file;
+		struct adddel *adddel;
+
+		entry = hashmap_get_from_hash(&s->file_map, hash, name);
+		if (entry)
+			file_index = entry->index;
+		else {
+			FLEX_ALLOC_STR(entry, pathname, name);
+			hashmap_entry_init(entry, hash);
+			entry->index = file_index = s->list->nr;
+			hashmap_add(&s->file_map, entry);
+
+			add_file_item(s->list, name);
+		}
+		file = s->list->file[file_index];
+
+		adddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;
+		adddel->seen = 1;
+		adddel->add = stat.files[i]->added;
+		adddel->del = stat.files[i]->deleted;
+		if (stat.files[i]->is_binary)
+			adddel->binary = 1;
+	}
+}
+
+static int get_modified_files(struct repository *r, struct file_list *list,
+			      const struct pathspec *ps)
+{
+	struct object_id head_oid;
+	int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+					     &head_oid, NULL);
+	struct collection_status s = { FROM_WORKTREE };
+
+	if (repo_read_index_preload(r, ps, 0) < 0)
+		return error(_("could not read index"));
+
+	s.list = list;
+	hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);
+
+	for (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {
+		struct rev_info rev;
+		struct setup_revision_opt opt = { 0 };
+
+		opt.def = is_initial ?
+			empty_tree_oid_hex() : oid_to_hex(&head_oid);
+
+		init_revisions(&rev, NULL);
+		setup_revisions(0, NULL, &rev, &opt);
+
+		rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+		rev.diffopt.format_callback = collect_changes_cb;
+		rev.diffopt.format_callback_data = &s;
+
+		if (ps)
+			copy_pathspec(&rev.prune_data, ps);
+
+		if (s.phase == FROM_INDEX)
+			run_diff_index(&rev, 1);
+		else {
+			rev.diffopt.flags.ignore_dirty_submodules = 1;
+			run_diff_files(&rev, 0);
+		}
+	}
+	hashmap_free(&s.file_map, 1);
+
+	/* While the diffs are ordered already, we ran *two* diffs... */
+	QSORT(list->file, list->nr, file_item_cmp);
+
+	return 0;
+}
+
+static void populate_wi_changes(struct strbuf *buf,
+				struct adddel *ad, const char *no_changes)
+{
+	if (ad->binary)
+		strbuf_addstr(buf, _("binary"));
+	else if (ad->seen)
+		strbuf_addf(buf, "+%"PRIuMAX"/-%"PRIuMAX,
+			    (uintmax_t)ad->add, (uintmax_t)ad->del);
+	else
+		strbuf_addstr(buf, no_changes);
+}
+
+struct print_file_item_data {
+	const char *modified_fmt;
+	struct strbuf buf, index, worktree;
+};
+
+static void print_file_item(int i, struct item *item,
+			    void *print_file_item_data)
+{
+	struct file_item *c = (struct file_item *)item;
+	struct print_file_item_data *d = print_file_item_data;
+
+	strbuf_reset(&d->index);
+	strbuf_reset(&d->worktree);
+	strbuf_reset(&d->buf);
+
+	populate_wi_changes(&d->worktree, &c->worktree, _("nothing"));
+	populate_wi_changes(&d->index, &c->index, _("unchanged"));
+	strbuf_addf(&d->buf, d->modified_fmt,
+		    d->index.buf, d->worktree.buf, item->name);
+
+	printf(" %2d: %s", i + 1, d->buf.buf);
+}
+
+static int run_status(struct repository *r, const struct pathspec *ps,
+		      struct file_list *files, struct list_options *opts)
+{
+	reset_file_list(files);
+
+	if (get_modified_files(r, files, ps) < 0)
+		return -1;
+
+	if (files->nr)
+		list((struct item **)files->file, files->nr, opts);
+	putchar('\n');
+
+	return 0;
+}
 
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
-	die(_("No commands are available in the built-in `git add -i` yet!"));
+	struct print_file_item_data print_file_item_data = {
+		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+	};
+	struct list_options opts = {
+		NULL, print_file_item, &print_file_item_data
+	};
+	struct strbuf header = STRBUF_INIT;
+	struct file_list files = { NULL };
+	int res = 0;
+
+	strbuf_addstr(&header, "      ");
+	strbuf_addf(&header, print_file_item_data.modified_fmt,
+		    _("staged"), _("unstaged"), _("path"));
+	opts.header = header.buf;
+
+	res = run_status(r, ps, &files, &opts);
+
+	release_file_list(&files);
+	strbuf_release(&print_file_item_data.buf);
+	strbuf_release(&print_file_item_data.index);
+	strbuf_release(&print_file_item_data.worktree);
+	strbuf_release(&header);
+
+	return res;
 }
-- 
gitgitgadget


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

* [PATCH v3 04/11] built-in add -i: refresh the index before running `status`
  2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                       ` (2 preceding siblings ...)
  2019-07-16 14:58     ` [PATCH v3 03/11] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
@ 2019-07-16 14:58     ` Johannes Schindelin via GitGitGadget
  2019-07-16 14:58     ` [PATCH v3 05/11] built-in add -i: color the header in the `status` command Johannes Schindelin via GitGitGadget
                       ` (8 subsequent siblings)
  12 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-07-16 14:58 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

This is what the Perl version does, and therefore it is what the
built-in version should do, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c |  4 +++-
 repository.c      | 19 +++++++++++++++++++
 repository.h      |  7 +++++++
 3 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index 59b28011f7..2dbf29dee2 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -258,7 +258,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 		    _("staged"), _("unstaged"), _("path"));
 	opts.header = header.buf;
 
-	res = run_status(r, ps, &files, &opts);
+	repo_refresh_and_write_index(r, REFRESH_QUIET, 1);
+	if (run_status(r, ps, &files, &opts) < 0)
+		res = -1;
 
 	release_file_list(&files);
 	strbuf_release(&print_file_item_data.buf);
diff --git a/repository.c b/repository.c
index 682c239fe3..def35c40fc 100644
--- a/repository.c
+++ b/repository.c
@@ -275,3 +275,22 @@ int repo_hold_locked_index(struct repository *repo,
 		BUG("the repo hasn't been setup");
 	return hold_lock_file_for_update(lf, repo->index_file, flags);
 }
+
+int repo_refresh_and_write_index(struct repository *r,
+				 unsigned int flags, int gentle)
+{
+	struct lock_file lock_file = LOCK_INIT;
+	int fd;
+
+	if (repo_read_index_preload(r, NULL, 0) < 0)
+		return error(_("could not read index"));
+	fd = repo_hold_locked_index(r, &lock_file, 0);
+	if (!gentle && fd < 0)
+		return error(_("could not lock index for writing"));
+	refresh_index(r->index, flags, NULL, NULL, NULL);
+	if (0 <= fd)
+		repo_update_index_if_able(r, &lock_file);
+	rollback_lock_file(&lock_file);
+
+	return 0;
+}
diff --git a/repository.h b/repository.h
index 4fb6a5885f..cf5d5bab48 100644
--- a/repository.h
+++ b/repository.h
@@ -157,5 +157,12 @@ int repo_read_index_unmerged(struct repository *);
  */
 void repo_update_index_if_able(struct repository *, struct lock_file *);
 
+/*
+ * Refresh the index and write it out. If the index file could not be
+ * locked, error out, except in gentle mode. The flags will be passed
+ * through to refresh_index().
+ */
+int repo_refresh_and_write_index(struct repository *r,
+				 unsigned int flags, int gentle);
 
 #endif /* REPOSITORY_H */
-- 
gitgitgadget


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

* [PATCH v3 05/11] built-in add -i: color the header in the `status` command
  2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                       ` (3 preceding siblings ...)
  2019-07-16 14:58     ` [PATCH v3 04/11] built-in add -i: refresh the index before running `status` Johannes Schindelin via GitGitGadget
@ 2019-07-16 14:58     ` Johannes Schindelin via GitGitGadget
  2019-07-16 14:58     ` [PATCH v3 06/11] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
                       ` (7 subsequent siblings)
  12 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-07-16 14:58 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

For simplicity, we only implemented the `status` command without colors.
This patch starts adding color, matching what the Perl script
`git-add--interactive.perl` does.

Original-Patch-By: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 60 ++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 54 insertions(+), 6 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 2dbf29dee2..6c2fca12c1 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -1,9 +1,51 @@
 #include "cache.h"
 #include "add-interactive.h"
+#include "color.h"
+#include "config.h"
 #include "diffcore.h"
 #include "revision.h"
 #include "refs.h"
 
+struct add_i_state {
+	struct repository *r;
+	int use_color;
+	char header_color[COLOR_MAXLEN];
+};
+
+static void init_color(struct repository *r, struct add_i_state *s,
+		       const char *slot_name, char *dst,
+		       const char *default_color)
+{
+	char *key = xstrfmt("color.interactive.%s", slot_name);
+	const char *value;
+
+	if (!s->use_color)
+		dst[0] = '\0';
+	else if (repo_config_get_value(r, key, &value) ||
+		 color_parse(value, dst))
+		strlcpy(dst, default_color, COLOR_MAXLEN);
+
+	free(key);
+}
+
+static int init_add_i_state(struct repository *r, struct add_i_state *s)
+{
+	const char *value;
+
+	s->r = r;
+
+	if (repo_config_get_value(r, "color.interactive", &value))
+		s->use_color = -1;
+	else
+		s->use_color =
+			git_config_colorbool("color.interactive", value);
+	s->use_color = want_color(s->use_color);
+
+	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
+
+	return 0;
+}
+
 struct item {
 	const char *name;
 };
@@ -14,7 +56,8 @@ struct list_options {
 	void *print_item_data;
 };
 
-static void list(struct item **list, size_t nr, struct list_options *opts)
+static void list(struct item **list, size_t nr,
+		 struct add_i_state *s, struct list_options *opts)
 {
 	int i;
 
@@ -22,7 +65,8 @@ static void list(struct item **list, size_t nr, struct list_options *opts)
 		return;
 
 	if (opts->header)
-		printf("%s\n", opts->header);
+		color_fprintf_ln(stdout, s->header_color,
+				 "%s", opts->header);
 
 	for (i = 0; i < nr; i++) {
 		opts->print_item(i, list[i], opts->print_item_data);
@@ -226,16 +270,16 @@ static void print_file_item(int i, struct item *item,
 	printf(" %2d: %s", i + 1, d->buf.buf);
 }
 
-static int run_status(struct repository *r, const struct pathspec *ps,
+static int run_status(struct add_i_state *s, const struct pathspec *ps,
 		      struct file_list *files, struct list_options *opts)
 {
 	reset_file_list(files);
 
-	if (get_modified_files(r, files, ps) < 0)
+	if (get_modified_files(s->r, files, ps) < 0)
 		return -1;
 
 	if (files->nr)
-		list((struct item **)files->file, files->nr, opts);
+		list((struct item **)files->file, files->nr, s, opts);
 	putchar('\n');
 
 	return 0;
@@ -243,6 +287,7 @@ static int run_status(struct repository *r, const struct pathspec *ps,
 
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
+	struct add_i_state s = { NULL };
 	struct print_file_item_data print_file_item_data = {
 		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 	};
@@ -253,13 +298,16 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	struct file_list files = { NULL };
 	int res = 0;
 
+	if (init_add_i_state(r, &s))
+		return error("could not parse `add -i` config");
+
 	strbuf_addstr(&header, "      ");
 	strbuf_addf(&header, print_file_item_data.modified_fmt,
 		    _("staged"), _("unstaged"), _("path"));
 	opts.header = header.buf;
 
 	repo_refresh_and_write_index(r, REFRESH_QUIET, 1);
-	if (run_status(r, ps, &files, &opts) < 0)
+	if (run_status(&s, ps, &files, &opts) < 0)
 		res = -1;
 
 	release_file_list(&files);
-- 
gitgitgadget


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

* [PATCH v3 06/11] built-in add -i: implement the main loop
  2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                       ` (4 preceding siblings ...)
  2019-07-16 14:58     ` [PATCH v3 05/11] built-in add -i: color the header in the `status` command Johannes Schindelin via GitGitGadget
@ 2019-07-16 14:58     ` Johannes Schindelin via GitGitGadget
  2019-07-31 18:14       ` Junio C Hamano
  2019-07-16 14:58     ` [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings Slavica Djukic via GitGitGadget
                       ` (6 subsequent siblings)
  12 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-07-16 14:58 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

The reason why we did not start with the main loop to begin with is that
it is the first user of `list_and_choose()`, which uses the `list()`
function that we conveniently introduced for use by the `status`
command.

Apart from the "and choose" part, there are more differences between the
way the `status` command calls the `list_and_choose()` function in the
Perl version of `git add -i` compared to the other callers of said
function. The most important ones:

- The list is not only shown, but the user is also asked to make a
  choice, possibly selecting multiple entries.

- The list of items is prefixed with a marker indicating what items have
  been selected, if multi-selection is allowed.

- Initially, for each item a unique prefix (if there exists any within
  the given parameters) is determined, and shown in the list, and
  accepted as a shortcut for the selection.

These features will be implemented later, except the part where the user
can choose a command. At this stage, though, the built-in `git add -i`
still only supports the `status` command, with the remaining commands to
follow over the course of the next commits.

In addition, we also modify `list()` to support displaying the commands
in columns, even if there is currently only one.

The Perl script `git-add--interactive.perl` mixed the purposes of the
"list" and the "and choose" part into the same function. In the C
version, we will keep them separate instead, calling the `list()`
function from the `list_and_choose()` function.

Note that we only have a prompt ending in a single ">" at this stage;
later commits will add commands that display a double ">>" to indicate
that the user is in a different loop than the main one.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 129 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 127 insertions(+), 2 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 6c2fca12c1..0ea4f3edb8 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -51,6 +51,7 @@ struct item {
 };
 
 struct list_options {
+	int columns;
 	const char *header;
 	void (*print_item)(int i, struct item *item, void *print_item_data);
 	void *print_item_data;
@@ -59,7 +60,7 @@ struct list_options {
 static void list(struct item **list, size_t nr,
 		 struct add_i_state *s, struct list_options *opts)
 {
-	int i;
+	int i, last_lf = 0;
 
 	if (!nr)
 		return;
@@ -70,8 +71,97 @@ static void list(struct item **list, size_t nr,
 
 	for (i = 0; i < nr; i++) {
 		opts->print_item(i, list[i], opts->print_item_data);
+
+		if ((opts->columns) && ((i + 1) % (opts->columns))) {
+			putchar('\t');
+			last_lf = 0;
+		}
+		else {
+			putchar('\n');
+			last_lf = 1;
+		}
+	}
+
+	if (!last_lf)
 		putchar('\n');
+}
+struct list_and_choose_options {
+	struct list_options list_opts;
+
+	const char *prompt;
+};
+
+#define LIST_AND_CHOOSE_ERROR (-1)
+#define LIST_AND_CHOOSE_QUIT  (-2)
+
+/*
+ * Returns the selected index.
+ *
+ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
+ * `LIST_AND_CHOOSE_QUIT` is returned.
+ */
+static ssize_t list_and_choose(struct item **items, size_t nr,
+			       struct add_i_state *s,
+			       struct list_and_choose_options *opts)
+{
+	struct strbuf input = STRBUF_INIT;
+	ssize_t res = LIST_AND_CHOOSE_ERROR;
+
+	for (;;) {
+		char *p, *endp;
+
+		strbuf_reset(&input);
+
+		list(items, nr, s, &opts->list_opts);
+
+		printf("%s%s", opts->prompt, "> ");
+		fflush(stdout);
+
+		if (strbuf_getline(&input, stdin) == EOF) {
+			putchar('\n');
+			res = LIST_AND_CHOOSE_QUIT;
+			break;
+		}
+		strbuf_trim(&input);
+
+		if (!input.len)
+			break;
+
+		p = input.buf;
+		for (;;) {
+			size_t sep = strcspn(p, " \t\r\n,");
+			ssize_t index = -1;
+
+			if (!sep) {
+				if (!*p)
+					break;
+				p++;
+				continue;
+			}
+
+			if (isdigit(*p)) {
+				index = strtoul(p, &endp, 10) - 1;
+				if (endp != p + sep)
+					index = -1;
+			}
+
+			p[sep] = '\0';
+			if (index < 0 || index >= nr)
+				printf(_("Huh (%s)?\n"), p);
+			else {
+				res = index;
+				break;
+			}
+
+			p += sep + 1;
+		}
+
+		if (res != LIST_AND_CHOOSE_ERROR)
+			break;
 	}
+
+	strbuf_release(&input);
+	return res;
 }
 
 struct adddel {
@@ -285,17 +375,40 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	return 0;
 }
 
+static void print_command_item(int i, struct item *item,
+			       void *print_command_item_data)
+{
+	printf(" %2d: %s", i + 1, item->name);
+}
+
+struct command_item {
+	struct item item;
+	int (*command)(struct add_i_state *s, const struct pathspec *ps,
+		       struct file_list *files, struct list_options *opts);
+};
+
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
+	struct list_and_choose_options main_loop_opts = {
+		{ 4, N_("*** Commands ***"), print_command_item, NULL },
+		N_("What now")
+	};
+	struct command_item
+		status = { { "status" }, run_status };
+	struct command_item *commands[] = {
+		&status
+	};
+
 	struct print_file_item_data print_file_item_data = {
 		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 	};
 	struct list_options opts = {
-		NULL, print_file_item, &print_file_item_data
+		0, NULL, print_file_item, &print_file_item_data
 	};
 	struct strbuf header = STRBUF_INIT;
 	struct file_list files = { NULL };
+	ssize_t i;
 	int res = 0;
 
 	if (init_add_i_state(r, &s))
@@ -310,6 +423,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	if (run_status(&s, ps, &files, &opts) < 0)
 		res = -1;
 
+	for (;;) {
+		i = list_and_choose((struct item **)commands,
+				    ARRAY_SIZE(commands), &s, &main_loop_opts);
+		if (i == LIST_AND_CHOOSE_QUIT) {
+			printf(_("Bye.\n"));
+			res = 0;
+			break;
+		}
+		if (i != LIST_AND_CHOOSE_ERROR)
+			res = commands[i]->command(&s, ps, &files, &opts);
+	}
+
 	release_file_list(&files);
 	strbuf_release(&print_file_item_data.buf);
 	strbuf_release(&print_file_item_data.index);
-- 
gitgitgadget


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

* [PATCH v3 08/11] built-in add -i: show unique prefixes of the commands
  2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                       ` (6 preceding siblings ...)
  2019-07-16 14:58     ` [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings Slavica Djukic via GitGitGadget
@ 2019-07-16 14:58     ` Slavica Djukic via GitGitGadget
  2019-07-16 14:58     ` [PATCH v3 09/11] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
                       ` (4 subsequent siblings)
  12 siblings, 0 replies; 124+ messages in thread
From: Slavica Djukic via GitGitGadget @ 2019-07-16 14:58 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Slavica Djukic

From: Slavica Djukic <slawica92@hotmail.com>

Just like in the Perl script `git-add--interactive.perl`, for each
command a unique prefix is determined (if there exists any within the
given parameters), and shown in the list, and accepted as a shortcut for
the command.

We use the prefix map implementation that we just added in the previous
commit for that purpose.

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
---
 add-interactive.c | 69 ++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 56 insertions(+), 13 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 0ea4f3edb8..7ff87bae1b 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -5,6 +5,7 @@
 #include "diffcore.h"
 #include "revision.h"
 #include "refs.h"
+#include "prefix-map.h"
 
 struct add_i_state {
 	struct repository *r;
@@ -46,18 +47,32 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)
 	return 0;
 }
 
-struct item {
-	const char *name;
-};
+static ssize_t find_unique(const char *string,
+			   struct prefix_item **list, size_t nr)
+{
+	ssize_t found = -1, i;
+
+	for (i = 0; i < nr; i++) {
+		struct prefix_item *item = list[i];
+		if (!starts_with(item->name, string))
+			continue;
+		if (found >= 0)
+			return -1;
+		found = i;
+	}
+
+	return found;
+}
 
 struct list_options {
 	int columns;
 	const char *header;
-	void (*print_item)(int i, struct item *item, void *print_item_data);
+	void (*print_item)(int i, struct prefix_item *item,
+			   void *print_item_data);
 	void *print_item_data;
 };
 
-static void list(struct item **list, size_t nr,
+static void list(struct prefix_item **list, size_t nr,
 		 struct add_i_state *s, struct list_options *opts)
 {
 	int i, last_lf = 0;
@@ -100,13 +115,15 @@ struct list_and_choose_options {
  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
  * `LIST_AND_CHOOSE_QUIT` is returned.
  */
-static ssize_t list_and_choose(struct item **items, size_t nr,
+static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 			       struct add_i_state *s,
 			       struct list_and_choose_options *opts)
 {
 	struct strbuf input = STRBUF_INIT;
 	ssize_t res = LIST_AND_CHOOSE_ERROR;
 
+	find_unique_prefixes(items, nr, 1, 4);
+
 	for (;;) {
 		char *p, *endp;
 
@@ -146,6 +163,9 @@ static ssize_t list_and_choose(struct item **items, size_t nr,
 			}
 
 			p[sep] = '\0';
+			if (index < 0)
+				index = find_unique(p, items, nr);
+
 			if (index < 0 || index >= nr)
 				printf(_("Huh (%s)?\n"), p);
 			else {
@@ -171,7 +191,7 @@ struct adddel {
 
 struct file_list {
 	struct file_item {
-		struct item item;
+		struct prefix_item item;
 		struct adddel index, worktree;
 	} **file;
 	size_t nr, alloc;
@@ -337,12 +357,29 @@ static void populate_wi_changes(struct strbuf *buf,
 		strbuf_addstr(buf, no_changes);
 }
 
+/* filters out prefixes which have special meaning to list_and_choose() */
+static int is_valid_prefix(const char *prefix, size_t prefix_len)
+{
+	return prefix_len && prefix &&
+		/*
+		 * We expect `prefix` to be NUL terminated, therefore this
+		 * `strcspn()` call is okay, even if it might do much more
+		 * work than strictly necessary.
+		 */
+		strcspn(prefix, " \t\r\n,") >= prefix_len &&	/* separators */
+		*prefix != '-' &&				/* deselection */
+		!isdigit(*prefix) &&				/* selection */
+		(prefix_len != 1 ||
+		 (*prefix != '*' &&				/* "all" wildcard */
+		  *prefix != '?'));				/* prompt help */
+}
+
 struct print_file_item_data {
 	const char *modified_fmt;
 	struct strbuf buf, index, worktree;
 };
 
-static void print_file_item(int i, struct item *item,
+static void print_file_item(int i, struct prefix_item *item,
 			    void *print_file_item_data)
 {
 	struct file_item *c = (struct file_item *)item;
@@ -369,20 +406,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 		return -1;
 
 	if (files->nr)
-		list((struct item **)files->file, files->nr, s, opts);
+		list((struct prefix_item **)files->file, files->nr, s, opts);
 	putchar('\n');
 
 	return 0;
 }
 
-static void print_command_item(int i, struct item *item,
+static void print_command_item(int i, struct prefix_item *item,
 			       void *print_command_item_data)
 {
-	printf(" %2d: %s", i + 1, item->name);
+	if (!item->prefix_length ||
+	    !is_valid_prefix(item->name, item->prefix_length))
+		printf(" %2d: %s", i + 1, item->name);
+	else
+		printf(" %3d: [%.*s]%s", i + 1,
+		       (int)item->prefix_length, item->name,
+		       item->name + item->prefix_length);
 }
 
 struct command_item {
-	struct item item;
+	struct prefix_item item;
 	int (*command)(struct add_i_state *s, const struct pathspec *ps,
 		       struct file_list *files, struct list_options *opts);
 };
@@ -424,7 +467,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 		res = -1;
 
 	for (;;) {
-		i = list_and_choose((struct item **)commands,
+		i = list_and_choose((struct prefix_item **)commands,
 				    ARRAY_SIZE(commands), &s, &main_loop_opts);
 		if (i == LIST_AND_CHOOSE_QUIT) {
 			printf(_("Bye.\n"));
-- 
gitgitgadget


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

* [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings
  2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                       ` (5 preceding siblings ...)
  2019-07-16 14:58     ` [PATCH v3 06/11] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
@ 2019-07-16 14:58     ` Slavica Djukic via GitGitGadget
  2019-07-31 18:39       ` Junio C Hamano
  2019-08-24 12:38       ` SZEDER Gábor
  2019-07-16 14:58     ` [PATCH v3 08/11] built-in add -i: show unique prefixes of the commands Slavica Djukic via GitGitGadget
                       ` (5 subsequent siblings)
  12 siblings, 2 replies; 124+ messages in thread
From: Slavica Djukic via GitGitGadget @ 2019-07-16 14:58 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Slavica Djukic

From: Slavica Djukic <slawica92@hotmail.com>

In the `git add -i` command, we show unique prefixes of the commands and
files, to give an indication what prefix would select them.

Naturally, the C implementation looks a lot different than the Perl
implementation: in Perl, a trie is much easier implemented, while we
already have a pretty neat hashmap implementation in C that we use for
the purpose of storing (not necessarily unique) prefixes.

The idea: for each item that we add, we generate prefixes starting with
the first letter, then the first two letters, then three, etc, until we
find a prefix that is unique (or until the prefix length would be
longer than we want). If we encounter a previously-unique prefix on the
way, we adjust that item's prefix to make it unique again (or we mark it
as having no unique prefix if we failed to find one). These partial
prefixes are stored in a hash map (for quick lookup times).

To make sure that this function works as expected, we add a test using a
special-purpose test helper that was added for that purpose.

Note: We expect the list of prefix items to be passed in as a list of
pointers rather than as regular list to avoid having to copy information
(the actual items will most likely contain more information than just
the name and the length of the unique prefix, but passing in `struct
prefix_item *` would not allow for that).

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile                   |   2 +
 prefix-map.c               | 109 +++++++++++++++++++++++++++++++++++++
 prefix-map.h               |  40 ++++++++++++++
 t/helper/test-prefix-map.c |  58 ++++++++++++++++++++
 t/helper/test-tool.c       |   1 +
 t/helper/test-tool.h       |   1 +
 t/t0018-prefix-map.sh      |  10 ++++
 7 files changed, 221 insertions(+)
 create mode 100644 prefix-map.c
 create mode 100644 prefix-map.h
 create mode 100644 t/helper/test-prefix-map.c
 create mode 100755 t/t0018-prefix-map.sh

diff --git a/Makefile b/Makefile
index 0a47200f47..545a0e8743 100644
--- a/Makefile
+++ b/Makefile
@@ -725,6 +725,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o
 TEST_BUILTINS_OBJS += test-parse-options.o
 TEST_BUILTINS_OBJS += test-path-utils.o
 TEST_BUILTINS_OBJS += test-pkt-line.o
+TEST_BUILTINS_OBJS += test-prefix-map.o
 TEST_BUILTINS_OBJS += test-prio-queue.o
 TEST_BUILTINS_OBJS += test-reach.o
 TEST_BUILTINS_OBJS += test-read-cache.o
@@ -943,6 +944,7 @@ LIB_OBJS += patch-ids.o
 LIB_OBJS += path.o
 LIB_OBJS += pathspec.o
 LIB_OBJS += pkt-line.o
+LIB_OBJS += prefix-map.o
 LIB_OBJS += preload-index.o
 LIB_OBJS += pretty.o
 LIB_OBJS += prio-queue.o
diff --git a/prefix-map.c b/prefix-map.c
new file mode 100644
index 0000000000..747ddb4ebc
--- /dev/null
+++ b/prefix-map.c
@@ -0,0 +1,109 @@
+#include "cache.h"
+#include "prefix-map.h"
+
+static int map_cmp(const void *unused_cmp_data,
+		   const void *entry,
+		   const void *entry_or_key,
+		   const void *unused_keydata)
+{
+	const struct prefix_map_entry *a = entry;
+	const struct prefix_map_entry *b = entry_or_key;
+
+	return a->prefix_length != b->prefix_length ||
+		strncmp(a->name, b->name, a->prefix_length);
+}
+
+static void add_prefix_entry(struct hashmap *map, const char *name,
+			     size_t prefix_length, struct prefix_item *item)
+{
+	struct prefix_map_entry *result = xmalloc(sizeof(*result));
+	result->name = name;
+	result->prefix_length = prefix_length;
+	result->item = item;
+	hashmap_entry_init(result, memhash(name, prefix_length));
+	hashmap_add(map, result);
+}
+
+static void init_prefix_map(struct prefix_map *prefix_map,
+			    int min_prefix_length, int max_prefix_length)
+{
+	hashmap_init(&prefix_map->map, map_cmp, NULL, 0);
+	prefix_map->min_length = min_prefix_length;
+	prefix_map->max_length = max_prefix_length;
+}
+
+static void add_prefix_item(struct prefix_map *prefix_map,
+			    struct prefix_item *item)
+{
+	struct prefix_map_entry e = { { NULL } }, *e2;
+	int j;
+
+	e.item = item;
+	e.name = item->name;
+
+	for (j = prefix_map->min_length;
+	     j <= prefix_map->max_length && e.name[j]; j++) {
+		/* Avoid breaking UTF-8 multi-byte sequences */
+		if (!isascii(e.name[j]))
+			break;
+
+		e.prefix_length = j;
+		hashmap_entry_init(&e, memhash(e.name, j));
+		e2 = hashmap_get(&prefix_map->map, &e, NULL);
+		if (!e2) {
+			/* prefix is unique at this stage */
+			item->prefix_length = j;
+			add_prefix_entry(&prefix_map->map, e.name, j, item);
+			break;
+		}
+
+		if (!e2->item)
+			continue; /* non-unique prefix */
+
+		if (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))
+			BUG("unexpected prefix length: %d != %d (%s != %s)",
+			    j, (int)e2->item->prefix_length, e.name, e2->name);
+
+		/* skip common prefix */
+		for (; j < prefix_map->max_length && e.name[j]; j++) {
+			if (e.item->name[j] != e2->item->name[j])
+				break;
+			add_prefix_entry(&prefix_map->map, e.name, j + 1,
+					 NULL);
+		}
+
+		/* e2 no longer refers to a unique prefix */
+		if (j < prefix_map->max_length && e2->name[j]) {
+			/* found a new unique prefix for e2's item */
+			e2->item->prefix_length = j + 1;
+			add_prefix_entry(&prefix_map->map, e2->name, j + 1,
+					 e2->item);
+		}
+		else
+			e2->item->prefix_length = 0;
+		e2->item = NULL;
+
+		if (j < prefix_map->max_length && e.name[j]) {
+			/* found a unique prefix for the item */
+			e.item->prefix_length = j + 1;
+			add_prefix_entry(&prefix_map->map, e.name, j + 1,
+					 e.item);
+		} else
+			/* item has no (short enough) unique prefix */
+			e.item->prefix_length = 0;
+
+		break;
+	}
+}
+
+void find_unique_prefixes(struct prefix_item **list, size_t nr,
+			  int min_length, int max_length)
+{
+	int i;
+	struct prefix_map prefix_map;
+
+	init_prefix_map(&prefix_map, min_length, max_length);
+	for (i = 0; i < nr; i++)
+		add_prefix_item(&prefix_map, list[i]);
+	hashmap_free(&prefix_map.map, 1);
+}
diff --git a/prefix-map.h b/prefix-map.h
new file mode 100644
index 0000000000..ce3b8a4a32
--- /dev/null
+++ b/prefix-map.h
@@ -0,0 +1,40 @@
+#ifndef PREFIX_MAP_H
+#define PREFIX_MAP_H
+
+#include "hashmap.h"
+
+struct prefix_item {
+	const char *name;
+	size_t prefix_length;
+};
+
+struct prefix_map_entry {
+	struct hashmap_entry e;
+	const char *name;
+	size_t prefix_length;
+	/* if item is NULL, the prefix is not unique */
+	struct prefix_item *item;
+};
+
+struct prefix_map {
+	struct hashmap map;
+	int min_length, max_length;
+};
+
+/*
+ * Find unique prefixes in a given list of strings.
+ *
+ * Typically, the `struct prefix_item` information will be but a field in the
+ * actual item struct; For this reason, the `list` parameter is specified as a
+ * list of pointers to the items.
+ *
+ * The `min_length`/`max_length` parameters define what length the unique
+ * prefixes should have.
+ *
+ * If no unique prefix could be found for a given item, its `prefix_length`
+ * will be set to 0.
+ */
+void find_unique_prefixes(struct prefix_item **list, size_t nr,
+			  int min_length, int max_length);
+
+#endif
diff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c
new file mode 100644
index 0000000000..3f1c90eaf0
--- /dev/null
+++ b/t/helper/test-prefix-map.c
@@ -0,0 +1,58 @@
+#include "test-tool.h"
+#include "cache.h"
+#include "prefix-map.h"
+
+static size_t test_count, failed_count;
+
+static void check(int succeeded, const char *file, size_t line_no,
+		  const char *fmt, ...)
+{
+	va_list ap;
+
+	test_count++;
+	if (succeeded)
+		return;
+
+	va_start(ap, fmt);
+	fprintf(stderr, "%s:%d: ", file, (int)line_no);
+	vfprintf(stderr, fmt, ap);
+	fputc('\n', stderr);
+	va_end(ap);
+
+	failed_count++;
+}
+
+#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \
+	check(expect == actual, __FILE__, __LINE__, \
+	      "size_t's do not match: %" \
+	      PRIdMAX " != %" PRIdMAX " (%s) (%s)", \
+	      (intmax_t)expect, (intmax_t)actual, #actual, hint)
+
+int cmd__prefix_map(int argc, const char **argv)
+{
+#define NR 5
+	struct prefix_item items[NR] = {
+		{ "unique" },
+		{ "hell" },
+		{ "hello" },
+		{ "wok" },
+		{ "world" },
+	};
+	struct prefix_item *list[NR] = {
+		items, items + 1, items + 2, items + 3, items + 4
+	};
+
+	find_unique_prefixes(list, NR, 1, 3);
+
+#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \
+	EXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \
+			     list[index]->name)
+
+	EXPECT_PREFIX_LENGTH_EQUALS(1, 0);
+	EXPECT_PREFIX_LENGTH_EQUALS(0, 1);
+	EXPECT_PREFIX_LENGTH_EQUALS(0, 2);
+	EXPECT_PREFIX_LENGTH_EQUALS(3, 3);
+	EXPECT_PREFIX_LENGTH_EQUALS(3, 4);
+
+	return !!failed_count;
+}
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index 087a8c0cc9..e076771b96 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -39,6 +39,7 @@ static struct test_cmd cmds[] = {
 	{ "parse-options", cmd__parse_options },
 	{ "path-utils", cmd__path_utils },
 	{ "pkt-line", cmd__pkt_line },
+	{ "prefix-map", cmd__prefix_map },
 	{ "prio-queue", cmd__prio_queue },
 	{ "reach", cmd__reach },
 	{ "read-cache", cmd__read_cache },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index 7e703f3038..891bac703b 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -29,6 +29,7 @@ int cmd__online_cpus(int argc, const char **argv);
 int cmd__parse_options(int argc, const char **argv);
 int cmd__path_utils(int argc, const char **argv);
 int cmd__pkt_line(int argc, const char **argv);
+int cmd__prefix_map(int argc, const char **argv);
 int cmd__prio_queue(int argc, const char **argv);
 int cmd__reach(int argc, const char **argv);
 int cmd__read_cache(int argc, const char **argv);
diff --git a/t/t0018-prefix-map.sh b/t/t0018-prefix-map.sh
new file mode 100755
index 0000000000..187fa92aec
--- /dev/null
+++ b/t/t0018-prefix-map.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+test_description='basic tests for prefix map'
+. ./test-lib.sh
+
+test_expect_success 'prefix map' '
+	test-tool prefix-map
+'
+
+test_done
-- 
gitgitgadget


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

* [PATCH v3 09/11] built-in add -i: support `?` (prompt help)
  2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                       ` (7 preceding siblings ...)
  2019-07-16 14:58     ` [PATCH v3 08/11] built-in add -i: show unique prefixes of the commands Slavica Djukic via GitGitGadget
@ 2019-07-16 14:58     ` Johannes Schindelin via GitGitGadget
  2019-07-16 14:58     ` [PATCH v3 10/11] built-in add -i: use color in the main loop Slavica Djukic via GitGitGadget
                       ` (3 subsequent siblings)
  12 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-07-16 14:58 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

With this change, we print out the same colored help text that the
Perl-based `git add -i` prints in the main loop when question mark is
entered.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index 7ff87bae1b..1435c12be9 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -11,6 +11,7 @@ struct add_i_state {
 	struct repository *r;
 	int use_color;
 	char header_color[COLOR_MAXLEN];
+	char help_color[COLOR_MAXLEN];
 };
 
 static void init_color(struct repository *r, struct add_i_state *s,
@@ -43,6 +44,7 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)
 	s->use_color = want_color(s->use_color);
 
 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
+	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
 
 	return 0;
 }
@@ -104,6 +106,7 @@ struct list_and_choose_options {
 	struct list_options list_opts;
 
 	const char *prompt;
+	void (*print_help)(struct add_i_state *s);
 };
 
 #define LIST_AND_CHOOSE_ERROR (-1)
@@ -144,6 +147,11 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 		if (!input.len)
 			break;
 
+		if (!strcmp(input.buf, "?")) {
+			opts->print_help(s);
+			continue;
+		}
+
 		p = input.buf;
 		for (;;) {
 			size_t sep = strcspn(p, " \t\r\n,");
@@ -430,12 +438,24 @@ struct command_item {
 		       struct file_list *files, struct list_options *opts);
 };
 
+static void command_prompt_help(struct add_i_state *s)
+{
+	const char *help_color = s->help_color;
+	color_fprintf_ln(stdout, help_color, "%s", _("Prompt help:"));
+	color_fprintf_ln(stdout, help_color, "1          - %s",
+			 _("select a numbered item"));
+	color_fprintf_ln(stdout, help_color, "foo        - %s",
+			 _("select item based on unique prefix"));
+	color_fprintf_ln(stdout, help_color, "           - %s",
+			 _("(empty) select nothing"));
+}
+
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
 	struct list_and_choose_options main_loop_opts = {
 		{ 4, N_("*** Commands ***"), print_command_item, NULL },
-		N_("What now")
+		N_("What now"), command_prompt_help
 	};
 	struct command_item
 		status = { { "status" }, run_status };
-- 
gitgitgadget


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

* [PATCH v3 10/11] built-in add -i: use color in the main loop
  2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                       ` (8 preceding siblings ...)
  2019-07-16 14:58     ` [PATCH v3 09/11] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
@ 2019-07-16 14:58     ` Slavica Djukic via GitGitGadget
  2019-07-16 14:58     ` [PATCH v3 11/11] built-in add -i: implement the `help` command Johannes Schindelin via GitGitGadget
                       ` (2 subsequent siblings)
  12 siblings, 0 replies; 124+ messages in thread
From: Slavica Djukic via GitGitGadget @ 2019-07-16 14:58 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Slavica Djukic

From: Slavica Djukic <slawica92@hotmail.com>

The error messages as well as the unique prefixes are colored in `git
add -i` by default; We need to do the same in the built-in version.

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 37 ++++++++++++++++++++++++++++++++-----
 1 file changed, 32 insertions(+), 5 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 1435c12be9..538658dfa7 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -12,6 +12,9 @@ struct add_i_state {
 	int use_color;
 	char header_color[COLOR_MAXLEN];
 	char help_color[COLOR_MAXLEN];
+	char prompt_color[COLOR_MAXLEN];
+	char error_color[COLOR_MAXLEN];
+	char reset_color[COLOR_MAXLEN];
 };
 
 static void init_color(struct repository *r, struct add_i_state *s,
@@ -45,6 +48,9 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)
 
 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
 	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
+	init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE);
+	init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED);
+	init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET);
 
 	return 0;
 }
@@ -134,7 +140,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 
 		list(items, nr, s, &opts->list_opts);
 
-		printf("%s%s", opts->prompt, "> ");
+		color_fprintf(stdout, s->prompt_color, "%s", opts->prompt);
+		fputs("> ", stdout);
 		fflush(stdout);
 
 		if (strbuf_getline(&input, stdin) == EOF) {
@@ -175,7 +182,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 				index = find_unique(p, items, nr);
 
 			if (index < 0 || index >= nr)
-				printf(_("Huh (%s)?\n"), p);
+				color_fprintf_ln(stdout, s->error_color,
+						 _("Huh (%s)?"), p);
 			else {
 				res = index;
 				break;
@@ -420,15 +428,21 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	return 0;
 }
 
+struct print_command_item_data {
+	const char *color, *reset;
+};
+
 static void print_command_item(int i, struct prefix_item *item,
 			       void *print_command_item_data)
 {
+	struct print_command_item_data *d = print_command_item_data;
+
 	if (!item->prefix_length ||
 	    !is_valid_prefix(item->name, item->prefix_length))
 		printf(" %2d: %s", i + 1, item->name);
 	else
-		printf(" %3d: [%.*s]%s", i + 1,
-		       (int)item->prefix_length, item->name,
+		printf(" %2d: %s%.*s%s%s", i + 1,
+		       d->color, (int)item->prefix_length, item->name, d->reset,
 		       item->name + item->prefix_length);
 }
 
@@ -453,8 +467,9 @@ static void command_prompt_help(struct add_i_state *s)
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
+	struct print_command_item_data data;
 	struct list_and_choose_options main_loop_opts = {
-		{ 4, N_("*** Commands ***"), print_command_item, NULL },
+		{ 4, N_("*** Commands ***"), print_command_item, &data },
 		N_("What now"), command_prompt_help
 	};
 	struct command_item
@@ -477,6 +492,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	if (init_add_i_state(r, &s))
 		return error("could not parse `add -i` config");
 
+	/*
+	 * When color was asked for, use the prompt color for
+	 * highlighting, otherwise use square brackets.
+	 */
+	if (s.use_color) {
+		data.color = s.prompt_color;
+		data.reset = s.reset_color;
+	} else {
+		data.color = "[";
+		data.reset = "]";
+	}
+
 	strbuf_addstr(&header, "      ");
 	strbuf_addf(&header, print_file_item_data.modified_fmt,
 		    _("staged"), _("unstaged"), _("path"));
-- 
gitgitgadget


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

* [PATCH v3 11/11] built-in add -i: implement the `help` command
  2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                       ` (9 preceding siblings ...)
  2019-07-16 14:58     ` [PATCH v3 10/11] built-in add -i: use color in the main loop Slavica Djukic via GitGitGadget
@ 2019-07-16 14:58     ` Johannes Schindelin via GitGitGadget
  2019-08-02 21:04       ` Junio C Hamano
  2019-07-16 18:38     ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin
  2019-08-27 12:57     ` [PATCH v4 " Johannes Schindelin via GitGitGadget
  12 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-07-16 14:58 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

This imitates the code to show the help text from the Perl script
`git-add--interactive.perl` in the built-in version.

To make sure that it renders exactly like the Perl version of `git add
-i`, we also add a test case for that to `t3701-add-interactive.sh`.

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c          | 27 +++++++++++++++++++++++++--
 t/t3701-add-interactive.sh | 25 +++++++++++++++++++++++++
 2 files changed, 50 insertions(+), 2 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 538658dfa7..c431c72e3f 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -428,6 +428,27 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	return 0;
 }
 
+static int run_help(struct add_i_state *s, const struct pathspec *ps,
+		    struct file_list *files, struct list_options *opts)
+{
+	const char *help_color = s->help_color;
+
+	color_fprintf_ln(stdout, help_color, "status        - %s",
+			 _("show paths with changes"));
+	color_fprintf_ln(stdout, help_color, "update        - %s",
+			 _("add working tree state to the staged set of changes"));
+	color_fprintf_ln(stdout, help_color, "revert        - %s",
+			 _("revert staged set of changes back to the HEAD version"));
+	color_fprintf_ln(stdout, help_color, "patch         - %s",
+			 _("pick hunks and update selectively"));
+	color_fprintf_ln(stdout, help_color, "diff          - %s",
+			 _("view diff between HEAD and index"));
+	color_fprintf_ln(stdout, help_color, "add untracked - %s",
+			 _("add contents of untracked files to the staged set of changes"));
+
+	return 0;
+}
+
 struct print_command_item_data {
 	const char *color, *reset;
 };
@@ -473,9 +494,11 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 		N_("What now"), command_prompt_help
 	};
 	struct command_item
-		status = { { "status" }, run_status };
+		status = { { "status" }, run_status },
+		help = { { "help" }, run_help };
 	struct command_item *commands[] = {
-		&status
+		&status,
+		&help
 	};
 
 	struct print_file_item_data print_file_item_data = {
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 69991a3168..cf67756b85 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' '
 	test_write_lines a b a b a a b a b a >expect &&
 	test_cmp expect a
 '
+
+test_expect_success 'show help from add--helper' '
+	git reset --hard &&
+	cat >expect <<-EOF &&
+
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>
+	<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>
+	<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>
+	<BOLD;RED>patch         - pick hunks and update selectively<RESET>
+	<BOLD;RED>diff          - view diff between HEAD and index<RESET>
+	<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>>$SP
+	Bye.
+	EOF
+	test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&
+	test_decode_color <actual.colored >actual &&
+	test_i18ncmp expect actual
+'
+
 test_done
-- 
gitgitgadget

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

* Re: [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help  so far)
  2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                       ` (10 preceding siblings ...)
  2019-07-16 14:58     ` [PATCH v3 11/11] built-in add -i: implement the `help` command Johannes Schindelin via GitGitGadget
@ 2019-07-16 18:38     ` Johannes Schindelin
  2019-08-02 21:06       ` Junio C Hamano
  2019-08-27 12:57     ` [PATCH v4 " Johannes Schindelin via GitGitGadget
  12 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin @ 2019-07-16 18:38 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Jeff Hostetler, Jeff King, Junio C Hamano

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

Hi Junio,


On Tue, 16 Jul 2019, Johannes Schindelin via GitGitGadget wrote:

> This is the first leg on the long journey to a fully built-in git add -i
> (next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3
> [https://github.com/gitgitgadget/git/pull/172], 4
> [https://github.com/gitgitgadget/git/pull/173], 5
> [https://github.com/gitgitgadget/git/pull/174], and 6
> [https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are
> not necessarily up to date, and will be re-targeted to the appropriate
> branches in https://github.com/gitster/git as soon as Junio picks them up.
>
> This here patch series reflects the part that was submitted a couple of
> times (see https://github.com/gitgitgadget/git/pull/103) during the
> Outreachy project by Slavica Ðukic that continued the journey based on an
> initial patch series by Daniel Ferreira.
>
> It only implements the status and the help part, in the interest of making
> the review remotely more reviewable.
>
> As I am a heavy user of git add -p myself and use a patched version for
> weeks already (it is so nice to not suffer over one second startup until the
> MSYS2 Perl finally shows me anything, instead it feels instantaneous), I
> integrated these patch series into Git for Windows' master already, as an
> opt-in feature guarded by the config variable add.interactive.useBuiltin
> (and Git for Windows' installer is prepared to detect this version and offer
> the option in the graphical user interface).
>
> I had planned on submitting this before v2.22.0-rc0, but there was such a
> backlog of builds from a big pushout that I had to wait ;-)

FWIW the v2 round has not seen any objections...

Maybe pick it up in `pu` this time round?

Thanks,
Dscho

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

* Re: [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`
  2019-07-16 14:58     ` [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
@ 2019-07-31 17:52       ` Junio C Hamano
  2019-08-26 21:26         ` Johannes Schindelin
  0 siblings, 1 reply; 124+ messages in thread
From: Junio C Hamano @ 2019-07-31 17:52 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Jeff Hostetler, Jeff King, Johannes Schindelin

"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> +add.interactive.useBuiltin::

I am not sure if three-level name is a good thing to use here.

If we have end-user controllable (like branch or remote names)
unbounded number of subcommand/submode to "add", and "interactive"
is merely one of it, then three-level name is a perfect fit, but
otherwise, not.

> @@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,
>  {
>  	int status, i;
>  	struct argv_array argv = ARGV_ARRAY_INIT;
> +	int use_builtin_add_i =
> +		git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
> +	if (use_builtin_add_i < 0)
> +		git_config_get_bool("add.interactive.usebuiltin",
> +				    &use_builtin_add_i);
> +
> +	if (use_builtin_add_i == 1 && !patch_mode)
> +		return !!run_add_i(the_repository, pathspec);

I am hoping that eventually "add -p" will also be routed to the new
codepath.  Would it make sense to have "&& !patch_mode" here,
especially at this step where run_add_i() won't do anything useful
anyway yet?

> @@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb)
>  		ignore_add_errors = git_config_bool(var, value);
>  		return 0;
>  	}
> +
>  	return git_default_config(var, value, cb);
>  }

Good addition while at it.

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

* Re: [PATCH v3 02/11] diff: export diffstat interface
  2019-07-16 14:58     ` [PATCH v3 02/11] diff: export diffstat interface Daniel Ferreira via GitGitGadget
@ 2019-07-31 17:59       ` Junio C Hamano
  2019-08-27  9:22         ` Johannes Schindelin
  0 siblings, 1 reply; 124+ messages in thread
From: Junio C Hamano @ 2019-07-31 17:59 UTC (permalink / raw)
  To: Daniel Ferreira via GitGitGadget
  Cc: git, Jeff Hostetler, Jeff King, Daniel Ferreira

"Daniel Ferreira via GitGitGadget" <gitgitgadget@gmail.com> writes:

> @@ -6273,12 +6257,7 @@ void diff_flush(struct diff_options *options)
>  	    dirstat_by_line) {
>  		struct diffstat_t diffstat;
>  
> -		memset(&diffstat, 0, sizeof(struct diffstat_t));
> -		for (i = 0; i < q->nr; i++) {
> -			struct diff_filepair *p = q->queue[i];
> -			if (check_pair_status(p))
> -				diff_flush_stat(p, options, &diffstat);
> -		}
> +		compute_diffstat(options, &diffstat, q);
>  		if (output_format & DIFF_FORMAT_NUMSTAT)
>  			show_numstat(&diffstat, options);
>  		if (output_format & DIFF_FORMAT_DIFFSTAT)
> @@ -6611,6 +6590,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)
>  	return ignored;
>  }
>  
> +void compute_diffstat(struct diff_options *options,
> +		      struct diffstat_t *diffstat,
> +		      struct diff_queue_struct *q)
> +{
> +	int i;
> +
> +	memset(diffstat, 0, sizeof(struct diffstat_t));
> +	for (i = 0; i < q->nr; i++) {
> +		struct diff_filepair *p = q->queue[i];
> +		if (check_pair_status(p))
> +			diff_flush_stat(p, options, diffstat);
> +	}
> +}

Hmm, (1) clearing diffstat struct to initialize, (2) looping over
diff_queue to compute stat for each path, (3) using diffstat
information and then (4) finally freeing the diffstat info is the
bog-standard sequence of the user of this API.  Merging step (1) and
(2) may probably be OK (iow, I do not think of a use pattern for
future users where being able to do some custom things between steps
(1) and (2) would be useful), which is this function is about.  (3)
is what the user of this API would do, but shouldn't (4) be exported
at the same time, if we are making (1+2) as an external API?

>  void diff_addremove(struct diff_options *options,
>  		    int addremove, unsigned mode,
>  		    const struct object_id *oid,
> diff --git a/diff.h b/diff.h
> index b680b377b2..34fc658946 100644
> --- a/diff.h
> +++ b/diff.h
> @@ -244,6 +244,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);
>  void diff_emit_submodule_pipethrough(struct diff_options *o,
>  				     const char *line, int len);
>  
> +struct diffstat_t {
> +	int nr;
> +	int alloc;
> +	struct diffstat_file {
> +		char *from_name;
> +		char *name;
> +		char *print_name;
> +		const char *comments;
> +		unsigned is_unmerged:1;
> +		unsigned is_binary:1;
> +		unsigned is_renamed:1;
> +		unsigned is_interesting:1;
> +		uintmax_t added, deleted;
> +	} **files;
> +};
> +
>  enum color_diff {
>  	DIFF_RESET = 0,
>  	DIFF_CONTEXT = 1,
> @@ -333,6 +349,9 @@ void diff_change(struct diff_options *,
>  
>  struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);
>  
> +void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
> +		      struct diff_queue_struct *q);
> +
>  #define DIFF_SETUP_REVERSE      	1
>  #define DIFF_SETUP_USE_SIZE_CACHE	4

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

* Re: [PATCH v3 03/11] built-in add -i: implement the `status` command
  2019-07-16 14:58     ` [PATCH v3 03/11] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
@ 2019-07-31 18:12       ` Junio C Hamano
  2019-08-27 10:04         ` Johannes Schindelin
  0 siblings, 1 reply; 124+ messages in thread
From: Junio C Hamano @ 2019-07-31 18:12 UTC (permalink / raw)
  To: Daniel Ferreira via GitGitGadget
  Cc: git, Jeff Hostetler, Jeff King, Daniel Ferreira

"Daniel Ferreira via GitGitGadget" <gitgitgadget@gmail.com> writes:

> +struct item {
> +	const char *name;
> +};
> +
> +struct list_options {
> +	const char *header;
> +	void (*print_item)(int i, struct item *item, void *print_item_data);
> +	void *print_item_data;
> +};
> +
> +struct adddel {
> +	uintmax_t add, del;
> +	unsigned seen:1, binary:1;
> +};
> +
> +struct file_list {
> +	struct file_item {
> +		struct item item;
> +		struct adddel index, worktree;
> +	} **file;
> +	size_t nr, alloc;
> +};
> +
> +struct pathname_entry {
> +	struct hashmap_entry ent;
> +	size_t index;
> +	char pathname[FLEX_ARRAY];
> +};

All of the above are named too generic but assuming that add-i will
stay to be a single file and these names will never leak outside the
file to become global, it would be perfectly fine.

> +static void populate_wi_changes(struct strbuf *buf,
> +				struct adddel *ad, const char *no_changes)
> +{
> +	if (ad->binary)
> +		strbuf_addstr(buf, _("binary"));
> +	else if (ad->seen)
> +		strbuf_addf(buf, "+%"PRIuMAX"/-%"PRIuMAX,
> +			    (uintmax_t)ad->add, (uintmax_t)ad->del);
> +	else
> +		strbuf_addstr(buf, no_changes);
> +}

I offhand do not see the need for (uintmax_t) casts here...

> +static int run_status(struct repository *r, const struct pathspec *ps,
> +		      struct file_list *files, struct list_options *opts)
> +{
> +	reset_file_list(files);
> +
> +	if (get_modified_files(r, files, ps) < 0)
> +		return -1;
> +
> +	if (files->nr)
> +		list((struct item **)files->file, files->nr, opts);
> +	putchar('\n');

So, if there is anything to list, we show list() and then add an
empty line; if there is nothing to list, we show an empty line
anyway?

As long as that matches the current scripted "add -i", it's
perfectly fine.  It's just that the code structure above looked
somewhat odd.

> +static void collect_changes_cb(struct diff_queue_struct *q,
> +			       struct diff_options *options,
> +			       void *data)
> +{
> +	struct collection_status *s = data;
> +	struct diffstat_t stat = { 0 };
> +	int i;
> +
> +	if (!q->nr)
> +		return;
> +
> +	compute_diffstat(options, &stat, q);
> +
> +	for (i = 0; i < stat.nr; i++) {
> +		const char *name = stat.files[i]->name;
> +		int hash = strhash(name);
> +		struct pathname_entry *entry;
> +		size_t file_index;
> +		struct file_item *file;
> +		struct adddel *adddel;
> +
> +		entry = hashmap_get_from_hash(&s->file_map, hash, name);
> +		if (entry)
> +			file_index = entry->index;
> +		else {
> +			FLEX_ALLOC_STR(entry, pathname, name);
> +			hashmap_entry_init(entry, hash);
> +			entry->index = file_index = s->list->nr;
> +			hashmap_add(&s->file_map, entry);
> +
> +			add_file_item(s->list, name);
> +		}
> +		file = s->list->file[file_index];
> +
> +		adddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;
> +		adddel->seen = 1;
> +		adddel->add = stat.files[i]->added;
> +		adddel->del = stat.files[i]->deleted;
> +		if (stat.files[i]->is_binary)
> +			adddel->binary = 1;
> +	}
> +}

Would resources held in the "stat" structure leak at the end of this
function?

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

* Re: [PATCH v3 06/11] built-in add -i: implement the main loop
  2019-07-16 14:58     ` [PATCH v3 06/11] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
@ 2019-07-31 18:14       ` Junio C Hamano
  0 siblings, 0 replies; 124+ messages in thread
From: Junio C Hamano @ 2019-07-31 18:14 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Jeff Hostetler, Jeff King, Johannes Schindelin

"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> The Perl script `git-add--interactive.perl` mixed the purposes of the
> "list" and the "and choose" part into the same function. In the C
> version, we will keep them separate instead, calling the `list()`
> function from the `list_and_choose()` function.

Makes sense.  After all, I recall writing "add -i" in a few
iterations of lunch-time hack.  If we are to rewrite it from
scratch, we should engineer it the right way ;-)

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

* Re: [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings
  2019-07-16 14:58     ` [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings Slavica Djukic via GitGitGadget
@ 2019-07-31 18:39       ` Junio C Hamano
  2019-08-24 12:38       ` SZEDER Gábor
  1 sibling, 0 replies; 124+ messages in thread
From: Junio C Hamano @ 2019-07-31 18:39 UTC (permalink / raw)
  To: Slavica Djukic via GitGitGadget
  Cc: git, Jeff Hostetler, Jeff King, Slavica Djukic

"Slavica Djukic via GitGitGadget" <gitgitgadget@gmail.com> writes:

> The idea: for each item that we add, we generate prefixes starting with
> the first letter, then the first two letters, then three, etc, until we
> find a prefix that is unique (or until the prefix length would be
> longer than we want). If we encounter a previously-unique prefix on the
> way, we adjust that item's prefix to make it unique again (or we mark it
> as having no unique prefix if we failed to find one). These partial
> prefixes are stored in a hash map (for quick lookup times).

OK.  I suppose such a machinery that accepts a set of strings and
then give us back a set of unique prefix length for each element of
the set would be useful in general, even outside the "and choose"
part of "list and choose".  Nice design.

> To make sure that this function works as expected, we add a test using a
> special-purpose test helper that was added for that purpose.

Somehow the above repeatedly says how special purpose it is
redundantly ;-)

> diff --git a/prefix-map.h b/prefix-map.h
> new file mode 100644
> index 0000000000..ce3b8a4a32
> --- /dev/null
> +++ b/prefix-map.h
> @@ -0,0 +1,40 @@
> +#ifndef PREFIX_MAP_H
> +#define PREFIX_MAP_H
> +
> +#include "hashmap.h"
> +
> +struct prefix_item {
> +	const char *name;
> +	size_t prefix_length;
> +};
> +
> +struct prefix_map_entry {
> +	struct hashmap_entry e;
> +	const char *name;
> +	size_t prefix_length;
> +	/* if item is NULL, the prefix is not unique */
> +	struct prefix_item *item;
> +};
> +
> +struct prefix_map {
> +	struct hashmap map;
> +	int min_length, max_length;
> +};
> +
> +/*
> + * Find unique prefixes in a given list of strings.
> + *
> + * Typically, the `struct prefix_item` information will be but a field in the
> + * actual item struct; For this reason, the `list` parameter is specified as a
> + * list of pointers to the items.
> + *
> + * The `min_length`/`max_length` parameters define what length the unique
> + * prefixes should have.
> + *
> + * If no unique prefix could be found for a given item, its `prefix_length`
> + * will be set to 0.
> + */
> +void find_unique_prefixes(struct prefix_item **list, size_t nr,
> +			  int min_length, int max_length);
> +
> +#endif

Looks like a quite sane interface to me.

Thanks.

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

* Re: [PATCH v3 11/11] built-in add -i: implement the `help` command
  2019-07-16 14:58     ` [PATCH v3 11/11] built-in add -i: implement the `help` command Johannes Schindelin via GitGitGadget
@ 2019-08-02 21:04       ` Junio C Hamano
  2019-08-02 22:26         ` Jeff King
  0 siblings, 1 reply; 124+ messages in thread
From: Junio C Hamano @ 2019-08-02 21:04 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Jeff Hostetler, Jeff King, Johannes Schindelin

"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> +static int run_help(struct add_i_state *s, const struct pathspec *ps,
> +		    struct file_list *files, struct list_options *opts)
> +{
> +	const char *help_color = s->help_color;
> +
> +	color_fprintf_ln(stdout, help_color, "status        - %s",
> +			 _("show paths with changes"));
> +	color_fprintf_ln(stdout, help_color, "update        - %s",
> +			 _("add working tree state to the staged set of changes"));
> +	color_fprintf_ln(stdout, help_color, "revert        - %s",
> +			 _("revert staged set of changes back to the HEAD version"));
> +	color_fprintf_ln(stdout, help_color, "patch         - %s",
> +			 _("pick hunks and update selectively"));
> +	color_fprintf_ln(stdout, help_color, "diff          - %s",
> +			 _("view diff between HEAD and index"));
> +	color_fprintf_ln(stdout, help_color, "add untracked - %s",
> +			 _("add contents of untracked files to the staged set of changes"));

As we do not allow the command names to get translated, this makes
sense.

Have we adopted the convention to name callback parameters that have
to stay unused (because the callback function must have a function
signature that accepts the union of what everybody needs to take)
differently from the parameters that actually get used?  It may make
sense to use it in a function like this, to prevent readers from
wasting time by wondering how pathspec is used in this function, for
example.

> diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
> index 69991a3168..cf67756b85 100755
> --- a/t/t3701-add-interactive.sh
> +++ b/t/t3701-add-interactive.sh
> @@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' '
>  	test_write_lines a b a b a a b a b a >expect &&
>  	test_cmp expect a
>  '
> +
> +test_expect_success 'show help from add--helper' '
> +	git reset --hard &&
> +	cat >expect <<-EOF &&
> +
> +	<BOLD>*** Commands ***<RESET>
> +	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
> +	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
> +	<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>
> +	<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>
> +	<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>
> +	<BOLD;RED>patch         - pick hunks and update selectively<RESET>
> +	<BOLD;RED>diff          - view diff between HEAD and index<RESET>
> +	<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>
> +	<BOLD>*** Commands ***<RESET>
> +	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
> +	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
> +	<BOLD;BLUE>What now<RESET>>$SP
> +	Bye.
> +	EOF
> +	test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&
> +	test_decode_color <actual.colored >actual &&
> +	test_i18ncmp expect actual
> +'

Nicely done.


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

* Re: [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help  so far)
  2019-07-16 18:38     ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin
@ 2019-08-02 21:06       ` Junio C Hamano
  0 siblings, 0 replies; 124+ messages in thread
From: Junio C Hamano @ 2019-08-02 21:06 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

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

>> As I am a heavy user of git add -p myself and use a patched version for
>> weeks already (it is so nice to not suffer over one second startup until the
>> MSYS2 Perl finally shows me anything, instead it feels instantaneous), I
>> integrated these patch series into Git for Windows' master already, as an
>> opt-in feature guarded by the config variable add.interactive.useBuiltin
>> (and Git for Windows' installer is prepared to detect this version and offer
>> the option in the graphical user interface).

I've sent comments on a few patches in the series; overall it was a
pleasant read.

Thanks.

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

* Re: [PATCH v3 11/11] built-in add -i: implement the `help` command
  2019-08-02 21:04       ` Junio C Hamano
@ 2019-08-02 22:26         ` Jeff King
  0 siblings, 0 replies; 124+ messages in thread
From: Jeff King @ 2019-08-02 22:26 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Johannes Schindelin

On Fri, Aug 02, 2019 at 02:04:09PM -0700, Junio C Hamano wrote:

> > +static int run_help(struct add_i_state *s, const struct pathspec *ps,
> > +		    struct file_list *files, struct list_options *opts)
> [...]
> 
> As we do not allow the command names to get translated, this makes
> sense.
> 
> Have we adopted the convention to name callback parameters that have
> to stay unused (because the callback function must have a function
> signature that accepts the union of what everybody needs to take)
> differently from the parameters that actually get used?  It may make
> sense to use it in a function like this, to prevent readers from
> wasting time by wondering how pathspec is used in this function, for
> example.

I haven't yet[1] polished up the remainder of my patches to make us
-Wunused-parameter clean, but the pattern there would look like:

  void some_function(const char *foo, void *UNUSED(bar))
  {
     ... use foo but not bar ...
  }

which both tells the compiler that "bar" may be unused, and renames it
behind the scenes to unused_bar so that it cannot be accidentally used
(or more importantly, so that we can drop the annotation when it does
get used).

All of which is to say that I'm fine if you call it "unused_bar"
manually for now, but I'd switch it to the above in my series. So it may
not matter all that much in the meantime.

-Peff

[1] The sticking point is a few more cases I found where it's unclear to
    me whether they should be marked, or if it's a latent bug.

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

* Re: [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings
  2019-07-16 14:58     ` [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings Slavica Djukic via GitGitGadget
  2019-07-31 18:39       ` Junio C Hamano
@ 2019-08-24 12:38       ` SZEDER Gábor
  2019-08-27 12:14         ` Johannes Schindelin
  1 sibling, 1 reply; 124+ messages in thread
From: SZEDER Gábor @ 2019-08-24 12:38 UTC (permalink / raw)
  To: Slavica Djukic via GitGitGadget
  Cc: git, Jeff Hostetler, Jeff King, Junio C Hamano, Slavica Djukic

On Tue, Jul 16, 2019 at 07:58:42AM -0700, Slavica Djukic via GitGitGadget wrote:
> In the `git add -i` command, we show unique prefixes of the commands and
> files, to give an indication what prefix would select them.
> 
> Naturally, the C implementation looks a lot different than the Perl
> implementation: in Perl, a trie is much easier implemented, while we
> already have a pretty neat hashmap implementation in C that we use for
> the purpose of storing (not necessarily unique) prefixes.
> 
> The idea: for each item that we add, we generate prefixes starting with
> the first letter, then the first two letters, then three, etc, until we
> find a prefix that is unique (or until the prefix length would be
> longer than we want). If we encounter a previously-unique prefix on the
> way, we adjust that item's prefix to make it unique again (or we mark it
> as having no unique prefix if we failed to find one). These partial
> prefixes are stored in a hash map (for quick lookup times).
> 
> To make sure that this function works as expected, we add a test using a
> special-purpose test helper that was added for that purpose.
> 
> Note: We expect the list of prefix items to be passed in as a list of
> pointers rather than as regular list to avoid having to copy information
> (the actual items will most likely contain more information than just
> the name and the length of the unique prefix, but passing in `struct
> prefix_item *` would not allow for that).

> diff --git a/prefix-map.c b/prefix-map.c
> new file mode 100644
> index 0000000000..747ddb4ebc
> --- /dev/null
> +++ b/prefix-map.c
> @@ -0,0 +1,109 @@
> +#include "cache.h"
> +#include "prefix-map.h"
> +
> +static int map_cmp(const void *unused_cmp_data,
> +		   const void *entry,
> +		   const void *entry_or_key,
> +		   const void *unused_keydata)
> +{
> +	const struct prefix_map_entry *a = entry;
> +	const struct prefix_map_entry *b = entry_or_key;
> +
> +	return a->prefix_length != b->prefix_length ||
> +		strncmp(a->name, b->name, a->prefix_length);
> +}
> +
> +static void add_prefix_entry(struct hashmap *map, const char *name,
> +			     size_t prefix_length, struct prefix_item *item)
> +{
> +	struct prefix_map_entry *result = xmalloc(sizeof(*result));
> +	result->name = name;
> +	result->prefix_length = prefix_length;
> +	result->item = item;
> +	hashmap_entry_init(result, memhash(name, prefix_length));
> +	hashmap_add(map, result);
> +}
> +
> +static void init_prefix_map(struct prefix_map *prefix_map,
> +			    int min_prefix_length, int max_prefix_length)
> +{
> +	hashmap_init(&prefix_map->map, map_cmp, NULL, 0);
> +	prefix_map->min_length = min_prefix_length;
> +	prefix_map->max_length = max_prefix_length;
> +}
> +
> +static void add_prefix_item(struct prefix_map *prefix_map,
> +			    struct prefix_item *item)
> +{
> +	struct prefix_map_entry e = { { NULL } }, *e2;
> +	int j;
> +
> +	e.item = item;
> +	e.name = item->name;
> +
> +	for (j = prefix_map->min_length;
> +	     j <= prefix_map->max_length && e.name[j]; j++) {
> +		/* Avoid breaking UTF-8 multi-byte sequences */
> +		if (!isascii(e.name[j]))
> +			break;
> +
> +		e.prefix_length = j;
> +		hashmap_entry_init(&e, memhash(e.name, j));
> +		e2 = hashmap_get(&prefix_map->map, &e, NULL);
> +		if (!e2) {
> +			/* prefix is unique at this stage */
> +			item->prefix_length = j;
> +			add_prefix_entry(&prefix_map->map, e.name, j, item);
> +			break;
> +		}
> +
> +		if (!e2->item)
> +			continue; /* non-unique prefix */
> +
> +		if (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))
> +			BUG("unexpected prefix length: %d != %d (%s != %s)",
> +			    j, (int)e2->item->prefix_length, e.name, e2->name);
> +
> +		/* skip common prefix */
> +		for (; j < prefix_map->max_length && e.name[j]; j++) {
> +			if (e.item->name[j] != e2->item->name[j])
> +				break;
> +			add_prefix_entry(&prefix_map->map, e.name, j + 1,
> +					 NULL);
> +		}
> +
> +		/* e2 no longer refers to a unique prefix */
> +		if (j < prefix_map->max_length && e2->name[j]) {
> +			/* found a new unique prefix for e2's item */
> +			e2->item->prefix_length = j + 1;
> +			add_prefix_entry(&prefix_map->map, e2->name, j + 1,
> +					 e2->item);
> +		}
> +		else
> +			e2->item->prefix_length = 0;
> +		e2->item = NULL;
> +
> +		if (j < prefix_map->max_length && e.name[j]) {
> +			/* found a unique prefix for the item */
> +			e.item->prefix_length = j + 1;
> +			add_prefix_entry(&prefix_map->map, e.name, j + 1,
> +					 e.item);
> +		} else
> +			/* item has no (short enough) unique prefix */
> +			e.item->prefix_length = 0;
> +
> +		break;
> +	}
> +}
> +
> +void find_unique_prefixes(struct prefix_item **list, size_t nr,
> +			  int min_length, int max_length)
> +{
> +	int i;
> +	struct prefix_map prefix_map;
> +
> +	init_prefix_map(&prefix_map, min_length, max_length);
> +	for (i = 0; i < nr; i++)
> +		add_prefix_item(&prefix_map, list[i]);
> +	hashmap_free(&prefix_map.map, 1);
> +}

Between the commit message, the in-code comment, the names of the new
files, and implementation I was left somewhat confused about what this
is about and how it works.  TBH, I didn't even try to understand how
all the above works, in particular the add_prefix_item() function.

However, I think it would be much-much simpler to first sort (a copy
of?) the array of prefix item pointers based on their 'name' field,
and then look for a unique prefix in each neighboring pair.  Perhaps
it would even be faster, because it doesn't have to allocate a bunch
of hashmap items, though I don't think that it matters much in
practice (i.e. I expect the number of items to be fairly small;
presumably nobody will run interactive add after a mass refactoring
modifying thousands of files).

> diff --git a/prefix-map.h b/prefix-map.h
> new file mode 100644
> index 0000000000..ce3b8a4a32
> --- /dev/null
> +++ b/prefix-map.h
> @@ -0,0 +1,40 @@
> +#ifndef PREFIX_MAP_H
> +#define PREFIX_MAP_H
>
> +#include "hashmap.h"
> +
> +struct prefix_item {
> +	const char *name;
> +	size_t prefix_length;
> +};

This struct is part of find_unique_prefixes()'s signature, good.

> +struct prefix_map_entry {
> +	struct hashmap_entry e;
> +	const char *name;
> +	size_t prefix_length;
> +	/* if item is NULL, the prefix is not unique */
> +	struct prefix_item *item;
> +};
> +
> +struct prefix_map {
> +	struct hashmap map;
> +	int min_length, max_length;
> +};

However, neither of these two structs nor the hashmap appear in the
function's signature, but are all implementation details.  Therefore,
they should not be defined and included here in the header but in the
.c source file.  (But as mentioned above, I think this could be
implemented much simpler without these data structures.)

Furthermore, this is not a map.
A map, in general, is a container of key-value pairs that allows
efficient insertion, removal and lookup.  This so-called prefix_map
does none of that, so it should not be called a map.

> +/*
> + * Find unique prefixes in a given list of strings.

... and stores the length of the unique prefixes in the
'prefix_length' field of the elements of the given array.

> + *
> + * Typically, the `struct prefix_item` information will be but a field in the

s/but //, perhaps?

> + * actual item struct; For this reason, the `list` parameter is specified as a
> + * list of pointers to the items.
> + *
> + * The `min_length`/`max_length` parameters define what length the unique
> + * prefixes should have.
> + *
> + * If no unique prefix could be found for a given item, its `prefix_length`
> + * will be set to 0.
> + */
> +void find_unique_prefixes(struct prefix_item **list, size_t nr,

The first argument is not a list but an array.

> +			  int min_length, int max_length);

size_t, perhaps?  These are closely related to
'prefix_item.prefix_length', which is already (rightfully) size_t.

> +
> +#endif
> diff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c
> new file mode 100644
> index 0000000000..3f1c90eaf0
> --- /dev/null
> +++ b/t/helper/test-prefix-map.c
> @@ -0,0 +1,58 @@
> +#include "test-tool.h"
> +#include "cache.h"
> +#include "prefix-map.h"
> +
> +static size_t test_count, failed_count;
> +
> +static void check(int succeeded, const char *file, size_t line_no,
> +		  const char *fmt, ...)
> +{
> +	va_list ap;
> +
> +	test_count++;
> +	if (succeeded)
> +		return;
> +
> +	va_start(ap, fmt);
> +	fprintf(stderr, "%s:%d: ", file, (int)line_no);
> +	vfprintf(stderr, fmt, ap);
> +	fputc('\n', stderr);
> +	va_end(ap);
> +
> +	failed_count++;
> +}
> +
> +#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \
> +	check(expect == actual, __FILE__, __LINE__, \
> +	      "size_t's do not match: %" \
> +	      PRIdMAX " != %" PRIdMAX " (%s) (%s)", \
> +	      (intmax_t)expect, (intmax_t)actual, #actual, hint)
> +
> +int cmd__prefix_map(int argc, const char **argv)
> +{
> +#define NR 5
> +	struct prefix_item items[NR] = {

You don't have to tell the compiler how many elements this array will
contain, it will figure that out on its own.

> +		{ "unique" },
> +		{ "hell" },
> +		{ "hello" },
> +		{ "wok" },
> +		{ "world" },
> +	};
> +	struct prefix_item *list[NR] = {

Likewise.

> +		items, items + 1, items + 2, items + 3, items + 4
> +	};
> +
> +	find_unique_prefixes(list, NR, 1, 3);

This could be find_unique_prefixes(list, ARRAY_SIZE(list), 1, 3), and
then there is no need for that NR macro anymore.

> +
> +#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \
> +	EXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \
> +			     list[index]->name)
> +
> +	EXPECT_PREFIX_LENGTH_EQUALS(1, 0);
> +	EXPECT_PREFIX_LENGTH_EQUALS(0, 1);
> +	EXPECT_PREFIX_LENGTH_EQUALS(0, 2);
> +	EXPECT_PREFIX_LENGTH_EQUALS(3, 3);
> +	EXPECT_PREFIX_LENGTH_EQUALS(3, 4);
> +
> +	return !!failed_count;
> +}

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

* Re: [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`
  2019-07-31 17:52       ` Junio C Hamano
@ 2019-08-26 21:26         ` Johannes Schindelin
  2019-08-27 22:25           ` Junio C Hamano
  0 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin @ 2019-08-26 21:26 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

Hi Junio,

On Wed, 31 Jul 2019, Junio C Hamano wrote:

> "Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
> writes:
>
> > +add.interactive.useBuiltin::
>
> I am not sure if three-level name is a good thing to use here.
>
> If we have end-user controllable (like branch or remote names)
> unbounded number of subcommand/submode to "add", and "interactive"
> is merely one of it, then three-level name is a perfect fit, but
> otherwise, not.

Well, my thinking was that `add.useBuiltin` would be misleading (because
the non-interactive part of `git add` is _already_ built-in, even `git
add -e` is built-in). And `addInteractive.useBuiltin`, to me, would
pretend that `add-interactive` is the name of the command.

Besides, I really hope that this would be only temporary, as I already
have a fully-built-in `git add -i` and `git add -p` in Git for Windows,
as an experimental opt-in, and so far it looks like it could replace the
scripted version relatively soon, so maybe that particular part is not
worth all that much worry ;-)

> > @@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,
> >  {
> >  	int status, i;
> >  	struct argv_array argv = ARGV_ARRAY_INIT;
> > +	int use_builtin_add_i =
> > +		git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
> > +	if (use_builtin_add_i < 0)
> > +		git_config_get_bool("add.interactive.usebuiltin",
> > +				    &use_builtin_add_i);
> > +
> > +	if (use_builtin_add_i == 1 && !patch_mode)
> > +		return !!run_add_i(the_repository, pathspec);
>
> I am hoping that eventually "add -p" will also be routed to the new
> codepath.  Would it make sense to have "&& !patch_mode" here,
> especially at this step where run_add_i() won't do anything useful
> anyway yet?

The `&& !patch_mode` is here to allow for a gradual adoption of the
built-in parts. I don't want users who opted in to using the built-in
`git add -i` to be stopped from using `git add -p`, so I don't want to
print even a warning, let alone an error message, when the patch mode
needs to run under `add.interactive.useBuiltin = true`, even if that
part is still scripted-only.

Of course, eventually this will be handled. See
https://github.com/gitgitgadget/git/pull/173 for the
yet-to-be-contributed patch series.

I just don't want to send a multi-dozen patch series. I really don't
think there is any effective way to review such a long patch series,
let alone an efficient way to develop it incrementally based on feedback
on the mailing list, hance I broke things up into 6 separate patch
series (as indicated by the cover letter), and this one is the first of
them.

> > @@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb)
> >  		ignore_add_errors = git_config_bool(var, value);
> >  		return 0;
> >  	}
> > +
> >  	return git_default_config(var, value, cb);
> >  }
>
> Good addition while at it.

:-)

This was actually an oversight, sorry... But since you're in favor ;-)

Ciao,
Dscho

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

* Re: [PATCH v3 02/11] diff: export diffstat interface
  2019-07-31 17:59       ` Junio C Hamano
@ 2019-08-27  9:22         ` Johannes Schindelin
  0 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin @ 2019-08-27  9:22 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Daniel Ferreira via GitGitGadget, git, Jeff Hostetler, Jeff King,
	Daniel Ferreira

Hi Junio,

On Wed, 31 Jul 2019, Junio C Hamano wrote:

> "Daniel Ferreira via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > @@ -6273,12 +6257,7 @@ void diff_flush(struct diff_options *options)
> >  	    dirstat_by_line) {
> >  		struct diffstat_t diffstat;
> >
> > -		memset(&diffstat, 0, sizeof(struct diffstat_t));
> > -		for (i = 0; i < q->nr; i++) {
> > -			struct diff_filepair *p = q->queue[i];
> > -			if (check_pair_status(p))
> > -				diff_flush_stat(p, options, &diffstat);
> > -		}
> > +		compute_diffstat(options, &diffstat, q);
> >  		if (output_format & DIFF_FORMAT_NUMSTAT)
> >  			show_numstat(&diffstat, options);
> >  		if (output_format & DIFF_FORMAT_DIFFSTAT)
> > @@ -6611,6 +6590,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)
> >  	return ignored;
> >  }
> >
> > +void compute_diffstat(struct diff_options *options,
> > +		      struct diffstat_t *diffstat,
> > +		      struct diff_queue_struct *q)
> > +{
> > +	int i;
> > +
> > +	memset(diffstat, 0, sizeof(struct diffstat_t));
> > +	for (i = 0; i < q->nr; i++) {
> > +		struct diff_filepair *p = q->queue[i];
> > +		if (check_pair_status(p))
> > +			diff_flush_stat(p, options, diffstat);
> > +	}
> > +}
>
> Hmm, (1) clearing diffstat struct to initialize, (2) looping over
> diff_queue to compute stat for each path, (3) using diffstat
> information and then (4) finally freeing the diffstat info is the
> bog-standard sequence of the user of this API.  Merging step (1) and
> (2) may probably be OK (iow, I do not think of a use pattern for
> future users where being able to do some custom things between steps
> (1) and (2) would be useful), which is this function is about.  (3)
> is what the user of this API would do, but shouldn't (4) be exported
> at the same time, if we are making (1+2) as an external API?

Good point.

It _also_ hints at the fact that we're not releasing the memory properly
after running the diffstat in the built-in `add -i`.

Will fix,
Dscho

>
> >  void diff_addremove(struct diff_options *options,
> >  		    int addremove, unsigned mode,
> >  		    const struct object_id *oid,
> > diff --git a/diff.h b/diff.h
> > index b680b377b2..34fc658946 100644
> > --- a/diff.h
> > +++ b/diff.h
> > @@ -244,6 +244,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);
> >  void diff_emit_submodule_pipethrough(struct diff_options *o,
> >  				     const char *line, int len);
> >
> > +struct diffstat_t {
> > +	int nr;
> > +	int alloc;
> > +	struct diffstat_file {
> > +		char *from_name;
> > +		char *name;
> > +		char *print_name;
> > +		const char *comments;
> > +		unsigned is_unmerged:1;
> > +		unsigned is_binary:1;
> > +		unsigned is_renamed:1;
> > +		unsigned is_interesting:1;
> > +		uintmax_t added, deleted;
> > +	} **files;
> > +};
> > +
> >  enum color_diff {
> >  	DIFF_RESET = 0,
> >  	DIFF_CONTEXT = 1,
> > @@ -333,6 +349,9 @@ void diff_change(struct diff_options *,
> >
> >  struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);
> >
> > +void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
> > +		      struct diff_queue_struct *q);
> > +
> >  #define DIFF_SETUP_REVERSE      	1
> >  #define DIFF_SETUP_USE_SIZE_CACHE	4
>

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

* Re: [PATCH v3 03/11] built-in add -i: implement the `status` command
  2019-07-31 18:12       ` Junio C Hamano
@ 2019-08-27 10:04         ` Johannes Schindelin
  0 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin @ 2019-08-27 10:04 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Daniel Ferreira via GitGitGadget, git, Jeff Hostetler, Jeff King,
	Daniel Ferreira

Hi Junio,

On Wed, 31 Jul 2019, Junio C Hamano wrote:

> "Daniel Ferreira via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > +struct item {
> > +	const char *name;
> > +};
> > +
> > +struct list_options {
> > +	const char *header;
> > +	void (*print_item)(int i, struct item *item, void *print_item_data);
> > +	void *print_item_data;
> > +};
> > +
> > +struct adddel {
> > +	uintmax_t add, del;
> > +	unsigned seen:1, binary:1;
> > +};
> > +
> > +struct file_list {
> > +	struct file_item {
> > +		struct item item;
> > +		struct adddel index, worktree;
> > +	} **file;
> > +	size_t nr, alloc;
> > +};
> > +
> > +struct pathname_entry {
> > +	struct hashmap_entry ent;
> > +	size_t index;
> > +	char pathname[FLEX_ARRAY];
> > +};
>
> All of the above are named too generic but assuming that add-i will
> stay to be a single file and these names will never leak outside the
> file to become global, it would be perfectly fine.

Yep.

> > +static void populate_wi_changes(struct strbuf *buf,
> > +				struct adddel *ad, const char *no_changes)
> > +{
> > +	if (ad->binary)
> > +		strbuf_addstr(buf, _("binary"));
> > +	else if (ad->seen)
> > +		strbuf_addf(buf, "+%"PRIuMAX"/-%"PRIuMAX,
> > +			    (uintmax_t)ad->add, (uintmax_t)ad->del);
> > +	else
> > +		strbuf_addstr(buf, no_changes);
> > +}
>
> I offhand do not see the need for (uintmax_t) casts here...

Ah, that's my mistake. I had converted the `add` and `del` fields from
the `unsigned long` data type that the 20th century wanted back.

> > +static int run_status(struct repository *r, const struct pathspec *ps,
> > +		      struct file_list *files, struct list_options *opts)
> > +{
> > +	reset_file_list(files);
> > +
> > +	if (get_modified_files(r, files, ps) < 0)
> > +		return -1;
> > +
> > +	if (files->nr)
> > +		list((struct item **)files->file, files->nr, opts);
> > +	putchar('\n');
>
> So, if there is anything to list, we show list() and then add an
> empty line; if there is nothing to list, we show an empty line
> anyway?
>
> As long as that matches the current scripted "add -i", it's
> perfectly fine.  It's just that the code structure above looked
> somewhat odd.

Yep, this is the Perl version:

sub status_cmd {
        list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },
			list_modified());
	print "\n";
}

> > +static void collect_changes_cb(struct diff_queue_struct *q,
> > +			       struct diff_options *options,
> > +			       void *data)
> > +{
> > +	struct collection_status *s = data;
> > +	struct diffstat_t stat = { 0 };
> > +	int i;
> > +
> > +	if (!q->nr)
> > +		return;
> > +
> > +	compute_diffstat(options, &stat, q);
> > +
> > +	for (i = 0; i < stat.nr; i++) {
> > +		const char *name = stat.files[i]->name;
> > +		int hash = strhash(name);
> > +		struct pathname_entry *entry;
> > +		size_t file_index;
> > +		struct file_item *file;
> > +		struct adddel *adddel;
> > +
> > +		entry = hashmap_get_from_hash(&s->file_map, hash, name);
> > +		if (entry)
> > +			file_index = entry->index;
> > +		else {
> > +			FLEX_ALLOC_STR(entry, pathname, name);
> > +			hashmap_entry_init(entry, hash);
> > +			entry->index = file_index = s->list->nr;
> > +			hashmap_add(&s->file_map, entry);
> > +
> > +			add_file_item(s->list, name);
> > +		}
> > +		file = s->list->file[file_index];
> > +
> > +		adddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;
> > +		adddel->seen = 1;
> > +		adddel->add = stat.files[i]->added;
> > +		adddel->del = stat.files[i]->deleted;
> > +		if (stat.files[i]->is_binary)
> > +			adddel->binary = 1;
> > +	}
> > +}
>
> Would resources held in the "stat" structure leak at the end of this
> function?

Yep, I added a call to the now-public `free_diffstat_info()` function.

Thanks,
Dscho

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

* Re: [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings
  2019-08-24 12:38       ` SZEDER Gábor
@ 2019-08-27 12:14         ` Johannes Schindelin
  2019-08-28 16:30           ` SZEDER Gábor
  0 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin @ 2019-08-27 12:14 UTC (permalink / raw)
  To: SZEDER Gábor
  Cc: Slavica Djukic via GitGitGadget, git, Jeff Hostetler, Jeff King,
	Junio C Hamano, Slavica Djukic

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

Hi Gábor,

On Sat, 24 Aug 2019, SZEDER Gábor wrote:

> On Tue, Jul 16, 2019 at 07:58:42AM -0700, Slavica Djukic via GitGitGadget wrote:
> > In the `git add -i` command, we show unique prefixes of the commands and
> > files, to give an indication what prefix would select them.
> >
> > Naturally, the C implementation looks a lot different than the Perl
> > implementation: in Perl, a trie is much easier implemented, while we
> > already have a pretty neat hashmap implementation in C that we use for
> > the purpose of storing (not necessarily unique) prefixes.
> >
> > The idea: for each item that we add, we generate prefixes starting with
> > the first letter, then the first two letters, then three, etc, until we
> > find a prefix that is unique (or until the prefix length would be
> > longer than we want). If we encounter a previously-unique prefix on the
> > way, we adjust that item's prefix to make it unique again (or we mark it
> > as having no unique prefix if we failed to find one). These partial
> > prefixes are stored in a hash map (for quick lookup times).
> >
> > To make sure that this function works as expected, we add a test using a
> > special-purpose test helper that was added for that purpose.
> >
> > Note: We expect the list of prefix items to be passed in as a list of
> > pointers rather than as regular list to avoid having to copy information
> > (the actual items will most likely contain more information than just
> > the name and the length of the unique prefix, but passing in `struct
> > prefix_item *` would not allow for that).
>
> > diff --git a/prefix-map.c b/prefix-map.c
> > new file mode 100644
> > index 0000000000..747ddb4ebc
> > --- /dev/null
> > +++ b/prefix-map.c
> > @@ -0,0 +1,109 @@
> > +#include "cache.h"
> > +#include "prefix-map.h"
> > +
> > +static int map_cmp(const void *unused_cmp_data,
> > +		   const void *entry,
> > +		   const void *entry_or_key,
> > +		   const void *unused_keydata)
> > +{
> > +	const struct prefix_map_entry *a = entry;
> > +	const struct prefix_map_entry *b = entry_or_key;
> > +
> > +	return a->prefix_length != b->prefix_length ||
> > +		strncmp(a->name, b->name, a->prefix_length);
> > +}
> > +
> > +static void add_prefix_entry(struct hashmap *map, const char *name,
> > +			     size_t prefix_length, struct prefix_item *item)
> > +{
> > +	struct prefix_map_entry *result = xmalloc(sizeof(*result));
> > +	result->name = name;
> > +	result->prefix_length = prefix_length;
> > +	result->item = item;
> > +	hashmap_entry_init(result, memhash(name, prefix_length));
> > +	hashmap_add(map, result);
> > +}
> > +
> > +static void init_prefix_map(struct prefix_map *prefix_map,
> > +			    int min_prefix_length, int max_prefix_length)
> > +{
> > +	hashmap_init(&prefix_map->map, map_cmp, NULL, 0);
> > +	prefix_map->min_length = min_prefix_length;
> > +	prefix_map->max_length = max_prefix_length;
> > +}
> > +
> > +static void add_prefix_item(struct prefix_map *prefix_map,
> > +			    struct prefix_item *item)
> > +{
> > +	struct prefix_map_entry e = { { NULL } }, *e2;
> > +	int j;
> > +
> > +	e.item = item;
> > +	e.name = item->name;
> > +
> > +	for (j = prefix_map->min_length;
> > +	     j <= prefix_map->max_length && e.name[j]; j++) {
> > +		/* Avoid breaking UTF-8 multi-byte sequences */
> > +		if (!isascii(e.name[j]))
> > +			break;
> > +
> > +		e.prefix_length = j;
> > +		hashmap_entry_init(&e, memhash(e.name, j));
> > +		e2 = hashmap_get(&prefix_map->map, &e, NULL);
> > +		if (!e2) {
> > +			/* prefix is unique at this stage */
> > +			item->prefix_length = j;
> > +			add_prefix_entry(&prefix_map->map, e.name, j, item);
> > +			break;
> > +		}
> > +
> > +		if (!e2->item)
> > +			continue; /* non-unique prefix */
> > +
> > +		if (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))
> > +			BUG("unexpected prefix length: %d != %d (%s != %s)",
> > +			    j, (int)e2->item->prefix_length, e.name, e2->name);
> > +
> > +		/* skip common prefix */
> > +		for (; j < prefix_map->max_length && e.name[j]; j++) {
> > +			if (e.item->name[j] != e2->item->name[j])
> > +				break;
> > +			add_prefix_entry(&prefix_map->map, e.name, j + 1,
> > +					 NULL);
> > +		}
> > +
> > +		/* e2 no longer refers to a unique prefix */
> > +		if (j < prefix_map->max_length && e2->name[j]) {
> > +			/* found a new unique prefix for e2's item */
> > +			e2->item->prefix_length = j + 1;
> > +			add_prefix_entry(&prefix_map->map, e2->name, j + 1,
> > +					 e2->item);
> > +		}
> > +		else
> > +			e2->item->prefix_length = 0;
> > +		e2->item = NULL;
> > +
> > +		if (j < prefix_map->max_length && e.name[j]) {
> > +			/* found a unique prefix for the item */
> > +			e.item->prefix_length = j + 1;
> > +			add_prefix_entry(&prefix_map->map, e.name, j + 1,
> > +					 e.item);
> > +		} else
> > +			/* item has no (short enough) unique prefix */
> > +			e.item->prefix_length = 0;
> > +
> > +		break;
> > +	}
> > +}
> > +
> > +void find_unique_prefixes(struct prefix_item **list, size_t nr,
> > +			  int min_length, int max_length)
> > +{
> > +	int i;
> > +	struct prefix_map prefix_map;
> > +
> > +	init_prefix_map(&prefix_map, min_length, max_length);
> > +	for (i = 0; i < nr; i++)
> > +		add_prefix_item(&prefix_map, list[i]);
> > +	hashmap_free(&prefix_map.map, 1);
> > +}
>
> Between the commit message, the in-code comment, the names of the new
> files, and implementation I was left somewhat confused about what this
> is about and how it works.  TBH, I didn't even try to understand how
> all the above works, in particular the add_prefix_item() function.

Let me try to explain it here, and maybe you can help me by suggesting
an improved commit message and/or code comments?

The problem is this: given a set of items with labels (e.g. file names),
find, for each item, the unique prefix that identifies it. Example:
given the files `hello.txt`, `heaven.txt` and `hell.txt`, the items'
unique prefixes would be `hello`, `hea` and `hell.`, respectively.

In `git add -i`, we actually only want to allow alphanumerical prefixes,
and we also want at least one, and at most three characters, so only the
second item would have an admissible unique prefix: `hea`.

> However, I think it would be much-much simpler to first sort (a copy
> of?) the array of prefix item pointers based on their 'name' field,
> and then look for a unique prefix in each neighboring pair.  Perhaps
> it would even be faster, because it doesn't have to allocate a bunch
> of hashmap items, though I don't think that it matters much in
> practice (i.e. I expect the number of items to be fairly small;
> presumably nobody will run interactive add after a mass refactoring
> modifying thousands of files).

The time complexity of the sorted list would be O(n*log(n)), while the
hashmap-based complexity would be an amortized O(n).

And yes, you would not _want_ to run interactive add after a mass
refactoring. But it happens. It happens to me more times than I care to
admit. And you know what? I really appreciate that even the Perl version
is relatively snappy in those circumstances.

> > diff --git a/prefix-map.h b/prefix-map.h
> > new file mode 100644
> > index 0000000000..ce3b8a4a32
> > --- /dev/null
> > +++ b/prefix-map.h
> > @@ -0,0 +1,40 @@
> > +#ifndef PREFIX_MAP_H
> > +#define PREFIX_MAP_H
> >
> > +#include "hashmap.h"
> > +
> > +struct prefix_item {
> > +	const char *name;
> > +	size_t prefix_length;
> > +};
>
> This struct is part of find_unique_prefixes()'s signature, good.
>
> > +struct prefix_map_entry {
> > +	struct hashmap_entry e;
> > +	const char *name;
> > +	size_t prefix_length;
> > +	/* if item is NULL, the prefix is not unique */
> > +	struct prefix_item *item;
> > +};
> > +
> > +struct prefix_map {
> > +	struct hashmap map;
> > +	int min_length, max_length;
> > +};
>
> However, neither of these two structs nor the hashmap appear in the
> function's signature, but are all implementation details.  Therefore,
> they should not be defined and included here in the header but in the
> .c source file.  (But as mentioned above, I think this could be
> implemented much simpler without these data structures.)

Right you are!

> Furthermore, this is not a map.
> A map, in general, is a container of key-value pairs that allows
> efficient insertion, removal and lookup.  This so-called prefix_map
> does none of that, so it should not be called a map.

What would you call it instead?

(I went with "map" because the underlying data structure is a "hash
map", I know, not the best argument, but I failed to find a better
name...)

I also have to admit that I thought that I could fix the design where
`git-add--interactive.perl` creates this trie, but then still performs a
linear search when searching by prefix. That seems not to be possible,
though, as the unique prefixes are limited to certain character ranges,
while the lookup-by-prefix is not limited in that way.

> > +/*
> > + * Find unique prefixes in a given list of strings.
>
> ... and stores the length of the unique prefixes in the
> 'prefix_length' field of the elements of the given array.

Good idea. I changed it to also explain what is meant by "unique
prefix":

 * Given a list of names, find unique prefixes (i.e. the first <n> characters
 * that uniquely identify the names) and store the lengths of the unique
 * prefixes in the 'prefix_length' field of the elements of the given array..

> > + *
> > + * Typically, the `struct prefix_item` information will be but a field in the
>
> s/but //, perhaps?

Sure. I am relatively certain that it is correct grammar, but it is
probably a good idea to remove it.

> > + * actual item struct; For this reason, the `list` parameter is specified as a
> > + * list of pointers to the items.
> > + *
> > + * The `min_length`/`max_length` parameters define what length the unique
> > + * prefixes should have.
> > + *
> > + * If no unique prefix could be found for a given item, its `prefix_length`
> > + * will be set to 0.
> > + */
> > +void find_unique_prefixes(struct prefix_item **list, size_t nr,
>
> The first argument is not a list but an array.

Indeed.

> > +			  int min_length, int max_length);
>
> size_t, perhaps?  These are closely related to
> 'prefix_item.prefix_length', which is already (rightfully) size_t.

True.

> > +#endif
> > diff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c
> > new file mode 100644
> > index 0000000000..3f1c90eaf0
> > --- /dev/null
> > +++ b/t/helper/test-prefix-map.c
> > @@ -0,0 +1,58 @@
> > +#include "test-tool.h"
> > +#include "cache.h"
> > +#include "prefix-map.h"
> > +
> > +static size_t test_count, failed_count;
> > +
> > +static void check(int succeeded, const char *file, size_t line_no,
> > +		  const char *fmt, ...)
> > +{
> > +	va_list ap;
> > +
> > +	test_count++;
> > +	if (succeeded)
> > +		return;
> > +
> > +	va_start(ap, fmt);
> > +	fprintf(stderr, "%s:%d: ", file, (int)line_no);
> > +	vfprintf(stderr, fmt, ap);
> > +	fputc('\n', stderr);
> > +	va_end(ap);
> > +
> > +	failed_count++;
> > +}
> > +
> > +#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \
> > +	check(expect == actual, __FILE__, __LINE__, \
> > +	      "size_t's do not match: %" \
> > +	      PRIdMAX " != %" PRIdMAX " (%s) (%s)", \
> > +	      (intmax_t)expect, (intmax_t)actual, #actual, hint)
> > +
> > +int cmd__prefix_map(int argc, const char **argv)
> > +{
> > +#define NR 5
> > +	struct prefix_item items[NR] = {
>
> You don't have to tell the compiler how many elements this array will
> contain, it will figure that out on its own.
>
> > +		{ "unique" },
> > +		{ "hell" },
> > +		{ "hello" },
> > +		{ "wok" },
> > +		{ "world" },
> > +	};
> > +	struct prefix_item *list[NR] = {
>
> Likewise.

That is correct.

What the compiler _cannot_ figure out, on its own, however, is that
`items` and `list` _need_ to contain the same number of items.

Hence the need for `NR`.

> > +		items, items + 1, items + 2, items + 3, items + 4
> > +	};
> > +
> > +	find_unique_prefixes(list, NR, 1, 3);
>
> This could be find_unique_prefixes(list, ARRAY_SIZE(list), 1, 3), and
> then there is no need for that NR macro anymore.
>
> > +
> > +#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \
> > +	EXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \
> > +			     list[index]->name)
> > +
> > +	EXPECT_PREFIX_LENGTH_EQUALS(1, 0);
> > +	EXPECT_PREFIX_LENGTH_EQUALS(0, 1);
> > +	EXPECT_PREFIX_LENGTH_EQUALS(0, 2);
> > +	EXPECT_PREFIX_LENGTH_EQUALS(3, 3);
> > +	EXPECT_PREFIX_LENGTH_EQUALS(3, 4);
> > +
> > +	return !!failed_count;
> > +}

Thank you for your review!
Dscho

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

* [PATCH v4 00/11] git add -i: add a rudimentary version in C (supporting only status and help  so far)
  2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                       ` (11 preceding siblings ...)
  2019-07-16 18:38     ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin
@ 2019-08-27 12:57     ` Johannes Schindelin via GitGitGadget
  2019-08-27 12:57       ` [PATCH v4 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
                         ` (11 more replies)
  12 siblings, 12 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-08-27 12:57 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano

This is the first leg on the long journey to a fully built-in git add -i 
(next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3
[https://github.com/gitgitgadget/git/pull/172], 4
[https://github.com/gitgitgadget/git/pull/173], 5
[https://github.com/gitgitgadget/git/pull/174], and 6
[https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are
not necessarily up to date, and will be re-targeted to the appropriate
branches in https://github.com/gitster/git as soon as Junio picks them up.

This here patch series reflects the part that was submitted a couple of
times (see https://github.com/gitgitgadget/git/pull/103) during the
Outreachy project by Slavica Ðukic that continued the journey based on an
initial patch series by Daniel Ferreira.

It only implements the status and the help part, in the interest of making
the review remotely more reviewable.

As I am a heavy user of git add -p myself and use a patched version for
weeks already (it is so nice to not suffer over one second startup until the
MSYS2 Perl finally shows me anything, instead it feels instantaneous), I
integrated these patch series into Git for Windows' master already, as an
opt-in feature guarded by the config variable add.interactive.useBuiltin 
(and Git for Windows' installer is prepared to detect this version and offer
the option in the graphical user interface).

I had planned on submitting this before v2.22.0-rc0, but there was such a
backlog of builds from a big pushout that I had to wait ;-)

Changes since v3:

 * Rebased to v2.23.0 to reduce friction.
 * free_diffstat_info() is now made public as well, and used, to avoid a
   memory leak.
 * Prepared the patches for ew/hashmap (which is strict about the hashmap
   entries' type in hashmap_entry_init() and friends).
 * The private data types have been moved from prefix-map.h to prefix-map.c.
 * A lot of int types were converted to more appropriate size_t in 
   prefix-map.c.
 * A misleading parameter name list was renamed to the correct array.
 * The code comment above find_unique_prefixes() was (hopefully) improved.
 * The run_help() function's signature now reflects that most of the
   parameters are actually unused.

Changes since v2:

 * Rebased to master to avoid merge conflicts.
 * Renumbered the prefix-map test to avoid conflicts with two patch series
   that are currently in-flight in pu.

Changes since v1:

 * The config machinery was reworked completely, to not use a callback to 
   git_config(), but instead to query the config via the repo_config_get_*() 
   functions. This also prevents a future "Huh???" moment: the internal add
   --interactive API accepts a parameter of type struct repository *r, but
   the previous configuration did not use that to query the config (and
   could in the future be a repository other than the_repository).
   
   
 * As a consequence, the color sequences are no longer stored in file-local
   variables, but passed around via a struct.
   
   
 * Instead of using the magical constant -2 to quit the main loop, it is now
   defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is
   defined as -1 and used where appropriate).
   
   
 * Improved the add_prefix_item() function by avoiding buffer overruns, not
   reusing the struct that is used for lookup also for adding the new item,
   and by strengthening the bug check.

Daniel Ferreira (2):
  diff: export diffstat interface
  built-in add -i: implement the `status` command

Johannes Schindelin (6):
  Start to implement a built-in version of `git add --interactive`
  built-in add -i: refresh the index before running `status`
  built-in add -i: color the header in the `status` command
  built-in add -i: implement the main loop
  built-in add -i: support `?` (prompt help)
  built-in add -i: implement the `help` command

Slavica Djukic (3):
  Add a function to determine unique prefixes for a list of strings
  built-in add -i: show unique prefixes of the commands
  built-in add -i: use color in the main loop

 Documentation/config/add.txt |   5 +
 Makefile                     |   3 +
 add-interactive.c            | 558 +++++++++++++++++++++++++++++++++++
 add-interactive.h            |   8 +
 builtin/add.c                |  10 +
 diff.c                       |  39 +--
 diff.h                       |  20 ++
 prefix-map.c                 | 123 ++++++++
 prefix-map.h                 |  29 ++
 repository.c                 |  19 ++
 repository.h                 |   7 +
 t/README                     |   4 +
 t/helper/test-prefix-map.c   |  58 ++++
 t/helper/test-tool.c         |   1 +
 t/helper/test-tool.h         |   1 +
 t/t0018-prefix-map.sh        |  10 +
 t/t3701-add-interactive.sh   |  25 ++
 17 files changed, 897 insertions(+), 23 deletions(-)
 create mode 100644 add-interactive.c
 create mode 100644 add-interactive.h
 create mode 100644 prefix-map.c
 create mode 100644 prefix-map.h
 create mode 100644 t/helper/test-prefix-map.c
 create mode 100755 t/t0018-prefix-map.sh


base-commit: 5fa0f5238b0cd46cfe7f6fa76c3f526ea98148d9
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v4
Pull-Request: https://github.com/gitgitgadget/git/pull/170

Range-diff vs v3:

  1:  0a5ec9345d =  1:  ad8752eca7 Start to implement a built-in version of `git add --interactive`
  2:  c7a377890d !  2:  38cc04c1d9 diff: export diffstat interface
     @@ -41,6 +41,15 @@
       static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
       					  const char *name_a,
       					  const char *name_b)
     +@@
     + 	gather_dirstat(options, &dir, changed, "", 0);
     + }
     + 
     +-static void free_diffstat_info(struct diffstat_t *diffstat)
     ++void free_diffstat_info(struct diffstat_t *diffstat)
     + {
     + 	int i;
     + 	for (i = 0; i < diffstat->nr; i++) {
      @@
       	    dirstat_by_line) {
       		struct diffstat_t diffstat;
     @@ -109,6 +118,7 @@
       
      +void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
      +		      struct diff_queue_struct *q);
     ++void free_diffstat_info(struct diffstat_t *diffstat);
      +
       #define DIFF_SETUP_REVERSE      	1
       #define DIFF_SETUP_USE_SIZE_CACHE	4
  3:  b93b055ebe !  3:  ee3e40293c built-in add -i: implement the `status` command
     @@ -158,9 +158,9 @@
      +			file_index = entry->index;
      +		else {
      +			FLEX_ALLOC_STR(entry, pathname, name);
     -+			hashmap_entry_init(entry, hash);
     ++			hashmap_entry_init(&entry->ent, hash);
      +			entry->index = file_index = s->list->nr;
     -+			hashmap_add(&s->file_map, entry);
     ++			hashmap_add(&s->file_map, &entry->ent);
      +
      +			add_file_item(s->list, name);
      +		}
     @@ -173,6 +173,7 @@
      +		if (stat.files[i]->is_binary)
      +			adddel->binary = 1;
      +	}
     ++	free_diffstat_info(&stat);
      +}
      +
      +static int get_modified_files(struct repository *r, struct file_list *list,
  4:  daff24074a =  4:  3c855d9fa5 built-in add -i: refresh the index before running `status`
  5:  15f18f5b3e =  5:  24737a09f7 built-in add -i: color the header in the `status` command
  6:  175409aaae =  6:  ac67731cf1 built-in add -i: implement the main loop
  7:  3000d7d08d !  7:  c5a699b6b2 Add a function to determine unique prefixes for a list of strings
     @@ -19,7 +19,7 @@
          prefixes are stored in a hash map (for quick lookup times).
      
          To make sure that this function works as expected, we add a test using a
     -    special-purpose test helper that was added for that purpose.
     +    special-purpose test helper.
      
          Note: We expect the list of prefix items to be passed in as a list of
          pointers rather than as regular list to avoid having to copy information
     @@ -58,6 +58,19 @@
      +#include "cache.h"
      +#include "prefix-map.h"
      +
     ++struct prefix_map_entry {
     ++	struct hashmap_entry e;
     ++	const char *name;
     ++	size_t prefix_length;
     ++	/* if item is NULL, the prefix is not unique */
     ++	struct prefix_item *item;
     ++};
     ++
     ++struct prefix_map {
     ++	struct hashmap map;
     ++	size_t min_length, max_length;
     ++};
     ++
      +static int map_cmp(const void *unused_cmp_data,
      +		   const void *entry,
      +		   const void *entry_or_key,
     @@ -77,12 +90,12 @@
      +	result->name = name;
      +	result->prefix_length = prefix_length;
      +	result->item = item;
     -+	hashmap_entry_init(result, memhash(name, prefix_length));
     -+	hashmap_add(map, result);
     ++	hashmap_entry_init(&result->e, memhash(name, prefix_length));
     ++	hashmap_add(map, &result->e);
      +}
      +
      +static void init_prefix_map(struct prefix_map *prefix_map,
     -+			    int min_prefix_length, int max_prefix_length)
     ++			    size_t min_prefix_length, size_t max_prefix_length)
      +{
      +	hashmap_init(&prefix_map->map, map_cmp, NULL, 0);
      +	prefix_map->min_length = min_prefix_length;
     @@ -93,7 +106,7 @@
      +			    struct prefix_item *item)
      +{
      +	struct prefix_map_entry e = { { NULL } }, *e2;
     -+	int j;
     ++	size_t j;
      +
      +	e.item = item;
      +	e.name = item->name;
     @@ -105,8 +118,8 @@
      +			break;
      +
      +		e.prefix_length = j;
     -+		hashmap_entry_init(&e, memhash(e.name, j));
     -+		e2 = hashmap_get(&prefix_map->map, &e, NULL);
     ++		hashmap_entry_init(&e.e, memhash(e.name, j));
     ++		e2 = hashmap_get(&prefix_map->map, &e.e, NULL);
      +		if (!e2) {
      +			/* prefix is unique at this stage */
      +			item->prefix_length = j;
     @@ -119,7 +132,8 @@
      +
      +		if (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))
      +			BUG("unexpected prefix length: %d != %d (%s != %s)",
     -+			    j, (int)e2->item->prefix_length, e.name, e2->name);
     ++			    (int)j, (int)e2->item->prefix_length,
     ++			    e.name, e2->name);
      +
      +		/* skip common prefix */
      +		for (; j < prefix_map->max_length && e.name[j]; j++) {
     @@ -153,15 +167,15 @@
      +	}
      +}
      +
     -+void find_unique_prefixes(struct prefix_item **list, size_t nr,
     -+			  int min_length, int max_length)
     ++void find_unique_prefixes(struct prefix_item **array, size_t nr,
     ++			  size_t min_length, size_t max_length)
      +{
     -+	int i;
     ++	size_t i;
      +	struct prefix_map prefix_map;
      +
      +	init_prefix_map(&prefix_map, min_length, max_length);
      +	for (i = 0; i < nr; i++)
     -+		add_prefix_item(&prefix_map, list[i]);
     ++		add_prefix_item(&prefix_map, array[i]);
      +	hashmap_free(&prefix_map.map, 1);
      +}
      
     @@ -180,25 +194,14 @@
      +	size_t prefix_length;
      +};
      +
     -+struct prefix_map_entry {
     -+	struct hashmap_entry e;
     -+	const char *name;
     -+	size_t prefix_length;
     -+	/* if item is NULL, the prefix is not unique */
     -+	struct prefix_item *item;
     -+};
     -+
     -+struct prefix_map {
     -+	struct hashmap map;
     -+	int min_length, max_length;
     -+};
     -+
      +/*
     -+ * Find unique prefixes in a given list of strings.
     ++ * Given an array of names, find unique prefixes (i.e. the first <n> characters
     ++ * that uniquely identify the names) and store the lengths of the unique
     ++ * prefixes in the 'prefix_length' field of the elements of the given array..
      + *
     -+ * Typically, the `struct prefix_item` information will be but a field in the
     -+ * actual item struct; For this reason, the `list` parameter is specified as a
     -+ * list of pointers to the items.
     ++ * Typically, the `struct prefix_item` information is a field in the actual
     ++ * item struct; For this reason, the `array` parameter is specified as an array
     ++ * of pointers to the items.
      + *
      + * The `min_length`/`max_length` parameters define what length the unique
      + * prefixes should have.
     @@ -206,8 +209,8 @@
      + * If no unique prefix could be found for a given item, its `prefix_length`
      + * will be set to 0.
      + */
     -+void find_unique_prefixes(struct prefix_item **list, size_t nr,
     -+			  int min_length, int max_length);
     ++void find_unique_prefixes(struct prefix_item **array, size_t nr,
     ++			  size_t min_length, size_t max_length);
      +
      +#endif
      
  8:  e23ddebfbf =  8:  bc7a74f697 built-in add -i: show unique prefixes of the commands
  9:  d8c012fce8 =  9:  74f73e26b4 built-in add -i: support `?` (prompt help)
 10:  8121a3ca1b = 10:  88001009bc built-in add -i: use color in the main loop
 11:  db70c6475d ! 11:  b27fbe289f built-in add -i: implement the `help` command
     @@ -18,22 +18,21 @@
       	return 0;
       }
       
     -+static int run_help(struct add_i_state *s, const struct pathspec *ps,
     -+		    struct file_list *files, struct list_options *opts)
     ++static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,
     ++		    struct file_list *unused_files,
     ++		    struct list_options *unused_opts)
      +{
     -+	const char *help_color = s->help_color;
     -+
     -+	color_fprintf_ln(stdout, help_color, "status        - %s",
     ++	color_fprintf_ln(stdout, s->help_color, "status        - %s",
      +			 _("show paths with changes"));
     -+	color_fprintf_ln(stdout, help_color, "update        - %s",
     ++	color_fprintf_ln(stdout, s->help_color, "update        - %s",
      +			 _("add working tree state to the staged set of changes"));
     -+	color_fprintf_ln(stdout, help_color, "revert        - %s",
     ++	color_fprintf_ln(stdout, s->help_color, "revert        - %s",
      +			 _("revert staged set of changes back to the HEAD version"));
     -+	color_fprintf_ln(stdout, help_color, "patch         - %s",
     ++	color_fprintf_ln(stdout, s->help_color, "patch         - %s",
      +			 _("pick hunks and update selectively"));
     -+	color_fprintf_ln(stdout, help_color, "diff          - %s",
     ++	color_fprintf_ln(stdout, s->help_color, "diff          - %s",
      +			 _("view diff between HEAD and index"));
     -+	color_fprintf_ln(stdout, help_color, "add untracked - %s",
     ++	color_fprintf_ln(stdout, s->help_color, "add untracked - %s",
      +			 _("add contents of untracked files to the staged set of changes"));
      +
      +	return 0;

-- 
gitgitgadget

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

* [PATCH v4 01/11] Start to implement a built-in version of `git add --interactive`
  2019-08-27 12:57     ` [PATCH v4 " Johannes Schindelin via GitGitGadget
@ 2019-08-27 12:57       ` Johannes Schindelin via GitGitGadget
  2019-08-27 12:57       ` [PATCH v4 02/11] diff: export diffstat interface Daniel Ferreira via GitGitGadget
                         ` (10 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-08-27 12:57 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

This is hardly the first conversion of a Git command that is implemented
as a script to a built-in. So far, the most successful strategy for such
conversions has been to add a built-in helper and call that for more and
more functionality from the script, as more and more parts are
converted.

With the interactive add, we choose a different strategy. The sole
reason for this is that on Windows (where such a conversion has the most
benefits in terms of speed and robustness) we face the very specific
problem that a `system()` call in Perl seems to close `stdin` in the
parent process when the spawned process consumes even one character from
`stdin`. And that just does not work for us here, as it would stop the
main loop as soon as any interactive command was performed by the
helper. Which is almost all of the commands in `git add -i`.

It is almost as if Perl told us once again that it does not want us to
use it on Windows.

Instead, we follow the opposite route where we start with a bare-bones
version of the built-in interactive add, guarded by the new
`add.interactive.useBuiltin` config variable, and then add more and more
functionality to it, until it is feature complete.

At this point, the built-in version of `git add -i` only states that it
cannot do anything yet ;-)

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/config/add.txt |  5 +++++
 Makefile                     |  1 +
 add-interactive.c            |  7 +++++++
 add-interactive.h            |  8 ++++++++
 builtin/add.c                | 10 ++++++++++
 t/README                     |  4 ++++
 6 files changed, 35 insertions(+)
 create mode 100644 add-interactive.c
 create mode 100644 add-interactive.h

diff --git a/Documentation/config/add.txt b/Documentation/config/add.txt
index 4d753f006e..c9f748f81c 100644
--- a/Documentation/config/add.txt
+++ b/Documentation/config/add.txt
@@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::
 	option of linkgit:git-add[1].  `add.ignore-errors` is deprecated,
 	as it does not follow the usual naming convention for configuration
 	variables.
+
+add.interactive.useBuiltin::
+	[EXPERIMENTAL] Set to `true` to use the experimental built-in
+	implementation of the interactive version of linkgit:git-add[1]
+	instead of the Perl script version. Is `false` by default.
diff --git a/Makefile b/Makefile
index f9255344ae..d04daf9fd8 100644
--- a/Makefile
+++ b/Makefile
@@ -826,6 +826,7 @@ LIB_H := $(sort $(shell git ls-files '*.h' ':!t/' ':!Documentation/' 2>/dev/null
 	-name '*.h' -print))
 
 LIB_OBJS += abspath.o
+LIB_OBJS += add-interactive.o
 LIB_OBJS += advice.o
 LIB_OBJS += alias.o
 LIB_OBJS += alloc.o
diff --git a/add-interactive.c b/add-interactive.c
new file mode 100644
index 0000000000..482e458dc6
--- /dev/null
+++ b/add-interactive.c
@@ -0,0 +1,7 @@
+#include "cache.h"
+#include "add-interactive.h"
+
+int run_add_i(struct repository *r, const struct pathspec *ps)
+{
+	die(_("No commands are available in the built-in `git add -i` yet!"));
+}
diff --git a/add-interactive.h b/add-interactive.h
new file mode 100644
index 0000000000..7043b8741d
--- /dev/null
+++ b/add-interactive.h
@@ -0,0 +1,8 @@
+#ifndef ADD_INTERACTIVE_H
+#define ADD_INTERACTIVE_H
+
+struct repository;
+struct pathspec;
+int run_add_i(struct repository *r, const struct pathspec *ps);
+
+#endif
diff --git a/builtin/add.c b/builtin/add.c
index dd18e5c9b6..4f625691b5 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -20,6 +20,7 @@
 #include "bulk-checkin.h"
 #include "argv-array.h"
 #include "submodule.h"
+#include "add-interactive.h"
 
 static const char * const builtin_add_usage[] = {
 	N_("git add [<options>] [--] <pathspec>..."),
@@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,
 {
 	int status, i;
 	struct argv_array argv = ARGV_ARRAY_INIT;
+	int use_builtin_add_i =
+		git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
+	if (use_builtin_add_i < 0)
+		git_config_get_bool("add.interactive.usebuiltin",
+				    &use_builtin_add_i);
+
+	if (use_builtin_add_i == 1 && !patch_mode)
+		return !!run_add_i(the_repository, pathspec);
 
 	argv_array_push(&argv, "add--interactive");
 	if (patch_mode)
@@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb)
 		ignore_add_errors = git_config_bool(var, value);
 		return 0;
 	}
+
 	return git_default_config(var, value, cb);
 }
 
diff --git a/t/README b/t/README
index 60d5b77bcc..bda93fe603 100644
--- a/t/README
+++ b/t/README
@@ -397,6 +397,10 @@ 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_ADD_I_USE_BUILTIN=<boolean>, when true, enables the
+builtin version of git add -i. See 'add.interactive.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
-- 
gitgitgadget


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

* [PATCH v4 02/11] diff: export diffstat interface
  2019-08-27 12:57     ` [PATCH v4 " Johannes Schindelin via GitGitGadget
  2019-08-27 12:57       ` [PATCH v4 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
@ 2019-08-27 12:57       ` Daniel Ferreira via GitGitGadget
  2019-08-27 12:57       ` [PATCH v4 03/11] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
                         ` (9 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Daniel Ferreira via GitGitGadget @ 2019-08-27 12:57 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Daniel Ferreira

From: Daniel Ferreira <bnmvco@gmail.com>

Make the diffstat interface (namely, the diffstat_t struct and
compute_diffstat) no longer be internal to diff.c and allow it to be used
by other parts of git.

This is helpful for code that may want to easily extract information
from files using the diff machinery, while flushing it differently from
how the show_* functions used by diff_flush() do it. One example is the
builtin implementation of git-add--interactive's status.

Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 39 ++++++++++++++++-----------------------
 diff.h | 20 ++++++++++++++++++++
 2 files changed, 36 insertions(+), 23 deletions(-)

diff --git a/diff.c b/diff.c
index efe42b341a..6829a74065 100644
--- a/diff.c
+++ b/diff.c
@@ -2492,22 +2492,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)
 	}
 }
 
-struct diffstat_t {
-	int nr;
-	int alloc;
-	struct diffstat_file {
-		char *from_name;
-		char *name;
-		char *print_name;
-		const char *comments;
-		unsigned is_unmerged:1;
-		unsigned is_binary:1;
-		unsigned is_renamed:1;
-		unsigned is_interesting:1;
-		uintmax_t added, deleted;
-	} **files;
-};
-
 static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
 					  const char *name_a,
 					  const char *name_b)
@@ -3154,7 +3138,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o
 	gather_dirstat(options, &dir, changed, "", 0);
 }
 
-static void free_diffstat_info(struct diffstat_t *diffstat)
+void free_diffstat_info(struct diffstat_t *diffstat)
 {
 	int i;
 	for (i = 0; i < diffstat->nr; i++) {
@@ -6278,12 +6262,7 @@ void diff_flush(struct diff_options *options)
 	    dirstat_by_line) {
 		struct diffstat_t diffstat;
 
-		memset(&diffstat, 0, sizeof(struct diffstat_t));
-		for (i = 0; i < q->nr; i++) {
-			struct diff_filepair *p = q->queue[i];
-			if (check_pair_status(p))
-				diff_flush_stat(p, options, &diffstat);
-		}
+		compute_diffstat(options, &diffstat, q);
 		if (output_format & DIFF_FORMAT_NUMSTAT)
 			show_numstat(&diffstat, options);
 		if (output_format & DIFF_FORMAT_DIFFSTAT)
@@ -6616,6 +6595,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)
 	return ignored;
 }
 
+void compute_diffstat(struct diff_options *options,
+		      struct diffstat_t *diffstat,
+		      struct diff_queue_struct *q)
+{
+	int i;
+
+	memset(diffstat, 0, sizeof(struct diffstat_t));
+	for (i = 0; i < q->nr; i++) {
+		struct diff_filepair *p = q->queue[i];
+		if (check_pair_status(p))
+			diff_flush_stat(p, options, diffstat);
+	}
+}
+
 void diff_addremove(struct diff_options *options,
 		    int addremove, unsigned mode,
 		    const struct object_id *oid,
diff --git a/diff.h b/diff.h
index c2c3056810..c791ecc972 100644
--- a/diff.h
+++ b/diff.h
@@ -245,6 +245,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);
 void diff_emit_submodule_pipethrough(struct diff_options *o,
 				     const char *line, int len);
 
+struct diffstat_t {
+	int nr;
+	int alloc;
+	struct diffstat_file {
+		char *from_name;
+		char *name;
+		char *print_name;
+		const char *comments;
+		unsigned is_unmerged:1;
+		unsigned is_binary:1;
+		unsigned is_renamed:1;
+		unsigned is_interesting:1;
+		uintmax_t added, deleted;
+	} **files;
+};
+
 enum color_diff {
 	DIFF_RESET = 0,
 	DIFF_CONTEXT = 1,
@@ -334,6 +350,10 @@ void diff_change(struct diff_options *,
 
 struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);
 
+void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
+		      struct diff_queue_struct *q);
+void free_diffstat_info(struct diffstat_t *diffstat);
+
 #define DIFF_SETUP_REVERSE      	1
 #define DIFF_SETUP_USE_SIZE_CACHE	4
 
-- 
gitgitgadget


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

* [PATCH v4 03/11] built-in add -i: implement the `status` command
  2019-08-27 12:57     ` [PATCH v4 " Johannes Schindelin via GitGitGadget
  2019-08-27 12:57       ` [PATCH v4 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
  2019-08-27 12:57       ` [PATCH v4 02/11] diff: export diffstat interface Daniel Ferreira via GitGitGadget
@ 2019-08-27 12:57       ` Daniel Ferreira via GitGitGadget
  2019-08-27 12:57       ` [PATCH v4 04/11] built-in add -i: refresh the index before running `status` Johannes Schindelin via GitGitGadget
                         ` (8 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Daniel Ferreira via GitGitGadget @ 2019-08-27 12:57 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Daniel Ferreira

From: Daniel Ferreira <bnmvco@gmail.com>

This implements the `status` command of `git add -i`. The data
structures introduced in this commit will be extended as needed later.

At this point, we re-implement only part of the `list_and_choose()`
function of the Perl script `git-add--interactive.perl` and call it
`list()`. It does not yet color anything, or do columns, or allow user
input.

Over the course of the next commits, we will introduce a
`list_and_choose()` function that uses `list()` to display the list of
options and let the user choose one or more of the displayed items. This
will be used to implement the main loop of the built-in `git add -i`, at
which point the new `status` command can actually be used.

Note that we pass the list of items as a `struct item **` as opposed to
a `struct item *`, to allow for the actual items to contain much more
information than merely the name.

Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 266 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 265 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index 482e458dc6..d64206ba1c 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -1,7 +1,271 @@
 #include "cache.h"
 #include "add-interactive.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "refs.h"
+
+struct item {
+	const char *name;
+};
+
+struct list_options {
+	const char *header;
+	void (*print_item)(int i, struct item *item, void *print_item_data);
+	void *print_item_data;
+};
+
+static void list(struct item **list, size_t nr, struct list_options *opts)
+{
+	int i;
+
+	if (!nr)
+		return;
+
+	if (opts->header)
+		printf("%s\n", opts->header);
+
+	for (i = 0; i < nr; i++) {
+		opts->print_item(i, list[i], opts->print_item_data);
+		putchar('\n');
+	}
+}
+
+struct adddel {
+	uintmax_t add, del;
+	unsigned seen:1, binary:1;
+};
+
+struct file_list {
+	struct file_item {
+		struct item item;
+		struct adddel index, worktree;
+	} **file;
+	size_t nr, alloc;
+};
+
+static void add_file_item(struct file_list *list, const char *name)
+{
+	struct file_item *item;
+
+	FLEXPTR_ALLOC_STR(item, item.name, name);
+
+	ALLOC_GROW(list->file, list->nr + 1, list->alloc);
+	list->file[list->nr++] = item;
+}
+
+static void reset_file_list(struct file_list *list)
+{
+	size_t i;
+
+	for (i = 0; i < list->nr; i++)
+		free(list->file[i]);
+	list->nr = 0;
+}
+
+static void release_file_list(struct file_list *list)
+{
+	reset_file_list(list);
+	FREE_AND_NULL(list->file);
+	list->alloc = 0;
+}
+
+static int file_item_cmp(const void *a, const void *b)
+{
+	const struct file_item * const *f1 = a;
+	const struct file_item * const *f2 = b;
+
+	return strcmp((*f1)->item.name, (*f2)->item.name);
+}
+
+struct pathname_entry {
+	struct hashmap_entry ent;
+	size_t index;
+	char pathname[FLEX_ARRAY];
+};
+
+static int pathname_entry_cmp(const void *unused_cmp_data,
+			      const void *entry, const void *entry_or_key,
+			      const void *pathname)
+{
+	const struct pathname_entry *e1 = entry, *e2 = entry_or_key;
+
+	return strcmp(e1->pathname,
+		      pathname ? (const char *)pathname : e2->pathname);
+}
+
+struct collection_status {
+	enum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase;
+
+	const char *reference;
+
+	struct file_list *list;
+	struct hashmap file_map;
+};
+
+static void collect_changes_cb(struct diff_queue_struct *q,
+			       struct diff_options *options,
+			       void *data)
+{
+	struct collection_status *s = data;
+	struct diffstat_t stat = { 0 };
+	int i;
+
+	if (!q->nr)
+		return;
+
+	compute_diffstat(options, &stat, q);
+
+	for (i = 0; i < stat.nr; i++) {
+		const char *name = stat.files[i]->name;
+		int hash = strhash(name);
+		struct pathname_entry *entry;
+		size_t file_index;
+		struct file_item *file;
+		struct adddel *adddel;
+
+		entry = hashmap_get_from_hash(&s->file_map, hash, name);
+		if (entry)
+			file_index = entry->index;
+		else {
+			FLEX_ALLOC_STR(entry, pathname, name);
+			hashmap_entry_init(&entry->ent, hash);
+			entry->index = file_index = s->list->nr;
+			hashmap_add(&s->file_map, &entry->ent);
+
+			add_file_item(s->list, name);
+		}
+		file = s->list->file[file_index];
+
+		adddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;
+		adddel->seen = 1;
+		adddel->add = stat.files[i]->added;
+		adddel->del = stat.files[i]->deleted;
+		if (stat.files[i]->is_binary)
+			adddel->binary = 1;
+	}
+	free_diffstat_info(&stat);
+}
+
+static int get_modified_files(struct repository *r, struct file_list *list,
+			      const struct pathspec *ps)
+{
+	struct object_id head_oid;
+	int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+					     &head_oid, NULL);
+	struct collection_status s = { FROM_WORKTREE };
+
+	if (repo_read_index_preload(r, ps, 0) < 0)
+		return error(_("could not read index"));
+
+	s.list = list;
+	hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);
+
+	for (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {
+		struct rev_info rev;
+		struct setup_revision_opt opt = { 0 };
+
+		opt.def = is_initial ?
+			empty_tree_oid_hex() : oid_to_hex(&head_oid);
+
+		init_revisions(&rev, NULL);
+		setup_revisions(0, NULL, &rev, &opt);
+
+		rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+		rev.diffopt.format_callback = collect_changes_cb;
+		rev.diffopt.format_callback_data = &s;
+
+		if (ps)
+			copy_pathspec(&rev.prune_data, ps);
+
+		if (s.phase == FROM_INDEX)
+			run_diff_index(&rev, 1);
+		else {
+			rev.diffopt.flags.ignore_dirty_submodules = 1;
+			run_diff_files(&rev, 0);
+		}
+	}
+	hashmap_free(&s.file_map, 1);
+
+	/* While the diffs are ordered already, we ran *two* diffs... */
+	QSORT(list->file, list->nr, file_item_cmp);
+
+	return 0;
+}
+
+static void populate_wi_changes(struct strbuf *buf,
+				struct adddel *ad, const char *no_changes)
+{
+	if (ad->binary)
+		strbuf_addstr(buf, _("binary"));
+	else if (ad->seen)
+		strbuf_addf(buf, "+%"PRIuMAX"/-%"PRIuMAX,
+			    (uintmax_t)ad->add, (uintmax_t)ad->del);
+	else
+		strbuf_addstr(buf, no_changes);
+}
+
+struct print_file_item_data {
+	const char *modified_fmt;
+	struct strbuf buf, index, worktree;
+};
+
+static void print_file_item(int i, struct item *item,
+			    void *print_file_item_data)
+{
+	struct file_item *c = (struct file_item *)item;
+	struct print_file_item_data *d = print_file_item_data;
+
+	strbuf_reset(&d->index);
+	strbuf_reset(&d->worktree);
+	strbuf_reset(&d->buf);
+
+	populate_wi_changes(&d->worktree, &c->worktree, _("nothing"));
+	populate_wi_changes(&d->index, &c->index, _("unchanged"));
+	strbuf_addf(&d->buf, d->modified_fmt,
+		    d->index.buf, d->worktree.buf, item->name);
+
+	printf(" %2d: %s", i + 1, d->buf.buf);
+}
+
+static int run_status(struct repository *r, const struct pathspec *ps,
+		      struct file_list *files, struct list_options *opts)
+{
+	reset_file_list(files);
+
+	if (get_modified_files(r, files, ps) < 0)
+		return -1;
+
+	if (files->nr)
+		list((struct item **)files->file, files->nr, opts);
+	putchar('\n');
+
+	return 0;
+}
 
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
-	die(_("No commands are available in the built-in `git add -i` yet!"));
+	struct print_file_item_data print_file_item_data = {
+		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+	};
+	struct list_options opts = {
+		NULL, print_file_item, &print_file_item_data
+	};
+	struct strbuf header = STRBUF_INIT;
+	struct file_list files = { NULL };
+	int res = 0;
+
+	strbuf_addstr(&header, "      ");
+	strbuf_addf(&header, print_file_item_data.modified_fmt,
+		    _("staged"), _("unstaged"), _("path"));
+	opts.header = header.buf;
+
+	res = run_status(r, ps, &files, &opts);
+
+	release_file_list(&files);
+	strbuf_release(&print_file_item_data.buf);
+	strbuf_release(&print_file_item_data.index);
+	strbuf_release(&print_file_item_data.worktree);
+	strbuf_release(&header);
+
+	return res;
 }
-- 
gitgitgadget


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

* [PATCH v4 04/11] built-in add -i: refresh the index before running `status`
  2019-08-27 12:57     ` [PATCH v4 " Johannes Schindelin via GitGitGadget
                         ` (2 preceding siblings ...)
  2019-08-27 12:57       ` [PATCH v4 03/11] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
@ 2019-08-27 12:57       ` Johannes Schindelin via GitGitGadget
  2019-08-27 12:57       ` [PATCH v4 05/11] built-in add -i: color the header in the `status` command Johannes Schindelin via GitGitGadget
                         ` (7 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-08-27 12:57 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

This is what the Perl version does, and therefore it is what the
built-in version should do, too.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c |  4 +++-
 repository.c      | 19 +++++++++++++++++++
 repository.h      |  7 +++++++
 3 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index d64206ba1c..427abe505e 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -259,7 +259,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 		    _("staged"), _("unstaged"), _("path"));
 	opts.header = header.buf;
 
-	res = run_status(r, ps, &files, &opts);
+	repo_refresh_and_write_index(r, REFRESH_QUIET, 1);
+	if (run_status(r, ps, &files, &opts) < 0)
+		res = -1;
 
 	release_file_list(&files);
 	strbuf_release(&print_file_item_data.buf);
diff --git a/repository.c b/repository.c
index 682c239fe3..def35c40fc 100644
--- a/repository.c
+++ b/repository.c
@@ -275,3 +275,22 @@ int repo_hold_locked_index(struct repository *repo,
 		BUG("the repo hasn't been setup");
 	return hold_lock_file_for_update(lf, repo->index_file, flags);
 }
+
+int repo_refresh_and_write_index(struct repository *r,
+				 unsigned int flags, int gentle)
+{
+	struct lock_file lock_file = LOCK_INIT;
+	int fd;
+
+	if (repo_read_index_preload(r, NULL, 0) < 0)
+		return error(_("could not read index"));
+	fd = repo_hold_locked_index(r, &lock_file, 0);
+	if (!gentle && fd < 0)
+		return error(_("could not lock index for writing"));
+	refresh_index(r->index, flags, NULL, NULL, NULL);
+	if (0 <= fd)
+		repo_update_index_if_able(r, &lock_file);
+	rollback_lock_file(&lock_file);
+
+	return 0;
+}
diff --git a/repository.h b/repository.h
index 4fb6a5885f..cf5d5bab48 100644
--- a/repository.h
+++ b/repository.h
@@ -157,5 +157,12 @@ int repo_read_index_unmerged(struct repository *);
  */
 void repo_update_index_if_able(struct repository *, struct lock_file *);
 
+/*
+ * Refresh the index and write it out. If the index file could not be
+ * locked, error out, except in gentle mode. The flags will be passed
+ * through to refresh_index().
+ */
+int repo_refresh_and_write_index(struct repository *r,
+				 unsigned int flags, int gentle);
 
 #endif /* REPOSITORY_H */
-- 
gitgitgadget


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

* [PATCH v4 05/11] built-in add -i: color the header in the `status` command
  2019-08-27 12:57     ` [PATCH v4 " Johannes Schindelin via GitGitGadget
                         ` (3 preceding siblings ...)
  2019-08-27 12:57       ` [PATCH v4 04/11] built-in add -i: refresh the index before running `status` Johannes Schindelin via GitGitGadget
@ 2019-08-27 12:57       ` Johannes Schindelin via GitGitGadget
  2019-08-27 12:57       ` [PATCH v4 07/11] Add a function to determine unique prefixes for a list of strings Slavica Djukic via GitGitGadget
                         ` (6 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-08-27 12:57 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

For simplicity, we only implemented the `status` command without colors.
This patch starts adding color, matching what the Perl script
`git-add--interactive.perl` does.

Original-Patch-By: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 60 ++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 54 insertions(+), 6 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 427abe505e..f5577a80d3 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -1,9 +1,51 @@
 #include "cache.h"
 #include "add-interactive.h"
+#include "color.h"
+#include "config.h"
 #include "diffcore.h"
 #include "revision.h"
 #include "refs.h"
 
+struct add_i_state {
+	struct repository *r;
+	int use_color;
+	char header_color[COLOR_MAXLEN];
+};
+
+static void init_color(struct repository *r, struct add_i_state *s,
+		       const char *slot_name, char *dst,
+		       const char *default_color)
+{
+	char *key = xstrfmt("color.interactive.%s", slot_name);
+	const char *value;
+
+	if (!s->use_color)
+		dst[0] = '\0';
+	else if (repo_config_get_value(r, key, &value) ||
+		 color_parse(value, dst))
+		strlcpy(dst, default_color, COLOR_MAXLEN);
+
+	free(key);
+}
+
+static int init_add_i_state(struct repository *r, struct add_i_state *s)
+{
+	const char *value;
+
+	s->r = r;
+
+	if (repo_config_get_value(r, "color.interactive", &value))
+		s->use_color = -1;
+	else
+		s->use_color =
+			git_config_colorbool("color.interactive", value);
+	s->use_color = want_color(s->use_color);
+
+	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
+
+	return 0;
+}
+
 struct item {
 	const char *name;
 };
@@ -14,7 +56,8 @@ struct list_options {
 	void *print_item_data;
 };
 
-static void list(struct item **list, size_t nr, struct list_options *opts)
+static void list(struct item **list, size_t nr,
+		 struct add_i_state *s, struct list_options *opts)
 {
 	int i;
 
@@ -22,7 +65,8 @@ static void list(struct item **list, size_t nr, struct list_options *opts)
 		return;
 
 	if (opts->header)
-		printf("%s\n", opts->header);
+		color_fprintf_ln(stdout, s->header_color,
+				 "%s", opts->header);
 
 	for (i = 0; i < nr; i++) {
 		opts->print_item(i, list[i], opts->print_item_data);
@@ -227,16 +271,16 @@ static void print_file_item(int i, struct item *item,
 	printf(" %2d: %s", i + 1, d->buf.buf);
 }
 
-static int run_status(struct repository *r, const struct pathspec *ps,
+static int run_status(struct add_i_state *s, const struct pathspec *ps,
 		      struct file_list *files, struct list_options *opts)
 {
 	reset_file_list(files);
 
-	if (get_modified_files(r, files, ps) < 0)
+	if (get_modified_files(s->r, files, ps) < 0)
 		return -1;
 
 	if (files->nr)
-		list((struct item **)files->file, files->nr, opts);
+		list((struct item **)files->file, files->nr, s, opts);
 	putchar('\n');
 
 	return 0;
@@ -244,6 +288,7 @@ static int run_status(struct repository *r, const struct pathspec *ps,
 
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
+	struct add_i_state s = { NULL };
 	struct print_file_item_data print_file_item_data = {
 		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 	};
@@ -254,13 +299,16 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	struct file_list files = { NULL };
 	int res = 0;
 
+	if (init_add_i_state(r, &s))
+		return error("could not parse `add -i` config");
+
 	strbuf_addstr(&header, "      ");
 	strbuf_addf(&header, print_file_item_data.modified_fmt,
 		    _("staged"), _("unstaged"), _("path"));
 	opts.header = header.buf;
 
 	repo_refresh_and_write_index(r, REFRESH_QUIET, 1);
-	if (run_status(r, ps, &files, &opts) < 0)
+	if (run_status(&s, ps, &files, &opts) < 0)
 		res = -1;
 
 	release_file_list(&files);
-- 
gitgitgadget


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

* [PATCH v4 06/11] built-in add -i: implement the main loop
  2019-08-27 12:57     ` [PATCH v4 " Johannes Schindelin via GitGitGadget
                         ` (5 preceding siblings ...)
  2019-08-27 12:57       ` [PATCH v4 07/11] Add a function to determine unique prefixes for a list of strings Slavica Djukic via GitGitGadget
@ 2019-08-27 12:57       ` Johannes Schindelin via GitGitGadget
  2019-08-27 12:57       ` [PATCH v4 08/11] built-in add -i: show unique prefixes of the commands Slavica Djukic via GitGitGadget
                         ` (4 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-08-27 12:57 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

The reason why we did not start with the main loop to begin with is that
it is the first user of `list_and_choose()`, which uses the `list()`
function that we conveniently introduced for use by the `status`
command.

Apart from the "and choose" part, there are more differences between the
way the `status` command calls the `list_and_choose()` function in the
Perl version of `git add -i` compared to the other callers of said
function. The most important ones:

- The list is not only shown, but the user is also asked to make a
  choice, possibly selecting multiple entries.

- The list of items is prefixed with a marker indicating what items have
  been selected, if multi-selection is allowed.

- Initially, for each item a unique prefix (if there exists any within
  the given parameters) is determined, and shown in the list, and
  accepted as a shortcut for the selection.

These features will be implemented later, except the part where the user
can choose a command. At this stage, though, the built-in `git add -i`
still only supports the `status` command, with the remaining commands to
follow over the course of the next commits.

In addition, we also modify `list()` to support displaying the commands
in columns, even if there is currently only one.

The Perl script `git-add--interactive.perl` mixed the purposes of the
"list" and the "and choose" part into the same function. In the C
version, we will keep them separate instead, calling the `list()`
function from the `list_and_choose()` function.

Note that we only have a prompt ending in a single ">" at this stage;
later commits will add commands that display a double ">>" to indicate
that the user is in a different loop than the main one.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 129 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 127 insertions(+), 2 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index f5577a80d3..bbab69d4bc 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -51,6 +51,7 @@ struct item {
 };
 
 struct list_options {
+	int columns;
 	const char *header;
 	void (*print_item)(int i, struct item *item, void *print_item_data);
 	void *print_item_data;
@@ -59,7 +60,7 @@ struct list_options {
 static void list(struct item **list, size_t nr,
 		 struct add_i_state *s, struct list_options *opts)
 {
-	int i;
+	int i, last_lf = 0;
 
 	if (!nr)
 		return;
@@ -70,8 +71,97 @@ static void list(struct item **list, size_t nr,
 
 	for (i = 0; i < nr; i++) {
 		opts->print_item(i, list[i], opts->print_item_data);
+
+		if ((opts->columns) && ((i + 1) % (opts->columns))) {
+			putchar('\t');
+			last_lf = 0;
+		}
+		else {
+			putchar('\n');
+			last_lf = 1;
+		}
+	}
+
+	if (!last_lf)
 		putchar('\n');
+}
+struct list_and_choose_options {
+	struct list_options list_opts;
+
+	const char *prompt;
+};
+
+#define LIST_AND_CHOOSE_ERROR (-1)
+#define LIST_AND_CHOOSE_QUIT  (-2)
+
+/*
+ * Returns the selected index.
+ *
+ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
+ * `LIST_AND_CHOOSE_QUIT` is returned.
+ */
+static ssize_t list_and_choose(struct item **items, size_t nr,
+			       struct add_i_state *s,
+			       struct list_and_choose_options *opts)
+{
+	struct strbuf input = STRBUF_INIT;
+	ssize_t res = LIST_AND_CHOOSE_ERROR;
+
+	for (;;) {
+		char *p, *endp;
+
+		strbuf_reset(&input);
+
+		list(items, nr, s, &opts->list_opts);
+
+		printf("%s%s", opts->prompt, "> ");
+		fflush(stdout);
+
+		if (strbuf_getline(&input, stdin) == EOF) {
+			putchar('\n');
+			res = LIST_AND_CHOOSE_QUIT;
+			break;
+		}
+		strbuf_trim(&input);
+
+		if (!input.len)
+			break;
+
+		p = input.buf;
+		for (;;) {
+			size_t sep = strcspn(p, " \t\r\n,");
+			ssize_t index = -1;
+
+			if (!sep) {
+				if (!*p)
+					break;
+				p++;
+				continue;
+			}
+
+			if (isdigit(*p)) {
+				index = strtoul(p, &endp, 10) - 1;
+				if (endp != p + sep)
+					index = -1;
+			}
+
+			p[sep] = '\0';
+			if (index < 0 || index >= nr)
+				printf(_("Huh (%s)?\n"), p);
+			else {
+				res = index;
+				break;
+			}
+
+			p += sep + 1;
+		}
+
+		if (res != LIST_AND_CHOOSE_ERROR)
+			break;
 	}
+
+	strbuf_release(&input);
+	return res;
 }
 
 struct adddel {
@@ -286,17 +376,40 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	return 0;
 }
 
+static void print_command_item(int i, struct item *item,
+			       void *print_command_item_data)
+{
+	printf(" %2d: %s", i + 1, item->name);
+}
+
+struct command_item {
+	struct item item;
+	int (*command)(struct add_i_state *s, const struct pathspec *ps,
+		       struct file_list *files, struct list_options *opts);
+};
+
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
+	struct list_and_choose_options main_loop_opts = {
+		{ 4, N_("*** Commands ***"), print_command_item, NULL },
+		N_("What now")
+	};
+	struct command_item
+		status = { { "status" }, run_status };
+	struct command_item *commands[] = {
+		&status
+	};
+
 	struct print_file_item_data print_file_item_data = {
 		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 	};
 	struct list_options opts = {
-		NULL, print_file_item, &print_file_item_data
+		0, NULL, print_file_item, &print_file_item_data
 	};
 	struct strbuf header = STRBUF_INIT;
 	struct file_list files = { NULL };
+	ssize_t i;
 	int res = 0;
 
 	if (init_add_i_state(r, &s))
@@ -311,6 +424,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	if (run_status(&s, ps, &files, &opts) < 0)
 		res = -1;
 
+	for (;;) {
+		i = list_and_choose((struct item **)commands,
+				    ARRAY_SIZE(commands), &s, &main_loop_opts);
+		if (i == LIST_AND_CHOOSE_QUIT) {
+			printf(_("Bye.\n"));
+			res = 0;
+			break;
+		}
+		if (i != LIST_AND_CHOOSE_ERROR)
+			res = commands[i]->command(&s, ps, &files, &opts);
+	}
+
 	release_file_list(&files);
 	strbuf_release(&print_file_item_data.buf);
 	strbuf_release(&print_file_item_data.index);
-- 
gitgitgadget


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

* [PATCH v4 07/11] Add a function to determine unique prefixes for a list of strings
  2019-08-27 12:57     ` [PATCH v4 " Johannes Schindelin via GitGitGadget
                         ` (4 preceding siblings ...)
  2019-08-27 12:57       ` [PATCH v4 05/11] built-in add -i: color the header in the `status` command Johannes Schindelin via GitGitGadget
@ 2019-08-27 12:57       ` Slavica Djukic via GitGitGadget
  2019-08-27 12:57       ` [PATCH v4 06/11] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
                         ` (5 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Slavica Djukic via GitGitGadget @ 2019-08-27 12:57 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Slavica Djukic

From: Slavica Djukic <slawica92@hotmail.com>

In the `git add -i` command, we show unique prefixes of the commands and
files, to give an indication what prefix would select them.

Naturally, the C implementation looks a lot different than the Perl
implementation: in Perl, a trie is much easier implemented, while we
already have a pretty neat hashmap implementation in C that we use for
the purpose of storing (not necessarily unique) prefixes.

The idea: for each item that we add, we generate prefixes starting with
the first letter, then the first two letters, then three, etc, until we
find a prefix that is unique (or until the prefix length would be
longer than we want). If we encounter a previously-unique prefix on the
way, we adjust that item's prefix to make it unique again (or we mark it
as having no unique prefix if we failed to find one). These partial
prefixes are stored in a hash map (for quick lookup times).

To make sure that this function works as expected, we add a test using a
special-purpose test helper.

Note: We expect the list of prefix items to be passed in as a list of
pointers rather than as regular list to avoid having to copy information
(the actual items will most likely contain more information than just
the name and the length of the unique prefix, but passing in `struct
prefix_item *` would not allow for that).

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile                   |   2 +
 prefix-map.c               | 123 +++++++++++++++++++++++++++++++++++++
 prefix-map.h               |  29 +++++++++
 t/helper/test-prefix-map.c |  58 +++++++++++++++++
 t/helper/test-tool.c       |   1 +
 t/helper/test-tool.h       |   1 +
 t/t0018-prefix-map.sh      |  10 +++
 7 files changed, 224 insertions(+)
 create mode 100644 prefix-map.c
 create mode 100644 prefix-map.h
 create mode 100644 t/helper/test-prefix-map.c
 create mode 100755 t/t0018-prefix-map.sh

diff --git a/Makefile b/Makefile
index d04daf9fd8..5c141c4609 100644
--- a/Makefile
+++ b/Makefile
@@ -727,6 +727,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o
 TEST_BUILTINS_OBJS += test-parse-options.o
 TEST_BUILTINS_OBJS += test-path-utils.o
 TEST_BUILTINS_OBJS += test-pkt-line.o
+TEST_BUILTINS_OBJS += test-prefix-map.o
 TEST_BUILTINS_OBJS += test-prio-queue.o
 TEST_BUILTINS_OBJS += test-reach.o
 TEST_BUILTINS_OBJS += test-read-cache.o
@@ -945,6 +946,7 @@ LIB_OBJS += patch-ids.o
 LIB_OBJS += path.o
 LIB_OBJS += pathspec.o
 LIB_OBJS += pkt-line.o
+LIB_OBJS += prefix-map.o
 LIB_OBJS += preload-index.o
 LIB_OBJS += pretty.o
 LIB_OBJS += prio-queue.o
diff --git a/prefix-map.c b/prefix-map.c
new file mode 100644
index 0000000000..ff4284bbe3
--- /dev/null
+++ b/prefix-map.c
@@ -0,0 +1,123 @@
+#include "cache.h"
+#include "prefix-map.h"
+
+struct prefix_map_entry {
+	struct hashmap_entry e;
+	const char *name;
+	size_t prefix_length;
+	/* if item is NULL, the prefix is not unique */
+	struct prefix_item *item;
+};
+
+struct prefix_map {
+	struct hashmap map;
+	size_t min_length, max_length;
+};
+
+static int map_cmp(const void *unused_cmp_data,
+		   const void *entry,
+		   const void *entry_or_key,
+		   const void *unused_keydata)
+{
+	const struct prefix_map_entry *a = entry;
+	const struct prefix_map_entry *b = entry_or_key;
+
+	return a->prefix_length != b->prefix_length ||
+		strncmp(a->name, b->name, a->prefix_length);
+}
+
+static void add_prefix_entry(struct hashmap *map, const char *name,
+			     size_t prefix_length, struct prefix_item *item)
+{
+	struct prefix_map_entry *result = xmalloc(sizeof(*result));
+	result->name = name;
+	result->prefix_length = prefix_length;
+	result->item = item;
+	hashmap_entry_init(&result->e, memhash(name, prefix_length));
+	hashmap_add(map, &result->e);
+}
+
+static void init_prefix_map(struct prefix_map *prefix_map,
+			    size_t min_prefix_length, size_t max_prefix_length)
+{
+	hashmap_init(&prefix_map->map, map_cmp, NULL, 0);
+	prefix_map->min_length = min_prefix_length;
+	prefix_map->max_length = max_prefix_length;
+}
+
+static void add_prefix_item(struct prefix_map *prefix_map,
+			    struct prefix_item *item)
+{
+	struct prefix_map_entry e = { { NULL } }, *e2;
+	size_t j;
+
+	e.item = item;
+	e.name = item->name;
+
+	for (j = prefix_map->min_length;
+	     j <= prefix_map->max_length && e.name[j]; j++) {
+		/* Avoid breaking UTF-8 multi-byte sequences */
+		if (!isascii(e.name[j]))
+			break;
+
+		e.prefix_length = j;
+		hashmap_entry_init(&e.e, memhash(e.name, j));
+		e2 = hashmap_get(&prefix_map->map, &e.e, NULL);
+		if (!e2) {
+			/* prefix is unique at this stage */
+			item->prefix_length = j;
+			add_prefix_entry(&prefix_map->map, e.name, j, item);
+			break;
+		}
+
+		if (!e2->item)
+			continue; /* non-unique prefix */
+
+		if (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))
+			BUG("unexpected prefix length: %d != %d (%s != %s)",
+			    (int)j, (int)e2->item->prefix_length,
+			    e.name, e2->name);
+
+		/* skip common prefix */
+		for (; j < prefix_map->max_length && e.name[j]; j++) {
+			if (e.item->name[j] != e2->item->name[j])
+				break;
+			add_prefix_entry(&prefix_map->map, e.name, j + 1,
+					 NULL);
+		}
+
+		/* e2 no longer refers to a unique prefix */
+		if (j < prefix_map->max_length && e2->name[j]) {
+			/* found a new unique prefix for e2's item */
+			e2->item->prefix_length = j + 1;
+			add_prefix_entry(&prefix_map->map, e2->name, j + 1,
+					 e2->item);
+		}
+		else
+			e2->item->prefix_length = 0;
+		e2->item = NULL;
+
+		if (j < prefix_map->max_length && e.name[j]) {
+			/* found a unique prefix for the item */
+			e.item->prefix_length = j + 1;
+			add_prefix_entry(&prefix_map->map, e.name, j + 1,
+					 e.item);
+		} else
+			/* item has no (short enough) unique prefix */
+			e.item->prefix_length = 0;
+
+		break;
+	}
+}
+
+void find_unique_prefixes(struct prefix_item **array, size_t nr,
+			  size_t min_length, size_t max_length)
+{
+	size_t i;
+	struct prefix_map prefix_map;
+
+	init_prefix_map(&prefix_map, min_length, max_length);
+	for (i = 0; i < nr; i++)
+		add_prefix_item(&prefix_map, array[i]);
+	hashmap_free(&prefix_map.map, 1);
+}
diff --git a/prefix-map.h b/prefix-map.h
new file mode 100644
index 0000000000..0c55ee0b2b
--- /dev/null
+++ b/prefix-map.h
@@ -0,0 +1,29 @@
+#ifndef PREFIX_MAP_H
+#define PREFIX_MAP_H
+
+#include "hashmap.h"
+
+struct prefix_item {
+	const char *name;
+	size_t prefix_length;
+};
+
+/*
+ * Given an array of names, find unique prefixes (i.e. the first <n> characters
+ * that uniquely identify the names) and store the lengths of the unique
+ * prefixes in the 'prefix_length' field of the elements of the given array..
+ *
+ * Typically, the `struct prefix_item` information is a field in the actual
+ * item struct; For this reason, the `array` parameter is specified as an array
+ * of pointers to the items.
+ *
+ * The `min_length`/`max_length` parameters define what length the unique
+ * prefixes should have.
+ *
+ * If no unique prefix could be found for a given item, its `prefix_length`
+ * will be set to 0.
+ */
+void find_unique_prefixes(struct prefix_item **array, size_t nr,
+			  size_t min_length, size_t max_length);
+
+#endif
diff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c
new file mode 100644
index 0000000000..3f1c90eaf0
--- /dev/null
+++ b/t/helper/test-prefix-map.c
@@ -0,0 +1,58 @@
+#include "test-tool.h"
+#include "cache.h"
+#include "prefix-map.h"
+
+static size_t test_count, failed_count;
+
+static void check(int succeeded, const char *file, size_t line_no,
+		  const char *fmt, ...)
+{
+	va_list ap;
+
+	test_count++;
+	if (succeeded)
+		return;
+
+	va_start(ap, fmt);
+	fprintf(stderr, "%s:%d: ", file, (int)line_no);
+	vfprintf(stderr, fmt, ap);
+	fputc('\n', stderr);
+	va_end(ap);
+
+	failed_count++;
+}
+
+#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \
+	check(expect == actual, __FILE__, __LINE__, \
+	      "size_t's do not match: %" \
+	      PRIdMAX " != %" PRIdMAX " (%s) (%s)", \
+	      (intmax_t)expect, (intmax_t)actual, #actual, hint)
+
+int cmd__prefix_map(int argc, const char **argv)
+{
+#define NR 5
+	struct prefix_item items[NR] = {
+		{ "unique" },
+		{ "hell" },
+		{ "hello" },
+		{ "wok" },
+		{ "world" },
+	};
+	struct prefix_item *list[NR] = {
+		items, items + 1, items + 2, items + 3, items + 4
+	};
+
+	find_unique_prefixes(list, NR, 1, 3);
+
+#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \
+	EXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \
+			     list[index]->name)
+
+	EXPECT_PREFIX_LENGTH_EQUALS(1, 0);
+	EXPECT_PREFIX_LENGTH_EQUALS(0, 1);
+	EXPECT_PREFIX_LENGTH_EQUALS(0, 2);
+	EXPECT_PREFIX_LENGTH_EQUALS(3, 3);
+	EXPECT_PREFIX_LENGTH_EQUALS(3, 4);
+
+	return !!failed_count;
+}
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index ce7e89028c..34ddf3f8b7 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -41,6 +41,7 @@ static struct test_cmd cmds[] = {
 	{ "parse-options", cmd__parse_options },
 	{ "path-utils", cmd__path_utils },
 	{ "pkt-line", cmd__pkt_line },
+	{ "prefix-map", cmd__prefix_map },
 	{ "prio-queue", cmd__prio_queue },
 	{ "reach", cmd__reach },
 	{ "read-cache", cmd__read_cache },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index f805bb39ae..400854e60d 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -31,6 +31,7 @@ int cmd__online_cpus(int argc, const char **argv);
 int cmd__parse_options(int argc, const char **argv);
 int cmd__path_utils(int argc, const char **argv);
 int cmd__pkt_line(int argc, const char **argv);
+int cmd__prefix_map(int argc, const char **argv);
 int cmd__prio_queue(int argc, const char **argv);
 int cmd__reach(int argc, const char **argv);
 int cmd__read_cache(int argc, const char **argv);
diff --git a/t/t0018-prefix-map.sh b/t/t0018-prefix-map.sh
new file mode 100755
index 0000000000..187fa92aec
--- /dev/null
+++ b/t/t0018-prefix-map.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+test_description='basic tests for prefix map'
+. ./test-lib.sh
+
+test_expect_success 'prefix map' '
+	test-tool prefix-map
+'
+
+test_done
-- 
gitgitgadget


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

* [PATCH v4 08/11] built-in add -i: show unique prefixes of the commands
  2019-08-27 12:57     ` [PATCH v4 " Johannes Schindelin via GitGitGadget
                         ` (6 preceding siblings ...)
  2019-08-27 12:57       ` [PATCH v4 06/11] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
@ 2019-08-27 12:57       ` Slavica Djukic via GitGitGadget
  2019-08-27 12:58       ` [PATCH v4 09/11] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
                         ` (3 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Slavica Djukic via GitGitGadget @ 2019-08-27 12:57 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Slavica Djukic

From: Slavica Djukic <slawica92@hotmail.com>

Just like in the Perl script `git-add--interactive.perl`, for each
command a unique prefix is determined (if there exists any within the
given parameters), and shown in the list, and accepted as a shortcut for
the command.

We use the prefix map implementation that we just added in the previous
commit for that purpose.

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
---
 add-interactive.c | 69 ++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 56 insertions(+), 13 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index bbab69d4bc..12a4c2adb8 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -5,6 +5,7 @@
 #include "diffcore.h"
 #include "revision.h"
 #include "refs.h"
+#include "prefix-map.h"
 
 struct add_i_state {
 	struct repository *r;
@@ -46,18 +47,32 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)
 	return 0;
 }
 
-struct item {
-	const char *name;
-};
+static ssize_t find_unique(const char *string,
+			   struct prefix_item **list, size_t nr)
+{
+	ssize_t found = -1, i;
+
+	for (i = 0; i < nr; i++) {
+		struct prefix_item *item = list[i];
+		if (!starts_with(item->name, string))
+			continue;
+		if (found >= 0)
+			return -1;
+		found = i;
+	}
+
+	return found;
+}
 
 struct list_options {
 	int columns;
 	const char *header;
-	void (*print_item)(int i, struct item *item, void *print_item_data);
+	void (*print_item)(int i, struct prefix_item *item,
+			   void *print_item_data);
 	void *print_item_data;
 };
 
-static void list(struct item **list, size_t nr,
+static void list(struct prefix_item **list, size_t nr,
 		 struct add_i_state *s, struct list_options *opts)
 {
 	int i, last_lf = 0;
@@ -100,13 +115,15 @@ struct list_and_choose_options {
  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
  * `LIST_AND_CHOOSE_QUIT` is returned.
  */
-static ssize_t list_and_choose(struct item **items, size_t nr,
+static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 			       struct add_i_state *s,
 			       struct list_and_choose_options *opts)
 {
 	struct strbuf input = STRBUF_INIT;
 	ssize_t res = LIST_AND_CHOOSE_ERROR;
 
+	find_unique_prefixes(items, nr, 1, 4);
+
 	for (;;) {
 		char *p, *endp;
 
@@ -146,6 +163,9 @@ static ssize_t list_and_choose(struct item **items, size_t nr,
 			}
 
 			p[sep] = '\0';
+			if (index < 0)
+				index = find_unique(p, items, nr);
+
 			if (index < 0 || index >= nr)
 				printf(_("Huh (%s)?\n"), p);
 			else {
@@ -171,7 +191,7 @@ struct adddel {
 
 struct file_list {
 	struct file_item {
-		struct item item;
+		struct prefix_item item;
 		struct adddel index, worktree;
 	} **file;
 	size_t nr, alloc;
@@ -338,12 +358,29 @@ static void populate_wi_changes(struct strbuf *buf,
 		strbuf_addstr(buf, no_changes);
 }
 
+/* filters out prefixes which have special meaning to list_and_choose() */
+static int is_valid_prefix(const char *prefix, size_t prefix_len)
+{
+	return prefix_len && prefix &&
+		/*
+		 * We expect `prefix` to be NUL terminated, therefore this
+		 * `strcspn()` call is okay, even if it might do much more
+		 * work than strictly necessary.
+		 */
+		strcspn(prefix, " \t\r\n,") >= prefix_len &&	/* separators */
+		*prefix != '-' &&				/* deselection */
+		!isdigit(*prefix) &&				/* selection */
+		(prefix_len != 1 ||
+		 (*prefix != '*' &&				/* "all" wildcard */
+		  *prefix != '?'));				/* prompt help */
+}
+
 struct print_file_item_data {
 	const char *modified_fmt;
 	struct strbuf buf, index, worktree;
 };
 
-static void print_file_item(int i, struct item *item,
+static void print_file_item(int i, struct prefix_item *item,
 			    void *print_file_item_data)
 {
 	struct file_item *c = (struct file_item *)item;
@@ -370,20 +407,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 		return -1;
 
 	if (files->nr)
-		list((struct item **)files->file, files->nr, s, opts);
+		list((struct prefix_item **)files->file, files->nr, s, opts);
 	putchar('\n');
 
 	return 0;
 }
 
-static void print_command_item(int i, struct item *item,
+static void print_command_item(int i, struct prefix_item *item,
 			       void *print_command_item_data)
 {
-	printf(" %2d: %s", i + 1, item->name);
+	if (!item->prefix_length ||
+	    !is_valid_prefix(item->name, item->prefix_length))
+		printf(" %2d: %s", i + 1, item->name);
+	else
+		printf(" %3d: [%.*s]%s", i + 1,
+		       (int)item->prefix_length, item->name,
+		       item->name + item->prefix_length);
 }
 
 struct command_item {
-	struct item item;
+	struct prefix_item item;
 	int (*command)(struct add_i_state *s, const struct pathspec *ps,
 		       struct file_list *files, struct list_options *opts);
 };
@@ -425,7 +468,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 		res = -1;
 
 	for (;;) {
-		i = list_and_choose((struct item **)commands,
+		i = list_and_choose((struct prefix_item **)commands,
 				    ARRAY_SIZE(commands), &s, &main_loop_opts);
 		if (i == LIST_AND_CHOOSE_QUIT) {
 			printf(_("Bye.\n"));
-- 
gitgitgadget


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

* [PATCH v4 09/11] built-in add -i: support `?` (prompt help)
  2019-08-27 12:57     ` [PATCH v4 " Johannes Schindelin via GitGitGadget
                         ` (7 preceding siblings ...)
  2019-08-27 12:57       ` [PATCH v4 08/11] built-in add -i: show unique prefixes of the commands Slavica Djukic via GitGitGadget
@ 2019-08-27 12:58       ` Johannes Schindelin via GitGitGadget
  2019-08-27 12:58       ` [PATCH v4 10/11] built-in add -i: use color in the main loop Slavica Djukic via GitGitGadget
                         ` (2 subsequent siblings)
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-08-27 12:58 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

With this change, we print out the same colored help text that the
Perl-based `git add -i` prints in the main loop when question mark is
entered.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index 12a4c2adb8..710317e599 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -11,6 +11,7 @@ struct add_i_state {
 	struct repository *r;
 	int use_color;
 	char header_color[COLOR_MAXLEN];
+	char help_color[COLOR_MAXLEN];
 };
 
 static void init_color(struct repository *r, struct add_i_state *s,
@@ -43,6 +44,7 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)
 	s->use_color = want_color(s->use_color);
 
 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
+	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
 
 	return 0;
 }
@@ -104,6 +106,7 @@ struct list_and_choose_options {
 	struct list_options list_opts;
 
 	const char *prompt;
+	void (*print_help)(struct add_i_state *s);
 };
 
 #define LIST_AND_CHOOSE_ERROR (-1)
@@ -144,6 +147,11 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 		if (!input.len)
 			break;
 
+		if (!strcmp(input.buf, "?")) {
+			opts->print_help(s);
+			continue;
+		}
+
 		p = input.buf;
 		for (;;) {
 			size_t sep = strcspn(p, " \t\r\n,");
@@ -431,12 +439,24 @@ struct command_item {
 		       struct file_list *files, struct list_options *opts);
 };
 
+static void command_prompt_help(struct add_i_state *s)
+{
+	const char *help_color = s->help_color;
+	color_fprintf_ln(stdout, help_color, "%s", _("Prompt help:"));
+	color_fprintf_ln(stdout, help_color, "1          - %s",
+			 _("select a numbered item"));
+	color_fprintf_ln(stdout, help_color, "foo        - %s",
+			 _("select item based on unique prefix"));
+	color_fprintf_ln(stdout, help_color, "           - %s",
+			 _("(empty) select nothing"));
+}
+
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
 	struct list_and_choose_options main_loop_opts = {
 		{ 4, N_("*** Commands ***"), print_command_item, NULL },
-		N_("What now")
+		N_("What now"), command_prompt_help
 	};
 	struct command_item
 		status = { { "status" }, run_status };
-- 
gitgitgadget


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

* [PATCH v4 11/11] built-in add -i: implement the `help` command
  2019-08-27 12:57     ` [PATCH v4 " Johannes Schindelin via GitGitGadget
                         ` (9 preceding siblings ...)
  2019-08-27 12:58       ` [PATCH v4 10/11] built-in add -i: use color in the main loop Slavica Djukic via GitGitGadget
@ 2019-08-27 12:58       ` Johannes Schindelin via GitGitGadget
  2019-11-04 12:15       ` [PATCH v5 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  11 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-08-27 12:58 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Johannes Schindelin

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

This imitates the code to show the help text from the Perl script
`git-add--interactive.perl` in the built-in version.

To make sure that it renders exactly like the Perl version of `git add
-i`, we also add a test case for that to `t3701-add-interactive.sh`.

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c          | 26 ++++++++++++++++++++++++--
 t/t3701-add-interactive.sh | 25 +++++++++++++++++++++++++
 2 files changed, 49 insertions(+), 2 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index a343195a67..765455a3fc 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -429,6 +429,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	return 0;
 }
 
+static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,
+		    struct file_list *unused_files,
+		    struct list_options *unused_opts)
+{
+	color_fprintf_ln(stdout, s->help_color, "status        - %s",
+			 _("show paths with changes"));
+	color_fprintf_ln(stdout, s->help_color, "update        - %s",
+			 _("add working tree state to the staged set of changes"));
+	color_fprintf_ln(stdout, s->help_color, "revert        - %s",
+			 _("revert staged set of changes back to the HEAD version"));
+	color_fprintf_ln(stdout, s->help_color, "patch         - %s",
+			 _("pick hunks and update selectively"));
+	color_fprintf_ln(stdout, s->help_color, "diff          - %s",
+			 _("view diff between HEAD and index"));
+	color_fprintf_ln(stdout, s->help_color, "add untracked - %s",
+			 _("add contents of untracked files to the staged set of changes"));
+
+	return 0;
+}
+
 struct print_command_item_data {
 	const char *color, *reset;
 };
@@ -474,9 +494,11 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 		N_("What now"), command_prompt_help
 	};
 	struct command_item
-		status = { { "status" }, run_status };
+		status = { { "status" }, run_status },
+		help = { { "help" }, run_help };
 	struct command_item *commands[] = {
-		&status
+		&status,
+		&help
 	};
 
 	struct print_file_item_data print_file_item_data = {
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 69991a3168..cf67756b85 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' '
 	test_write_lines a b a b a a b a b a >expect &&
 	test_cmp expect a
 '
+
+test_expect_success 'show help from add--helper' '
+	git reset --hard &&
+	cat >expect <<-EOF &&
+
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>
+	<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>
+	<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>
+	<BOLD;RED>patch         - pick hunks and update selectively<RESET>
+	<BOLD;RED>diff          - view diff between HEAD and index<RESET>
+	<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>>$SP
+	Bye.
+	EOF
+	test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&
+	test_decode_color <actual.colored >actual &&
+	test_i18ncmp expect actual
+'
+
 test_done
-- 
gitgitgadget

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

* [PATCH v4 10/11] built-in add -i: use color in the main loop
  2019-08-27 12:57     ` [PATCH v4 " Johannes Schindelin via GitGitGadget
                         ` (8 preceding siblings ...)
  2019-08-27 12:58       ` [PATCH v4 09/11] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
@ 2019-08-27 12:58       ` Slavica Djukic via GitGitGadget
  2019-08-27 12:58       ` [PATCH v4 11/11] built-in add -i: implement the `help` command Johannes Schindelin via GitGitGadget
  2019-11-04 12:15       ` [PATCH v5 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  11 siblings, 0 replies; 124+ messages in thread
From: Slavica Djukic via GitGitGadget @ 2019-08-27 12:58 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Junio C Hamano, Slavica Djukic

From: Slavica Djukic <slawica92@hotmail.com>

The error messages as well as the unique prefixes are colored in `git
add -i` by default; We need to do the same in the built-in version.

Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 37 ++++++++++++++++++++++++++++++++-----
 1 file changed, 32 insertions(+), 5 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 710317e599..a343195a67 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -12,6 +12,9 @@ struct add_i_state {
 	int use_color;
 	char header_color[COLOR_MAXLEN];
 	char help_color[COLOR_MAXLEN];
+	char prompt_color[COLOR_MAXLEN];
+	char error_color[COLOR_MAXLEN];
+	char reset_color[COLOR_MAXLEN];
 };
 
 static void init_color(struct repository *r, struct add_i_state *s,
@@ -45,6 +48,9 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)
 
 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
 	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
+	init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE);
+	init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED);
+	init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET);
 
 	return 0;
 }
@@ -134,7 +140,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 
 		list(items, nr, s, &opts->list_opts);
 
-		printf("%s%s", opts->prompt, "> ");
+		color_fprintf(stdout, s->prompt_color, "%s", opts->prompt);
+		fputs("> ", stdout);
 		fflush(stdout);
 
 		if (strbuf_getline(&input, stdin) == EOF) {
@@ -175,7 +182,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
 				index = find_unique(p, items, nr);
 
 			if (index < 0 || index >= nr)
-				printf(_("Huh (%s)?\n"), p);
+				color_fprintf_ln(stdout, s->error_color,
+						 _("Huh (%s)?"), p);
 			else {
 				res = index;
 				break;
@@ -421,15 +429,21 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	return 0;
 }
 
+struct print_command_item_data {
+	const char *color, *reset;
+};
+
 static void print_command_item(int i, struct prefix_item *item,
 			       void *print_command_item_data)
 {
+	struct print_command_item_data *d = print_command_item_data;
+
 	if (!item->prefix_length ||
 	    !is_valid_prefix(item->name, item->prefix_length))
 		printf(" %2d: %s", i + 1, item->name);
 	else
-		printf(" %3d: [%.*s]%s", i + 1,
-		       (int)item->prefix_length, item->name,
+		printf(" %2d: %s%.*s%s%s", i + 1,
+		       d->color, (int)item->prefix_length, item->name, d->reset,
 		       item->name + item->prefix_length);
 }
 
@@ -454,8 +468,9 @@ static void command_prompt_help(struct add_i_state *s)
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
+	struct print_command_item_data data;
 	struct list_and_choose_options main_loop_opts = {
-		{ 4, N_("*** Commands ***"), print_command_item, NULL },
+		{ 4, N_("*** Commands ***"), print_command_item, &data },
 		N_("What now"), command_prompt_help
 	};
 	struct command_item
@@ -478,6 +493,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	if (init_add_i_state(r, &s))
 		return error("could not parse `add -i` config");
 
+	/*
+	 * When color was asked for, use the prompt color for
+	 * highlighting, otherwise use square brackets.
+	 */
+	if (s.use_color) {
+		data.color = s.prompt_color;
+		data.reset = s.reset_color;
+	} else {
+		data.color = "[";
+		data.reset = "]";
+	}
+
 	strbuf_addstr(&header, "      ");
 	strbuf_addf(&header, print_file_item_data.modified_fmt,
 		    _("staged"), _("unstaged"), _("path"));
-- 
gitgitgadget


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

* Re: [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`
  2019-08-26 21:26         ` Johannes Schindelin
@ 2019-08-27 22:25           ` Junio C Hamano
  2019-08-28 15:06             ` Johannes Schindelin
  0 siblings, 1 reply; 124+ messages in thread
From: Junio C Hamano @ 2019-08-27 22:25 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

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

> Besides, I really hope that this would be only temporary,...

Oh, no question about it.  This should be temporary knob.

I still do worry about giving a bad example for others to copy.
People tend to copy & paste without thinking.  Either we come up
with and use a two-level name, or we add a comment to explain to
developers (not users---as this is merely a temporary thing) why
they should never follow suit using three-level names for things
like this one written in big red letters, or something, then perhaps
we won't have to worry about too much?  I dunno.

>> > +	if (use_builtin_add_i == 1 && !patch_mode)
>> > +		return !!run_add_i(the_repository, pathspec);
>>
>> I am hoping that eventually "add -p" will also be routed to the new
>> codepath.  Would it make sense to have "&& !patch_mode" here,
>> especially at this step where run_add_i() won't do anything useful
>> anyway yet?
>
> The `&& !patch_mode` is here to allow for a gradual adoption of the
> built-in parts. ...

Ah, so "add.usebuiltin = interactive patch" can (eventually) choose
to use the C code for both while "add.usebuiltin = interactive"
would not use it for the patch mode, or something?  Or even

	add.interactive.usebuiltin = yes
	add.patch.usebuiltin = no

perhaps?

> Of course, eventually this will be handled.

Yup, again, the knob is merely temporary.

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

* Re: [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`
  2019-08-27 22:25           ` Junio C Hamano
@ 2019-08-28 15:06             ` Johannes Schindelin
  2019-08-28 15:37               ` Junio C Hamano
  0 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin @ 2019-08-28 15:06 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

Hi Junio,

On Tue, 27 Aug 2019, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
> > Besides, I really hope that this would be only temporary,...
>
> Oh, no question about it.  This should be temporary knob.
>
> I still do worry about giving a bad example for others to copy.
> People tend to copy & paste without thinking.  Either we come up
> with and use a two-level name, or we add a comment to explain to
> developers (not users---as this is merely a temporary thing) why
> they should never follow suit using three-level names for things
> like this one written in big red letters, or something, then perhaps
> we won't have to worry about too much?  I dunno.
>
> >> > +	if (use_builtin_add_i == 1 && !patch_mode)
> >> > +		return !!run_add_i(the_repository, pathspec);
> >>
> >> I am hoping that eventually "add -p" will also be routed to the new
> >> codepath.  Would it make sense to have "&& !patch_mode" here,
> >> especially at this step where run_add_i() won't do anything useful
> >> anyway yet?
> >
> > The `&& !patch_mode` is here to allow for a gradual adoption of the
> > built-in parts. ...
>
> Ah, so "add.usebuiltin = interactive patch" can (eventually) choose
> to use the C code for both while "add.usebuiltin = interactive"
> would not use it for the patch mode, or something?  Or even
>
> 	add.interactive.usebuiltin = yes

This is what I had in mind.

> 	add.patch.usebuiltin = no

And this is what I should have done ;-)

But maybe I should do

	add.useBuiltin = wheneverPossible

?

Ciao,
Dscho

>
> perhaps?
>
> > Of course, eventually this will be handled.
>
> Yup, again, the knob is merely temporary.


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

* Re: [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`
  2019-08-28 15:06             ` Johannes Schindelin
@ 2019-08-28 15:37               ` Junio C Hamano
  0 siblings, 0 replies; 124+ messages in thread
From: Junio C Hamano @ 2019-08-28 15:37 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

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

>> Ah, so "add.usebuiltin = interactive patch" can (eventually) choose
>> to use the C code for both while "add.usebuiltin = interactive"
>> would not use it for the patch mode, or something?  Or even
>>
>> 	add.interactive.usebuiltin = yes
>
> This is what I had in mind.
>
>> 	add.patch.usebuiltin = no
>
> And this is what I should have done ;-)
>
> But maybe I should do
>
> 	add.useBuiltin = wheneverPossible

Ah, either is fine, I think, but because this is meant to be
temporary and not advertised to end-users for purposes other than
purely an escape hatch, I would not spend too much engineering
effort (iow, do the easiest to add and then easiest to later yank
out).

Thanks.


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

* Re: [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings
  2019-08-27 12:14         ` Johannes Schindelin
@ 2019-08-28 16:30           ` SZEDER Gábor
  2019-08-28 16:34             ` [PATCH] [PoC] A simpler find_unique_prefixes() implementation SZEDER Gábor
  0 siblings, 1 reply; 124+ messages in thread
From: SZEDER Gábor @ 2019-08-28 16:30 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Slavica Djukic via GitGitGadget, git, Jeff Hostetler, Jeff King,
	Junio C Hamano, Slavica Djukic

On Tue, Aug 27, 2019 at 02:14:06PM +0200, Johannes Schindelin wrote:
> Hi Gábor,
> 
> On Sat, 24 Aug 2019, SZEDER Gábor wrote:
> 
> > On Tue, Jul 16, 2019 at 07:58:42AM -0700, Slavica Djukic via GitGitGadget wrote:
> > > In the `git add -i` command, we show unique prefixes of the commands and
> > > files, to give an indication what prefix would select them.
> > >
> > > Naturally, the C implementation looks a lot different than the Perl
> > > implementation: in Perl, a trie is much easier implemented, while we
> > > already have a pretty neat hashmap implementation in C that we use for
> > > the purpose of storing (not necessarily unique) prefixes.
> > >
> > > The idea: for each item that we add, we generate prefixes starting with
> > > the first letter, then the first two letters, then three, etc, until we
> > > find a prefix that is unique (or until the prefix length would be
> > > longer than we want). If we encounter a previously-unique prefix on the
> > > way, we adjust that item's prefix to make it unique again (or we mark it
> > > as having no unique prefix if we failed to find one). These partial
> > > prefixes are stored in a hash map (for quick lookup times).
> > >
> > > To make sure that this function works as expected, we add a test using a
> > > special-purpose test helper that was added for that purpose.
> > >
> > > Note: We expect the list of prefix items to be passed in as a list of
> > > pointers rather than as regular list to avoid having to copy information
> > > (the actual items will most likely contain more information than just
> > > the name and the length of the unique prefix, but passing in `struct
> > > prefix_item *` would not allow for that).
> >
> > > diff --git a/prefix-map.c b/prefix-map.c
> > > new file mode 100644
> > > index 0000000000..747ddb4ebc
> > > --- /dev/null
> > > +++ b/prefix-map.c
> > > @@ -0,0 +1,109 @@
> > > +#include "cache.h"
> > > +#include "prefix-map.h"
> > > +
> > > +static int map_cmp(const void *unused_cmp_data,
> > > +		   const void *entry,
> > > +		   const void *entry_or_key,
> > > +		   const void *unused_keydata)
> > > +{
> > > +	const struct prefix_map_entry *a = entry;
> > > +	const struct prefix_map_entry *b = entry_or_key;
> > > +
> > > +	return a->prefix_length != b->prefix_length ||
> > > +		strncmp(a->name, b->name, a->prefix_length);
> > > +}
> > > +
> > > +static void add_prefix_entry(struct hashmap *map, const char *name,
> > > +			     size_t prefix_length, struct prefix_item *item)
> > > +{
> > > +	struct prefix_map_entry *result = xmalloc(sizeof(*result));
> > > +	result->name = name;
> > > +	result->prefix_length = prefix_length;
> > > +	result->item = item;
> > > +	hashmap_entry_init(result, memhash(name, prefix_length));
> > > +	hashmap_add(map, result);
> > > +}
> > > +
> > > +static void init_prefix_map(struct prefix_map *prefix_map,
> > > +			    int min_prefix_length, int max_prefix_length)
> > > +{
> > > +	hashmap_init(&prefix_map->map, map_cmp, NULL, 0);
> > > +	prefix_map->min_length = min_prefix_length;
> > > +	prefix_map->max_length = max_prefix_length;
> > > +}
> > > +
> > > +static void add_prefix_item(struct prefix_map *prefix_map,
> > > +			    struct prefix_item *item)
> > > +{
> > > +	struct prefix_map_entry e = { { NULL } }, *e2;
> > > +	int j;
> > > +
> > > +	e.item = item;
> > > +	e.name = item->name;
> > > +
> > > +	for (j = prefix_map->min_length;
> > > +	     j <= prefix_map->max_length && e.name[j]; j++) {
> > > +		/* Avoid breaking UTF-8 multi-byte sequences */

Ok, that's a good idea, but...

> > > +		if (!isascii(e.name[j]))
> > > +			break;

This doesn't seem right.  It just breaks the loop, potentially leaving
'item->prefix_length' set from the previous iteration.  Try the two
strings "123" and "12ő", in this order: they both get prefix_length =
3, i.e. the prefix ends between the two bytes of the multi-byte
character.

Furthermore, neither the docstring of find_unique_prefixes() nor the
commit message mention what is supposed to happen with paths with
multi-byte characters.  I think at least one of them should.

> > > +
> > > +		e.prefix_length = j;
> > > +		hashmap_entry_init(&e, memhash(e.name, j));
> > > +		e2 = hashmap_get(&prefix_map->map, &e, NULL);
> > > +		if (!e2) {
> > > +			/* prefix is unique at this stage */
> > > +			item->prefix_length = j;
> > > +			add_prefix_entry(&prefix_map->map, e.name, j, item);
> > > +			break;
> > > +		}
> > > +
> > > +		if (!e2->item)
> > > +			continue; /* non-unique prefix */
> > > +
> > > +		if (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))
> > > +			BUG("unexpected prefix length: %d != %d (%s != %s)",
> > > +			    j, (int)e2->item->prefix_length, e.name, e2->name);
> > > +
> > > +		/* skip common prefix */
> > > +		for (; j < prefix_map->max_length && e.name[j]; j++) {
> > > +			if (e.item->name[j] != e2->item->name[j])
> > > +				break;
> > > +			add_prefix_entry(&prefix_map->map, e.name, j + 1,
> > > +					 NULL);
> > > +		}
> > > +
> > > +		/* e2 no longer refers to a unique prefix */
> > > +		if (j < prefix_map->max_length && e2->name[j]) {
> > > +			/* found a new unique prefix for e2's item */
> > > +			e2->item->prefix_length = j + 1;
> > > +			add_prefix_entry(&prefix_map->map, e2->name, j + 1,
> > > +					 e2->item);
> > > +		}
> > > +		else
> > > +			e2->item->prefix_length = 0;
> > > +		e2->item = NULL;
> > > +
> > > +		if (j < prefix_map->max_length && e.name[j]) {
> > > +			/* found a unique prefix for the item */
> > > +			e.item->prefix_length = j + 1;
> > > +			add_prefix_entry(&prefix_map->map, e.name, j + 1,
> > > +					 e.item);
> > > +		} else
> > > +			/* item has no (short enough) unique prefix */
> > > +			e.item->prefix_length = 0;
> > > +
> > > +		break;
> > > +	}
> > > +}
> > > +
> > > +void find_unique_prefixes(struct prefix_item **list, size_t nr,
> > > +			  int min_length, int max_length)
> > > +{
> > > +	int i;
> > > +	struct prefix_map prefix_map;
> > > +
> > > +	init_prefix_map(&prefix_map, min_length, max_length);
> > > +	for (i = 0; i < nr; i++)
> > > +		add_prefix_item(&prefix_map, list[i]);
> > > +	hashmap_free(&prefix_map.map, 1);
> > > +}
> >
> > Between the commit message, the in-code comment, the names of the new
> > files, and implementation I was left somewhat confused about what this
> > is about and how it works.  TBH, I didn't even try to understand how
> > all the above works, in particular the add_prefix_item() function.
> 
> Let me try to explain it here, and maybe you can help me by suggesting
> an improved commit message and/or code comments?
> 
> The problem is this: given a set of items with labels (e.g. file names),
> find, for each item, the unique prefix that identifies it. Example:
> given the files `hello.txt`, `heaven.txt` and `hell.txt`, the items'
> unique prefixes would be `hello`, `hea` and `hell.`, respectively.
> 
> In `git add -i`, we actually only want to allow alphanumerical prefixes,
> and we also want at least one, and at most three characters, so only the
> second item would have an admissible unique prefix: `hea`.

You say "at most three characters", but I notive that the call added
to 'add-interactive.c' in a later commit specifies 4.

I wonder what's the reason for the minimum prefix length, though.  I
mean, you can't really have a unique prefix shorter than one
character...

> > However, I think it would be much-much simpler to first sort (a copy
> > of?) the array of prefix item pointers based on their 'name' field,
> > and then look for a unique prefix in each neighboring pair.  Perhaps
> > it would even be faster, because it doesn't have to allocate a bunch
> > of hashmap items, though I don't think that it matters much in
> > practice (i.e. I expect the number of items to be fairly small;
> > presumably nobody will run interactive add after a mass refactoring
> > modifying thousands of files).
> 
> The time complexity of the sorted list would be O(n*log(n)), while the
> hashmap-based complexity would be an amortized O(n).

Oh, so you don't know how/why it works, either!? ;)

This hashmap-based implementation calls hashmap_get() approx.
n * (max_length - min_length) times.

Furthermore, depending on the strings and 'max_length', it can call
add_prefix_entry() more than n times.  E.g. the six strings "a1",
"a2", "ab1", "ab2", "abc1", "abc2", in this order, and max_length=4
result in 9 add_prefix_entry() calls.  And each add_prefix_entry()
call involves a memory allocation.

Ok, that's theory, let's see practice.

For a (very) quick'n'dirty performance test I replaced those five
items in 't/helper/test-prefix-map.c' with the output of:

  git -C ..../webkit.git/ ls-tree -r --name-only HEAD | sed -e 's/^/{ "/; s/$/" },/'

That's 280734 real paths.  Then a simple 'time test-tool prefix-map'
with the hashmap-based implementation ran for 0.055s, while using my
PoC sort-based algorithm it ran for 0.063s.  So while the
hashmap-based implementation is indeed faster, they are both well
below 0.1s, so in the context of 'git add -i' the performance
difference between the two approaches doesn't really matter.  

If we look beyond 'git add -i's requirements, and specify larger
max_length values, the hashmap-based implementation gets progressively
slower.  Already at max_length=5 it's a tad slower than the sort-based
implementation, and at 9 it takes twice as long.

The current hashmap-based implementation is ~50% more lines than my
sort-based PoC.  I'll send it out in a minute.

> And yes, you would not _want_ to run interactive add after a mass
> refactoring. But it happens. It happens to me more times than I care to
> admit. And you know what? I really appreciate that even the Perl version
> is relatively snappy in those circumstances.
> 
> > > diff --git a/prefix-map.h b/prefix-map.h
> > > new file mode 100644
> > > index 0000000000..ce3b8a4a32
> > > --- /dev/null
> > > +++ b/prefix-map.h
> > > @@ -0,0 +1,40 @@
> > > +#ifndef PREFIX_MAP_H
> > > +#define PREFIX_MAP_H
> > >
> > > +#include "hashmap.h"
> > > +
> > > +struct prefix_item {
> > > +	const char *name;
> > > +	size_t prefix_length;
> > > +};
> >
> > This struct is part of find_unique_prefixes()'s signature, good.
> >
> > > +struct prefix_map_entry {
> > > +	struct hashmap_entry e;
> > > +	const char *name;
> > > +	size_t prefix_length;
> > > +	/* if item is NULL, the prefix is not unique */
> > > +	struct prefix_item *item;
> > > +};
> > > +
> > > +struct prefix_map {
> > > +	struct hashmap map;
> > > +	int min_length, max_length;
> > > +};
> >
> > However, neither of these two structs nor the hashmap appear in the
> > function's signature, but are all implementation details.  Therefore,
> > they should not be defined and included here in the header but in the
> > .c source file.  (But as mentioned above, I think this could be
> > implemented much simpler without these data structures.)
> 
> Right you are!
> 
> > Furthermore, this is not a map.
> > A map, in general, is a container of key-value pairs that allows
> > efficient insertion, removal and lookup.  This so-called prefix_map
> > does none of that, so it should not be called a map.
> 
> What would you call it instead?

Well, I would rather get rid of it in the first place :)

In my PoC I called the files 'unique-prefix.{c,h}'.

> > > +/*
> > > + * Find unique prefixes in a given list of strings.
> >
> > ... and stores the length of the unique prefixes in the
> > 'prefix_length' field of the elements of the given array.
> 
> Good idea. I changed it to also explain what is meant by "unique
> prefix":
> 
>  * Given a list of names, find unique prefixes (i.e. the first <n> characters
>  * that uniquely identify the names) and store the lengths of the unique
>  * prefixes in the 'prefix_length' field of the elements of the given array..

Sounds good.

> > > + *
> > > + * Typically, the `struct prefix_item` information will be but a field in the

> > > + * actual item struct; For this reason, the `list` parameter is specified as a
> > > + * list of pointers to the items.
> > > + *
> > > + * The `min_length`/`max_length` parameters define what length the unique
> > > + * prefixes should have.
> > > + *
> > > + * If no unique prefix could be found for a given item, its `prefix_length`
> > > + * will be set to 0.

I ran into cases where the hashmap-based implementation didn't set
'prefix_length' to 0 when it, in my opinion, should have, but left it
untouched.  Just try any string shorter than 'min_length'.

May or may not be related: try "ab", "ab", "abc", in this order: both
"ab" get their 'prefix_length' set to 0, as they should, but the
'prefix_length' of "abc" is left untouched, although it should be set
to 3.

Hmm, now that I've mentioned "in this order" three times already:
another benefit of the sort-based approach is that the order of
elements doesn't matter.

> > > + */
> > > +void find_unique_prefixes(struct prefix_item **list, size_t nr,
> >
> > The first argument is not a list but an array.
> 
> Indeed.
> 
> > > +			  int min_length, int max_length);
> >
> > size_t, perhaps?  These are closely related to
> > 'prefix_item.prefix_length', which is already (rightfully) size_t.
> 
> True.
> 
> > > +#endif
> > > diff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c
> > > new file mode 100644
> > > index 0000000000..3f1c90eaf0
> > > --- /dev/null
> > > +++ b/t/helper/test-prefix-map.c
> > > @@ -0,0 +1,58 @@
> > > +#include "test-tool.h"
> > > +#include "cache.h"
> > > +#include "prefix-map.h"
> > > +
> > > +static size_t test_count, failed_count;
> > > +
> > > +static void check(int succeeded, const char *file, size_t line_no,
> > > +		  const char *fmt, ...)
> > > +{
> > > +	va_list ap;
> > > +
> > > +	test_count++;
> > > +	if (succeeded)
> > > +		return;
> > > +
> > > +	va_start(ap, fmt);
> > > +	fprintf(stderr, "%s:%d: ", file, (int)line_no);
> > > +	vfprintf(stderr, fmt, ap);
> > > +	fputc('\n', stderr);
> > > +	va_end(ap);
> > > +
> > > +	failed_count++;
> > > +}
> > > +
> > > +#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \
> > > +	check(expect == actual, __FILE__, __LINE__, \
> > > +	      "size_t's do not match: %" \
> > > +	      PRIdMAX " != %" PRIdMAX " (%s) (%s)", \
> > > +	      (intmax_t)expect, (intmax_t)actual, #actual, hint)
> > > +
> > > +int cmd__prefix_map(int argc, const char **argv)
> > > +{
> > > +#define NR 5
> > > +	struct prefix_item items[NR] = {
> >
> > You don't have to tell the compiler how many elements this array will
> > contain, it will figure that out on its own.
> >
> > > +		{ "unique" },
> > > +		{ "hell" },
> > > +		{ "hello" },
> > > +		{ "wok" },
> > > +		{ "world" },
> > > +	};
> > > +	struct prefix_item *list[NR] = {
> >
> > Likewise.
> 
> That is correct.
> 
> What the compiler _cannot_ figure out, on its own, however, is that
> `items` and `list` _need_ to contain the same number of items.
> 
> Hence the need for `NR`.

OK, that's a good point.  I'd still like to get rid of that NR with
something like this instead:

  struct prefix_item items[] = { .... };
  struct prefix_item *list[ARRAY_SIZE(items)];
  for (i = 0; i < ARRAY_SIZE(items); i++) {
      list[i] = &items[i];
      list[i]->prefix_length = 12345; /* dummy */
  }
  find_unique_prefixes(list, ARRAY_SIZE(list), 0, 3);

Note the dummy value, to catch cases when 'prefix_length' is left
untouched.

This made this test tool more convenient to use, because I didn't have
to fiddle with NR and, worse, with those 'items, items+1, items+N'.

It's still a bit PITA (having to re-build each time I change the test
vector), though, compared to driving it from stdin and verifying it
through its stdout.

> > > +		items, items + 1, items + 2, items + 3, items + 4
> > > +	};
> > > +
> > > +	find_unique_prefixes(list, NR, 1, 3);
> >
> > This could be find_unique_prefixes(list, ARRAY_SIZE(list), 1, 3), and
> > then there is no need for that NR macro anymore.
> >
> > > +
> > > +#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \
> > > +	EXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \
> > > +			     list[index]->name)
> > > +
> > > +	EXPECT_PREFIX_LENGTH_EQUALS(1, 0);
> > > +	EXPECT_PREFIX_LENGTH_EQUALS(0, 1);
> > > +	EXPECT_PREFIX_LENGTH_EQUALS(0, 2);
> > > +	EXPECT_PREFIX_LENGTH_EQUALS(3, 3);
> > > +	EXPECT_PREFIX_LENGTH_EQUALS(3, 4);
> > > +
> > > +	return !!failed_count;
> > > +}
> 
> Thank you for your review!
> Dscho


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

* [PATCH] [PoC] A simpler find_unique_prefixes() implementation
  2019-08-28 16:30           ` SZEDER Gábor
@ 2019-08-28 16:34             ` SZEDER Gábor
  2019-08-30 20:12               ` Johannes Schindelin
  0 siblings, 1 reply; 124+ messages in thread
From: SZEDER Gábor @ 2019-08-28 16:34 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Jeff Hostetler, Jeff King, Junio C Hamano,
	Slavica Djukic, Slavica Djukic via GitGitGadget,
	SZEDER Gábor

Implement find_unique_prefixes() by sorting the prefix items
lexicographically and then looking for unique prefixes in adjacent
pairs.  It's definitely shorter than the current hashmap-based
implementation (101 vs 149 lines), subjectively easier to follow, and
hopefully less buggy (some of the new elements in the test trigger
failure with the hashmap).

TODO: deal multi-byte caracter sequences, make the test tool more
      developer-friendly
---
 Makefile                   |   2 +-
 add-interactive.c          |   2 +-
 prefix-map.c               | 109 -------------------------------------
 prefix-map.h               |  40 --------------
 t/helper/test-prefix-map.c |  31 +++++++----
 unique-prefix.c            |  73 +++++++++++++++++++++++++
 unique-prefix.h            |  28 ++++++++++
 7 files changed, 124 insertions(+), 161 deletions(-)
 delete mode 100644 prefix-map.c
 delete mode 100644 prefix-map.h
 create mode 100644 unique-prefix.c
 create mode 100644 unique-prefix.h

diff --git a/Makefile b/Makefile
index 545a0e8743..6773041f0d 100644
--- a/Makefile
+++ b/Makefile
@@ -944,7 +944,6 @@ LIB_OBJS += patch-ids.o
 LIB_OBJS += path.o
 LIB_OBJS += pathspec.o
 LIB_OBJS += pkt-line.o
-LIB_OBJS += prefix-map.o
 LIB_OBJS += preload-index.o
 LIB_OBJS += pretty.o
 LIB_OBJS += prio-queue.o
@@ -1013,6 +1012,7 @@ LIB_OBJS += transport-helper.o
 LIB_OBJS += tree-diff.o
 LIB_OBJS += tree.o
 LIB_OBJS += tree-walk.o
+LIB_OBJS += unique-prefix.o
 LIB_OBJS += unpack-trees.o
 LIB_OBJS += upload-pack.o
 LIB_OBJS += url.o
diff --git a/add-interactive.c b/add-interactive.c
index c431c72e3f..cf22749b81 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -5,7 +5,7 @@
 #include "diffcore.h"
 #include "revision.h"
 #include "refs.h"
-#include "prefix-map.h"
+#include "unique-prefix.h"
 
 struct add_i_state {
 	struct repository *r;
diff --git a/prefix-map.c b/prefix-map.c
deleted file mode 100644
index 747ddb4ebc..0000000000
--- a/prefix-map.c
+++ /dev/null
@@ -1,109 +0,0 @@
-#include "cache.h"
-#include "prefix-map.h"
-
-static int map_cmp(const void *unused_cmp_data,
-		   const void *entry,
-		   const void *entry_or_key,
-		   const void *unused_keydata)
-{
-	const struct prefix_map_entry *a = entry;
-	const struct prefix_map_entry *b = entry_or_key;
-
-	return a->prefix_length != b->prefix_length ||
-		strncmp(a->name, b->name, a->prefix_length);
-}
-
-static void add_prefix_entry(struct hashmap *map, const char *name,
-			     size_t prefix_length, struct prefix_item *item)
-{
-	struct prefix_map_entry *result = xmalloc(sizeof(*result));
-	result->name = name;
-	result->prefix_length = prefix_length;
-	result->item = item;
-	hashmap_entry_init(result, memhash(name, prefix_length));
-	hashmap_add(map, result);
-}
-
-static void init_prefix_map(struct prefix_map *prefix_map,
-			    int min_prefix_length, int max_prefix_length)
-{
-	hashmap_init(&prefix_map->map, map_cmp, NULL, 0);
-	prefix_map->min_length = min_prefix_length;
-	prefix_map->max_length = max_prefix_length;
-}
-
-static void add_prefix_item(struct prefix_map *prefix_map,
-			    struct prefix_item *item)
-{
-	struct prefix_map_entry e = { { NULL } }, *e2;
-	int j;
-
-	e.item = item;
-	e.name = item->name;
-
-	for (j = prefix_map->min_length;
-	     j <= prefix_map->max_length && e.name[j]; j++) {
-		/* Avoid breaking UTF-8 multi-byte sequences */
-		if (!isascii(e.name[j]))
-			break;
-
-		e.prefix_length = j;
-		hashmap_entry_init(&e, memhash(e.name, j));
-		e2 = hashmap_get(&prefix_map->map, &e, NULL);
-		if (!e2) {
-			/* prefix is unique at this stage */
-			item->prefix_length = j;
-			add_prefix_entry(&prefix_map->map, e.name, j, item);
-			break;
-		}
-
-		if (!e2->item)
-			continue; /* non-unique prefix */
-
-		if (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))
-			BUG("unexpected prefix length: %d != %d (%s != %s)",
-			    j, (int)e2->item->prefix_length, e.name, e2->name);
-
-		/* skip common prefix */
-		for (; j < prefix_map->max_length && e.name[j]; j++) {
-			if (e.item->name[j] != e2->item->name[j])
-				break;
-			add_prefix_entry(&prefix_map->map, e.name, j + 1,
-					 NULL);
-		}
-
-		/* e2 no longer refers to a unique prefix */
-		if (j < prefix_map->max_length && e2->name[j]) {
-			/* found a new unique prefix for e2's item */
-			e2->item->prefix_length = j + 1;
-			add_prefix_entry(&prefix_map->map, e2->name, j + 1,
-					 e2->item);
-		}
-		else
-			e2->item->prefix_length = 0;
-		e2->item = NULL;
-
-		if (j < prefix_map->max_length && e.name[j]) {
-			/* found a unique prefix for the item */
-			e.item->prefix_length = j + 1;
-			add_prefix_entry(&prefix_map->map, e.name, j + 1,
-					 e.item);
-		} else
-			/* item has no (short enough) unique prefix */
-			e.item->prefix_length = 0;
-
-		break;
-	}
-}
-
-void find_unique_prefixes(struct prefix_item **list, size_t nr,
-			  int min_length, int max_length)
-{
-	int i;
-	struct prefix_map prefix_map;
-
-	init_prefix_map(&prefix_map, min_length, max_length);
-	for (i = 0; i < nr; i++)
-		add_prefix_item(&prefix_map, list[i]);
-	hashmap_free(&prefix_map.map, 1);
-}
diff --git a/prefix-map.h b/prefix-map.h
deleted file mode 100644
index ce3b8a4a32..0000000000
--- a/prefix-map.h
+++ /dev/null
@@ -1,40 +0,0 @@
-#ifndef PREFIX_MAP_H
-#define PREFIX_MAP_H
-
-#include "hashmap.h"
-
-struct prefix_item {
-	const char *name;
-	size_t prefix_length;
-};
-
-struct prefix_map_entry {
-	struct hashmap_entry e;
-	const char *name;
-	size_t prefix_length;
-	/* if item is NULL, the prefix is not unique */
-	struct prefix_item *item;
-};
-
-struct prefix_map {
-	struct hashmap map;
-	int min_length, max_length;
-};
-
-/*
- * Find unique prefixes in a given list of strings.
- *
- * Typically, the `struct prefix_item` information will be but a field in the
- * actual item struct; For this reason, the `list` parameter is specified as a
- * list of pointers to the items.
- *
- * The `min_length`/`max_length` parameters define what length the unique
- * prefixes should have.
- *
- * If no unique prefix could be found for a given item, its `prefix_length`
- * will be set to 0.
- */
-void find_unique_prefixes(struct prefix_item **list, size_t nr,
-			  int min_length, int max_length);
-
-#endif
diff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c
index 3f1c90eaf0..d472ff5dc6 100644
--- a/t/helper/test-prefix-map.c
+++ b/t/helper/test-prefix-map.c
@@ -1,6 +1,5 @@
 #include "test-tool.h"
-#include "cache.h"
-#include "prefix-map.h"
+#include "unique-prefix.h"
 
 static size_t test_count, failed_count;
 
@@ -24,25 +23,32 @@ static void check(int succeeded, const char *file, size_t line_no,
 
 #define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \
 	check(expect == actual, __FILE__, __LINE__, \
-	      "size_t's do not match: %" \
-	      PRIdMAX " != %" PRIdMAX " (%s) (%s)", \
+	      "size_t's do not match: expected: %" \
+	      PRIdMAX " got: %" PRIdMAX " (%s) (%s)", \
 	      (intmax_t)expect, (intmax_t)actual, #actual, hint)
 
 int cmd__prefix_map(int argc, const char **argv)
 {
-#define NR 5
-	struct prefix_item items[NR] = {
+	size_t i;
+	struct prefix_item items[] = {
 		{ "unique" },
 		{ "hell" },
 		{ "hello" },
 		{ "wok" },
 		{ "world" },
+		{ "a" },
+		{ "" },
+		{ "pq" },
+		{ "pq" },
+		{ "pqr" },
 	};
-	struct prefix_item *list[NR] = {
-		items, items + 1, items + 2, items + 3, items + 4
-	};
+	struct prefix_item *list[ARRAY_SIZE(items)];
+	for (i = 0; i < ARRAY_SIZE(items); i++) {
+		list[i] = &items[i];
+		list[i]->prefix_length = 12345; /* dummy */
+	}
 
-	find_unique_prefixes(list, NR, 1, 3);
+	find_unique_prefixes(list, ARRAY_SIZE(list), 1, 3);
 
 #define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \
 	EXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \
@@ -53,6 +59,11 @@ int cmd__prefix_map(int argc, const char **argv)
 	EXPECT_PREFIX_LENGTH_EQUALS(0, 2);
 	EXPECT_PREFIX_LENGTH_EQUALS(3, 3);
 	EXPECT_PREFIX_LENGTH_EQUALS(3, 4);
+	EXPECT_PREFIX_LENGTH_EQUALS(1, 5);
+	EXPECT_PREFIX_LENGTH_EQUALS(0, 6);
+	EXPECT_PREFIX_LENGTH_EQUALS(0, 7);
+	EXPECT_PREFIX_LENGTH_EQUALS(0, 8);
+	EXPECT_PREFIX_LENGTH_EQUALS(3, 9);
 
 	return !!failed_count;
 }
diff --git a/unique-prefix.c b/unique-prefix.c
new file mode 100644
index 0000000000..327e8fa32c
--- /dev/null
+++ b/unique-prefix.c
@@ -0,0 +1,73 @@
+#include "git-compat-util.h"
+#include "unique-prefix.h"
+
+static int prefix_item_ptr_cmp(const void *item1, const void *item2)
+{
+	const struct prefix_item *pi1 = *((struct prefix_item**) item1);
+	const struct prefix_item *pi2 = *((struct prefix_item**) item2);
+	return strcmp(pi1->name, pi2->name);
+}
+
+static int str_is_shorter_than(const char *s, size_t len)
+{
+	for (; *s && len; s++, len--);
+	return !!len;
+}
+
+static size_t str_common_prefix_len(const char *s1, const char *s2, size_t limit)
+{
+	size_t i;
+	for (i = 0; i < limit; i++)
+		if (s1[i] != s2[i] || s1[i] == '\0')
+			return i;
+	return limit + 1;
+}
+
+void find_unique_prefixes(struct prefix_item **items, size_t nr,
+			  int min_length, int max_length)
+{
+	size_t i;
+	struct prefix_item **sorted_items;
+
+	ALLOC_ARRAY(sorted_items, nr);
+	COPY_ARRAY(sorted_items, items, nr);
+	QSORT(sorted_items, nr, prefix_item_ptr_cmp);
+
+	if (str_is_shorter_than(sorted_items[0]->name, min_length))
+		sorted_items[0]->prefix_length = 0;
+	else
+		sorted_items[0]->prefix_length = min_length;
+	for (i = 1; i < nr; i++) {
+		size_t common_len = str_common_prefix_len(
+			sorted_items[i - 1]->name, sorted_items[i]->name,
+			max_length);
+
+		if (!sorted_items[i - 1]->prefix_length)
+			/*
+			 * The previous iteration already determined that
+			 * it doesn't have a unique prefix.
+			 */
+			;
+		else if (max_length < common_len)
+			sorted_items[i - 1]->prefix_length = 0;
+		else if (sorted_items[i - 1]->name[common_len] == '\0')
+			/* Either prefix of or equal to the next string. */
+			sorted_items[i - 1]->prefix_length = 0;
+		else if (sorted_items[i - 1]->prefix_length <= common_len)
+			sorted_items[i - 1]->prefix_length = common_len + 1;
+
+		if (max_length < common_len)
+			sorted_items[i]->prefix_length = 0;
+		else if (sorted_items[i]->name[common_len] == '\0')
+			/* the two strings are the same */
+			sorted_items[i]->prefix_length = 0;
+		else if (min_length <= common_len)
+			sorted_items[i]->prefix_length = common_len + 1;
+		else if (str_is_shorter_than(sorted_items[i]->name, min_length))
+			sorted_items[i]->prefix_length = 0;
+		else
+			sorted_items[i]->prefix_length = min_length;
+	}
+
+	free(sorted_items);
+}
diff --git a/unique-prefix.h b/unique-prefix.h
new file mode 100644
index 0000000000..cbddc29a9a
--- /dev/null
+++ b/unique-prefix.h
@@ -0,0 +1,28 @@
+#ifndef UNIQUE_PREFIX_H
+#define UNIQUE_PREFIX_H
+
+struct prefix_item {
+	const char *name;
+	size_t prefix_length;
+};
+
+/*
+ * Find unique prefixes in a given array of strings.
+ * Given a list of names, find unique prefixes (i.e. the first <n> characters
+ * that uniquely identify the names) and store the lengths of the unique
+ * prefixes in the 'prefix_length' field of the elements of the given array.
+ *
+ * Typically, the `struct prefix_item` information will be a field in the
+ * actual item struct; For this reason, the `items` parameter is specified
+ * as an array of pointers to the items.
+ *
+ * The `min_length`/`max_length` parameters define what length the unique
+ * prefixes should have.
+ *
+ * If no unique prefix could be found for a given item, its `prefix_length`
+ * will be set to 0.
+ */
+void find_unique_prefixes(struct prefix_item **items, size_t nr,
+			  int min_length, int max_length);
+
+#endif
-- 
2.23.0.331.g4e51dcdf11


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

* Re: [PATCH] [PoC] A simpler find_unique_prefixes() implementation
  2019-08-28 16:34             ` [PATCH] [PoC] A simpler find_unique_prefixes() implementation SZEDER Gábor
@ 2019-08-30 20:12               ` Johannes Schindelin
  0 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin @ 2019-08-30 20:12 UTC (permalink / raw)
  To: SZEDER Gábor
  Cc: git, Jeff Hostetler, Jeff King, Junio C Hamano, Slavica Djukic,
	Slavica Djukic via GitGitGadget

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

Hi Gábor,

On Wed, 28 Aug 2019, SZEDER Gábor wrote:

> Implement find_unique_prefixes() by sorting the prefix items
> lexicographically and then looking for unique prefixes in adjacent
> pairs.  It's definitely shorter than the current hashmap-based
> implementation (101 vs 149 lines), subjectively easier to follow, and
> hopefully less buggy (some of the new elements in the test trigger
> failure with the hashmap).
>
> TODO: deal multi-byte caracter sequences, make the test tool more
>       developer-friendly

Sorry for my late response, I am still trying to catch up with an
ever-growing inbox after trying to pretend to be on vacation.

Your argument is a strong one, and I will try to re-roll, folding the
functionality to find the unique prefixes into `add-interactive.c` while
trying to reuse the already-sorted list for the lookup by prefix.

As to the multi-byte UTF-8 characters: you're right, that code has a
bug. The intention was to require only single-byte characters in the
prefix, for simplicity.

Thanks,
Dscho

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

* [PATCH v5 0/9] git add -i: add a rudimentary version in C (supporting only status and help  so far)
  2019-08-27 12:57     ` [PATCH v4 " Johannes Schindelin via GitGitGadget
                         ` (10 preceding siblings ...)
  2019-08-27 12:58       ` [PATCH v4 11/11] built-in add -i: implement the `help` command Johannes Schindelin via GitGitGadget
@ 2019-11-04 12:15       ` Johannes Schindelin via GitGitGadget
  2019-11-04 12:15         ` [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
                           ` (9 more replies)
  11 siblings, 10 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-11-04 12:15 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano

This is the first leg on the long journey to a fully built-in git add -i 
(next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3
[https://github.com/gitgitgadget/git/pull/172], 4
[https://github.com/gitgitgadget/git/pull/173], 5
[https://github.com/gitgitgadget/git/pull/174], and 6
[https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are
not necessarily up to date, and will be re-targeted to the appropriate
branches in https://github.com/gitster/git as soon as Junio picks them up.

This here patch series reflects the part that was submitted a couple of
times (see https://github.com/gitgitgadget/git/pull/103) during the
Outreachy project by Slavica Ðukic that continued the journey based on an
initial patch series by Daniel Ferreira.

It only implements the status and the help part, in the interest of making
the review remotely more reviewable.

As I am a heavy user of git add -p myself and use a patched version for
several months already (it is so nice to not suffer over one second startup
until the MSYS2 Perl finally shows me anything, instead it feels
instantaneous), I integrated these patch series into Git for Windows
already, as an opt-in feature guarded by the config variable 
add.interactive.useBuiltin (and Git for Windows' installer knows to detect
this version and offer the option in the graphical user interface).

Changes since v4:

 * Rebased onto current master to make use of Thomas Gummerer's 
   repo_refresh_and_write_index() as well as to avoid merge conflicts with
   Eric Wong's work on struct hashmap.
 * Instead of rolling a dedicated data struct to simulate a Trie, we now use 
   string-list extensively (an unsorted copy and a sorted one, the latter to
   determine unique prefixes). This had massive ramifications on the rest of
   the patches... For example, the struct command_item structure no longer
   contains the name field, but is intended to be a util in a string_list.
 * Changed the commit messages and author lines to reflect Slavica's name
   correctly.
 * Touched up a couple commit messages.

Changes since v3:

 * Rebased to v2.23.0 to reduce friction.
 * free_diffstat_info() is now made public as well, and used, to avoid a
   memory leak.
 * Prepared the patches for ew/hashmap (which is strict about the hashmap
   entries' type in hashmap_entry_init() and friends).
 * The private data types have been moved from prefix-map.h to prefix-map.c.
 * A lot of int types were converted to more appropriate size_t in 
   prefix-map.c.
 * A misleading parameter name list was renamed to the correct array.
 * The code comment above find_unique_prefixes() was (hopefully) improved.
 * The run_help() function's signature now reflects that most of the
   parameters are actually unused.

Changes since v2:

 * Rebased to master to avoid merge conflicts.
 * Renumbered the prefix-map test to avoid conflicts with two patch series
   that are currently in-flight in pu.

Changes since v1:

 * The config machinery was reworked completely, to not use a callback to 
   git_config(), but instead to query the config via the repo_config_get_*() 
   functions. This also prevents a future "Huh???" moment: the internal add
   --interactive API accepts a parameter of type struct repository *r, but
   the previous configuration did not use that to query the config (and
   could in the future be a repository other than the_repository).
   
   
 * As a consequence, the color sequences are no longer stored in file-local
   variables, but passed around via a struct.
   
   
 * Instead of using the magical constant -2 to quit the main loop, it is now
   defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is
   defined as -1 and used where appropriate).
   
   
 * Improved the add_prefix_item() function by avoiding buffer overruns, not
   reusing the struct that is used for lookup also for adding the new item,
   and by strengthening the bug check.

Daniel Ferreira (2):
  diff: export diffstat interface
  built-in add -i: implement the `status` command

Johannes Schindelin (4):
  Start to implement a built-in version of `git add --interactive`
  built-in add -i: implement the main loop
  built-in add -i: show unique prefixes of the commands
  built-in add -i: support `?` (prompt help)

Slavica Đukić (3):
  built-in add -i: color the header in the `status` command
  built-in add -i: use color in the main loop
  built-in add -i: implement the `help` command

 Documentation/config/add.txt |   5 +
 Makefile                     |   1 +
 add-interactive.c            | 650 +++++++++++++++++++++++++++++++++++
 add-interactive.h            |   8 +
 builtin/add.c                |  10 +
 diff.c                       |  39 +--
 diff.h                       |  20 ++
 t/README                     |   4 +
 t/t3701-add-interactive.sh   |  25 ++
 9 files changed, 739 insertions(+), 23 deletions(-)
 create mode 100644 add-interactive.c
 create mode 100644 add-interactive.h


base-commit: 566a1439f6f56c2171b8853ddbca0ad3f5098770
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v5
Pull-Request: https://github.com/gitgitgadget/git/pull/170

Range-diff vs v4:

  1:  ad8752eca7 !  1:  ff59d2d0b3 Start to implement a built-in version of `git add --interactive`
     @@ -47,7 +47,7 @@
       --- a/Makefile
       +++ b/Makefile
      @@
     - 	-name '*.h' -print))
     + 	-name '*.h' -print)))
       
       LIB_OBJS += abspath.o
      +LIB_OBJS += add-interactive.o
     @@ -125,7 +125,7 @@
       git-config(1).
       
      +GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the
     -+builtin version of git add -i. See 'add.interactive.useBuiltin' in
     ++built-in version of git add -i. See 'add.interactive.useBuiltin' in
      +git-config(1).
      +
       GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading
  2:  38cc04c1d9 !  2:  2fc8cc3546 diff: export diffstat interface
     @@ -12,7 +12,7 @@
          builtin implementation of git-add--interactive's status.
      
          Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
     -    Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
     +    Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
       diff --git a/diff.c b/diff.c
  3:  ee3e40293c !  3:  6aaa0de4f4 built-in add -i: implement the `status` command
     @@ -3,7 +3,7 @@
          built-in add -i: implement the `status` command
      
          This implements the `status` command of `git add -i`. The data
     -    structures introduced in this commit will be extended as needed later.
     +    structures introduced in this commit will be extended later, as needed.
      
          At this point, we re-implement only part of the `list_and_choose()`
          function of the Perl script `git-add--interactive.perl` and call it
     @@ -16,12 +16,8 @@
          will be used to implement the main loop of the built-in `git add -i`, at
          which point the new `status` command can actually be used.
      
     -    Note that we pass the list of items as a `struct item **` as opposed to
     -    a `struct item *`, to allow for the actual items to contain much more
     -    information than merely the name.
     -
          Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
     -    Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
     +    Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
       diff --git a/add-interactive.c b/add-interactive.c
     @@ -33,29 +29,35 @@
      +#include "diffcore.h"
      +#include "revision.h"
      +#include "refs.h"
     ++#include "string-list.h"
      +
     -+struct item {
     -+	const char *name;
     ++struct add_i_state {
     ++	struct repository *r;
      +};
      +
     ++static void init_add_i_state(struct add_i_state *s, struct repository *r)
     ++{
     ++       s->r = r;
     ++}
     ++
      +struct list_options {
      +	const char *header;
     -+	void (*print_item)(int i, struct item *item, void *print_item_data);
     ++	void (*print_item)(int i, struct string_list_item *item, void *print_item_data);
      +	void *print_item_data;
      +};
      +
     -+static void list(struct item **list, size_t nr, struct list_options *opts)
     ++static void list(struct string_list *list, struct list_options *opts)
      +{
      +	int i;
      +
     -+	if (!nr)
     ++	if (!list->nr)
      +		return;
      +
      +	if (opts->header)
      +		printf("%s\n", opts->header);
      +
     -+	for (i = 0; i < nr; i++) {
     -+		opts->print_item(i, list[i], opts->print_item_data);
     ++	for (i = 0; i < list->nr; i++) {
     ++		opts->print_item(i, list->items + i, opts->print_item_data);
      +		putchar('\n');
      +	}
      +}
     @@ -65,62 +67,34 @@
      +	unsigned seen:1, binary:1;
      +};
      +
     -+struct file_list {
     -+	struct file_item {
     -+		struct item item;
     -+		struct adddel index, worktree;
     -+	} **file;
     -+	size_t nr, alloc;
     ++struct file_item {
     ++	struct adddel index, worktree;
      +};
      +
     -+static void add_file_item(struct file_list *list, const char *name)
     ++static void add_file_item(struct string_list *files, const char *name)
      +{
     -+	struct file_item *item;
     -+
     -+	FLEXPTR_ALLOC_STR(item, item.name, name);
     ++	struct file_item *item = xcalloc(sizeof(*item), 1);
      +
     -+	ALLOC_GROW(list->file, list->nr + 1, list->alloc);
     -+	list->file[list->nr++] = item;
     -+}
     -+
     -+static void reset_file_list(struct file_list *list)
     -+{
     -+	size_t i;
     -+
     -+	for (i = 0; i < list->nr; i++)
     -+		free(list->file[i]);
     -+	list->nr = 0;
     -+}
     -+
     -+static void release_file_list(struct file_list *list)
     -+{
     -+	reset_file_list(list);
     -+	FREE_AND_NULL(list->file);
     -+	list->alloc = 0;
     -+}
     -+
     -+static int file_item_cmp(const void *a, const void *b)
     -+{
     -+	const struct file_item * const *f1 = a;
     -+	const struct file_item * const *f2 = b;
     -+
     -+	return strcmp((*f1)->item.name, (*f2)->item.name);
     ++	string_list_append(files, name)->util = item;
      +}
      +
      +struct pathname_entry {
      +	struct hashmap_entry ent;
     -+	size_t index;
     -+	char pathname[FLEX_ARRAY];
     ++	const char *name;
     ++	struct file_item *item;
      +};
      +
      +static int pathname_entry_cmp(const void *unused_cmp_data,
     -+			      const void *entry, const void *entry_or_key,
     -+			      const void *pathname)
     ++			      const struct hashmap_entry *he1,
     ++			      const struct hashmap_entry *he2,
     ++			      const void *name)
      +{
     -+	const struct pathname_entry *e1 = entry, *e2 = entry_or_key;
     ++	const struct pathname_entry *e1 =
     ++		container_of(he1, const struct pathname_entry, ent);
     ++	const struct pathname_entry *e2 =
     ++		container_of(he2, const struct pathname_entry, ent);
      +
     -+	return strcmp(e1->pathname,
     -+		      pathname ? (const char *)pathname : e2->pathname);
     ++	return strcmp(e1->name, name ? (const char *)name : e2->name);
      +}
      +
      +struct collection_status {
     @@ -128,7 +102,7 @@
      +
      +	const char *reference;
      +
     -+	struct file_list *list;
     ++	struct string_list *files;
      +	struct hashmap file_map;
      +};
      +
     @@ -149,24 +123,24 @@
      +		const char *name = stat.files[i]->name;
      +		int hash = strhash(name);
      +		struct pathname_entry *entry;
     -+		size_t file_index;
     -+		struct file_item *file;
     ++		struct file_item *file_item;
      +		struct adddel *adddel;
      +
     -+		entry = hashmap_get_from_hash(&s->file_map, hash, name);
     -+		if (entry)
     -+			file_index = entry->index;
     -+		else {
     -+			FLEX_ALLOC_STR(entry, pathname, name);
     ++		entry = hashmap_get_entry_from_hash(&s->file_map, hash, name,
     ++						    struct pathname_entry, ent);
     ++		if (!entry) {
     ++			add_file_item(s->files, name);
     ++
     ++			entry = xcalloc(sizeof(*entry), 1);
      +			hashmap_entry_init(&entry->ent, hash);
     -+			entry->index = file_index = s->list->nr;
     ++			entry->name = s->files->items[s->files->nr - 1].string;
     ++			entry->item = s->files->items[s->files->nr - 1].util;
      +			hashmap_add(&s->file_map, &entry->ent);
     -+
     -+			add_file_item(s->list, name);
      +		}
     -+		file = s->list->file[file_index];
      +
     -+		adddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;
     ++		file_item = entry->item;
     ++		adddel = s->phase == FROM_INDEX ?
     ++			&file_item->index : &file_item->worktree;
      +		adddel->seen = 1;
      +		adddel->add = stat.files[i]->added;
      +		adddel->del = stat.files[i]->deleted;
     @@ -176,7 +150,7 @@
      +	free_diffstat_info(&stat);
      +}
      +
     -+static int get_modified_files(struct repository *r, struct file_list *list,
     ++static int get_modified_files(struct repository *r, struct string_list *files,
      +			      const struct pathspec *ps)
      +{
      +	struct object_id head_oid;
     @@ -184,10 +158,12 @@
      +					     &head_oid, NULL);
      +	struct collection_status s = { FROM_WORKTREE };
      +
     -+	if (repo_read_index_preload(r, ps, 0) < 0)
     ++	if (discard_index(r->index) < 0 ||
     ++	    repo_read_index_preload(r, ps, 0) < 0)
      +		return error(_("could not read index"));
      +
     -+	s.list = list;
     ++	string_list_clear(files, 1);
     ++	s.files = files;
      +	hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);
      +
      +	for (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {
     @@ -214,15 +190,15 @@
      +			run_diff_files(&rev, 0);
      +		}
      +	}
     -+	hashmap_free(&s.file_map, 1);
     ++	hashmap_free_entries(&s.file_map, struct pathname_entry, ent);
      +
      +	/* While the diffs are ordered already, we ran *two* diffs... */
     -+	QSORT(list->file, list->nr, file_item_cmp);
     ++	string_list_sort(files);
      +
      +	return 0;
      +}
      +
     -+static void populate_wi_changes(struct strbuf *buf,
     ++static void render_adddel(struct strbuf *buf,
      +				struct adddel *ad, const char *no_changes)
      +{
      +	if (ad->binary)
     @@ -239,34 +215,31 @@
      +	struct strbuf buf, index, worktree;
      +};
      +
     -+static void print_file_item(int i, struct item *item,
     ++static void print_file_item(int i, struct string_list_item *item,
      +			    void *print_file_item_data)
      +{
     -+	struct file_item *c = (struct file_item *)item;
     ++	struct file_item *c = item->util;
      +	struct print_file_item_data *d = print_file_item_data;
      +
      +	strbuf_reset(&d->index);
      +	strbuf_reset(&d->worktree);
      +	strbuf_reset(&d->buf);
      +
     -+	populate_wi_changes(&d->worktree, &c->worktree, _("nothing"));
     -+	populate_wi_changes(&d->index, &c->index, _("unchanged"));
     ++	render_adddel(&d->worktree, &c->worktree, _("nothing"));
     ++	render_adddel(&d->index, &c->index, _("unchanged"));
      +	strbuf_addf(&d->buf, d->modified_fmt,
     -+		    d->index.buf, d->worktree.buf, item->name);
     ++		    d->index.buf, d->worktree.buf, item->string);
      +
      +	printf(" %2d: %s", i + 1, d->buf.buf);
      +}
      +
     -+static int run_status(struct repository *r, const struct pathspec *ps,
     -+		      struct file_list *files, struct list_options *opts)
     ++static int run_status(struct add_i_state *s, const struct pathspec *ps,
     ++		      struct string_list *files, struct list_options *opts)
      +{
     -+	reset_file_list(files);
     -+
     -+	if (get_modified_files(r, files, ps) < 0)
     ++	if (get_modified_files(s->r, files, ps) < 0)
      +		return -1;
      +
     -+	if (files->nr)
     -+		list((struct item **)files->file, files->nr, opts);
     ++	list(files, opts);
      +	putchar('\n');
      +
      +	return 0;
     @@ -275,6 +248,7 @@
       int run_add_i(struct repository *r, const struct pathspec *ps)
       {
      -	die(_("No commands are available in the built-in `git add -i` yet!"));
     ++	struct add_i_state s = { NULL };
      +	struct print_file_item_data print_file_item_data = {
      +		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
      +	};
     @@ -282,17 +256,24 @@
      +		NULL, print_file_item, &print_file_item_data
      +	};
      +	struct strbuf header = STRBUF_INIT;
     -+	struct file_list files = { NULL };
     ++	struct string_list files = STRING_LIST_INIT_DUP;
      +	int res = 0;
      +
     ++	init_add_i_state(&s, r);
      +	strbuf_addstr(&header, "      ");
      +	strbuf_addf(&header, print_file_item_data.modified_fmt,
      +		    _("staged"), _("unstaged"), _("path"));
      +	opts.header = header.buf;
      +
     -+	res = run_status(r, ps, &files, &opts);
     ++	if (discard_index(r->index) < 0 ||
     ++	    repo_read_index(r) < 0 ||
     ++	    repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
     ++					 NULL, NULL, NULL) < 0)
     ++		warning(_("could not refresh index"));
     ++
     ++	res = run_status(&s, ps, &files, &opts);
      +
     -+	release_file_list(&files);
     ++	string_list_clear(&files, 1);
      +	strbuf_release(&print_file_item_data.buf);
      +	strbuf_release(&print_file_item_data.index);
      +	strbuf_release(&print_file_item_data.worktree);
  4:  3c855d9fa5 <  -:  ---------- built-in add -i: refresh the index before running `status`
  5:  24737a09f7 !  4:  e405f07110 built-in add -i: color the header in the `status` command
     @@ -1,4 +1,4 @@
     -Author: Johannes Schindelin <johannes.schindelin@gmx.de>
     +Author: Slavica Đukić <slawica92@hotmail.com>
      
          built-in add -i: color the header in the `status` command
      
     @@ -7,7 +7,7 @@
          `git-add--interactive.perl` does.
      
          Original-Patch-By: Daniel Ferreira <bnmvco@gmail.com>
     -    Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
     +    Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
       diff --git a/add-interactive.c b/add-interactive.c
     @@ -21,13 +21,14 @@
       #include "diffcore.h"
       #include "revision.h"
       #include "refs.h"
     +@@
       
     -+struct add_i_state {
     -+	struct repository *r;
     + struct add_i_state {
     + 	struct repository *r;
      +	int use_color;
      +	char header_color[COLOR_MAXLEN];
     -+};
     -+
     + };
     + 
      +static void init_color(struct repository *r, struct add_i_state *s,
      +		       const char *slot_name, char *dst,
      +		       const char *default_color)
     @@ -44,8 +45,9 @@
      +	free(key);
      +}
      +
     -+static int init_add_i_state(struct repository *r, struct add_i_state *s)
     -+{
     + static void init_add_i_state(struct add_i_state *s, struct repository *r)
     + {
     +-       s->r = r;
      +	const char *value;
      +
      +	s->r = r;
     @@ -58,20 +60,16 @@
      +	s->use_color = want_color(s->use_color);
      +
      +	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
     -+
     -+	return 0;
     -+}
     -+
     - struct item {
     - 	const char *name;
     - };
     + }
     + 
     + struct list_options {
      @@
       	void *print_item_data;
       };
       
     --static void list(struct item **list, size_t nr, struct list_options *opts)
     -+static void list(struct item **list, size_t nr,
     -+		 struct add_i_state *s, struct list_options *opts)
     +-static void list(struct string_list *list, struct list_options *opts)
     ++static void list(struct add_i_state *s, struct string_list *list,
     ++		 struct list_options *opts)
       {
       	int i;
       
     @@ -83,51 +81,14 @@
      +		color_fprintf_ln(stdout, s->header_color,
      +				 "%s", opts->header);
       
     - 	for (i = 0; i < nr; i++) {
     - 		opts->print_item(i, list[i], opts->print_item_data);
     + 	for (i = 0; i < list->nr; i++) {
     + 		opts->print_item(i, list->items + i, opts->print_item_data);
      @@
     - 	printf(" %2d: %s", i + 1, d->buf.buf);
     - }
     - 
     --static int run_status(struct repository *r, const struct pathspec *ps,
     -+static int run_status(struct add_i_state *s, const struct pathspec *ps,
     - 		      struct file_list *files, struct list_options *opts)
     - {
     - 	reset_file_list(files);
     - 
     --	if (get_modified_files(r, files, ps) < 0)
     -+	if (get_modified_files(s->r, files, ps) < 0)
     + 	if (get_modified_files(s->r, files, ps) < 0)
       		return -1;
       
     - 	if (files->nr)
     --		list((struct item **)files->file, files->nr, opts);
     -+		list((struct item **)files->file, files->nr, s, opts);
     +-	list(files, opts);
     ++	list(s, files, opts);
       	putchar('\n');
       
       	return 0;
     -@@
     - 
     - int run_add_i(struct repository *r, const struct pathspec *ps)
     - {
     -+	struct add_i_state s = { NULL };
     - 	struct print_file_item_data print_file_item_data = {
     - 		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
     - 	};
     -@@
     - 	struct file_list files = { NULL };
     - 	int res = 0;
     - 
     -+	if (init_add_i_state(r, &s))
     -+		return error("could not parse `add -i` config");
     -+
     - 	strbuf_addstr(&header, "      ");
     - 	strbuf_addf(&header, print_file_item_data.modified_fmt,
     - 		    _("staged"), _("unstaged"), _("path"));
     - 	opts.header = header.buf;
     - 
     - 	repo_refresh_and_write_index(r, REFRESH_QUIET, 1);
     --	if (run_status(r, ps, &files, &opts) < 0)
     -+	if (run_status(&s, ps, &files, &opts) < 0)
     - 		res = -1;
     - 
     - 	release_file_list(&files);
  6:  ac67731cf1 !  5:  25590fbbbe built-in add -i: implement the main loop
     @@ -45,26 +45,26 @@
       --- a/add-interactive.c
       +++ b/add-interactive.c
      @@
     - };
     + }
       
       struct list_options {
      +	int columns;
       	const char *header;
     - 	void (*print_item)(int i, struct item *item, void *print_item_data);
     + 	void (*print_item)(int i, struct string_list_item *item, void *print_item_data);
       	void *print_item_data;
      @@
     - static void list(struct item **list, size_t nr,
     - 		 struct add_i_state *s, struct list_options *opts)
     + static void list(struct add_i_state *s, struct string_list *list,
     + 		 struct list_options *opts)
       {
      -	int i;
      +	int i, last_lf = 0;
       
     - 	if (!nr)
     + 	if (!list->nr)
       		return;
      @@
       
     - 	for (i = 0; i < nr; i++) {
     - 		opts->print_item(i, list[i], opts->print_item_data);
     + 	for (i = 0; i < list->nr; i++) {
     + 		opts->print_item(i, list->items + i, opts->print_item_data);
      +
      +		if ((opts->columns) && ((i + 1) % (opts->columns))) {
      +			putchar('\t');
     @@ -94,8 +94,7 @@
      + * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
      + * `LIST_AND_CHOOSE_QUIT` is returned.
      + */
     -+static ssize_t list_and_choose(struct item **items, size_t nr,
     -+			       struct add_i_state *s,
     ++static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
      +			       struct list_and_choose_options *opts)
      +{
      +	struct strbuf input = STRBUF_INIT;
     @@ -106,7 +105,7 @@
      +
      +		strbuf_reset(&input);
      +
     -+		list(items, nr, s, &opts->list_opts);
     ++		list(s, items, &opts->list_opts);
      +
      +		printf("%s%s", opts->prompt, "> ");
      +		fflush(stdout);
     @@ -140,7 +139,7 @@
      +			}
      +
      +			p[sep] = '\0';
     -+			if (index < 0 || index >= nr)
     ++			if (index < 0 || index >= items->nr)
      +				printf(_("Huh (%s)?\n"), p);
      +			else {
      +				res = index;
     @@ -163,17 +162,15 @@
       	return 0;
       }
       
     -+static void print_command_item(int i, struct item *item,
     ++typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
     ++			 struct string_list *files,
     ++			 struct list_options *opts);
     ++
     ++static void print_command_item(int i, struct string_list_item *item,
      +			       void *print_command_item_data)
      +{
     -+	printf(" %2d: %s", i + 1, item->name);
     ++	printf(" %2d: %s", i + 1, item->string);
      +}
     -+
     -+struct command_item {
     -+	struct item item;
     -+	int (*command)(struct add_i_state *s, const struct pathspec *ps,
     -+		       struct file_list *files, struct list_options *opts);
     -+};
      +
       int run_add_i(struct repository *r, const struct pathspec *ps)
       {
     @@ -182,11 +179,13 @@
      +		{ 4, N_("*** Commands ***"), print_command_item, NULL },
      +		N_("What now")
      +	};
     -+	struct command_item
     -+		status = { { "status" }, run_status };
     -+	struct command_item *commands[] = {
     -+		&status
     ++	struct {
     ++		const char *string;
     ++		command_t command;
     ++	} command_list[] = {
     ++		{ "status", run_status },
      +	};
     ++	struct string_list commands = STRING_LIST_INIT_NODUP;
      +
       	struct print_file_item_data print_file_item_data = {
       		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
     @@ -196,27 +195,42 @@
      +		0, NULL, print_file_item, &print_file_item_data
       	};
       	struct strbuf header = STRBUF_INIT;
     - 	struct file_list files = { NULL };
     + 	struct string_list files = STRING_LIST_INIT_DUP;
      +	ssize_t i;
       	int res = 0;
       
     - 	if (init_add_i_state(r, &s))
     ++	for (i = 0; i < ARRAY_SIZE(command_list); i++)
     ++		string_list_append(&commands, command_list[i].string)
     ++			->util = command_list[i].command;
     ++
     + 	init_add_i_state(&s, r);
     ++
     + 	strbuf_addstr(&header, "      ");
     + 	strbuf_addf(&header, print_file_item_data.modified_fmt,
     + 		    _("staged"), _("unstaged"), _("path"));
      @@
     - 	if (run_status(&s, ps, &files, &opts) < 0)
     - 		res = -1;
     + 
     + 	res = run_status(&s, ps, &files, &opts);
       
      +	for (;;) {
     -+		i = list_and_choose((struct item **)commands,
     -+				    ARRAY_SIZE(commands), &s, &main_loop_opts);
     ++		i = list_and_choose(&s, &commands, &main_loop_opts);
      +		if (i == LIST_AND_CHOOSE_QUIT) {
      +			printf(_("Bye.\n"));
      +			res = 0;
      +			break;
      +		}
     -+		if (i != LIST_AND_CHOOSE_ERROR)
     -+			res = commands[i]->command(&s, ps, &files, &opts);
     ++		if (i != LIST_AND_CHOOSE_ERROR) {
     ++			command_t command = commands.items[i].util;
     ++			res = command(&s, ps, &files, &opts);
     ++		}
      +	}
      +
     - 	release_file_list(&files);
     + 	string_list_clear(&files, 1);
       	strbuf_release(&print_file_item_data.buf);
       	strbuf_release(&print_file_item_data.index);
     + 	strbuf_release(&print_file_item_data.worktree);
     + 	strbuf_release(&header);
     ++	string_list_clear(&commands, 0);
     + 
     + 	return res;
     + }
  7:  c5a699b6b2 <  -:  ---------- Add a function to determine unique prefixes for a list of strings
  8:  bc7a74f697 !  6:  57fdc01463 built-in add -i: show unique prefixes of the commands
     @@ -1,4 +1,4 @@
     -Author: Slavica Djukic <slawica92@hotmail.com>
     +Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
          built-in add -i: show unique prefixes of the commands
      
     @@ -7,97 +7,197 @@
          given parameters), and shown in the list, and accepted as a shortcut for
          the command.
      
     -    We use the prefix map implementation that we just added in the previous
     -    commit for that purpose.
     +    To determine the unique prefixes, as well as to look up the command in
     +    question, we use a copy of the list and sort it.
      
     -    Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
     +    While this might seem like overkill for a single command, it will make
     +    much more sense when all the commands are implemented, and when we reuse
     +    the same logic to present a list of files to edit, with convenient
     +    unique prefixes.
     +
     +    At the start of the development of this patch series, a dedicated data
     +    structure was introduced that imitated the Trie that the Perl version
     +    implements. However, this was deemed overkill, and we now simply sort
     +    the list before determining the length of the unique prefixes by looking
     +    at each item's neighbor. As a bonus, we now use the same sorted list to
     +    perform a binary search using the user-provided prefix as search key.
     +
     +    Original-patch-by: Slavica Đukić <slawica92@hotmail.com>
     +    Helped-by: SZEDER Gábor <szeder.dev@gmail.com>
          Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
      
       diff --git a/add-interactive.c b/add-interactive.c
       --- a/add-interactive.c
       +++ b/add-interactive.c
      @@
     - #include "diffcore.h"
     - #include "revision.h"
     - #include "refs.h"
     -+#include "prefix-map.h"
     - 
     - struct add_i_state {
     - 	struct repository *r;
     -@@
     - 	return 0;
     + 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
       }
       
     --struct item {
     --	const char *name;
     --};
     -+static ssize_t find_unique(const char *string,
     -+			   struct prefix_item **list, size_t nr)
     ++/*
     ++ * A "prefix item list" is a list of items that are identified by a string, and
     ++ * a unique prefix (if any) is determined for each item.
     ++ *
     ++ * It is implemented in the form of a pair of `string_list`s, the first one
     ++ * duplicating the strings, with the `util` field pointing at a structure whose
     ++ * first field must be `size_t prefix_length`.
     ++ *
     ++ * That `prefix_length` field will be computed by `find_unique_prefixes()`; It
     ++ * will be set to zero if no valid, unique prefix could be found.
     ++ *
     ++ * The second `string_list` is called `sorted` and does _not_ duplicate the
     ++ * strings but simply reuses the first one's, with the `util` field pointing at
     ++ * the `string_item_list` of the first `string_list`. It  will be populated and
     ++ * sorted by `find_unique_prefixes()`.
     ++ */
     ++struct prefix_item_list {
     ++	struct string_list items;
     ++	struct string_list sorted;
     ++	size_t min_length, max_length;
     ++};
     ++#define PREFIX_ITEM_LIST_INIT \
     ++	{ STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, 1, 4 }
     ++
     ++static void prefix_item_list_clear(struct prefix_item_list *list)
     ++{
     ++	string_list_clear(&list->items, 1);
     ++	string_list_clear(&list->sorted, 0);
     ++}
     ++
     ++static void extend_prefix_length(struct string_list_item *p,
     ++				 const char *other_string, size_t max_length)
      +{
     -+	ssize_t found = -1, i;
     -+
     -+	for (i = 0; i < nr; i++) {
     -+		struct prefix_item *item = list[i];
     -+		if (!starts_with(item->name, string))
     -+			continue;
     -+		if (found >= 0)
     -+			return -1;
     -+		found = i;
     ++	size_t *len = p->util;
     ++
     ++	if (!*len || memcmp(p->string, other_string, *len))
     ++		return;
     ++
     ++	for (;;) {
     ++		char c = p->string[*len];
     ++
     ++		/*
     ++		 * Is `p` a strict prefix of `other`? Or have we exhausted the
     ++		 * maximal length of the prefix? Or is the current character a
     ++		 * multi-byte UTF-8 one? If so, there is no valid, unique
     ++		 * prefix.
     ++		 */
     ++		if (!c || ++*len > max_length || !isascii(c)) {
     ++			*len = 0;
     ++			break;
     ++		}
     ++
     ++		if (c != other_string[*len - 1])
     ++			break;
      +	}
     ++}
      +
     -+	return found;
     ++static void find_unique_prefixes(struct prefix_item_list *list)
     ++{
     ++	size_t i;
     ++
     ++	if (list->sorted.nr == list->items.nr)
     ++		return;
     ++
     ++	string_list_clear(&list->sorted, 0);
     ++	/* Avoid reallocating incrementally */
     ++	list->sorted.items = xmalloc(st_mult(sizeof(*list->sorted.items),
     ++					     list->items.nr));
     ++	list->sorted.nr = list->sorted.alloc = list->items.nr;
     ++
     ++	for (i = 0; i < list->items.nr; i++) {
     ++		list->sorted.items[i].string = list->items.items[i].string;
     ++		list->sorted.items[i].util = list->items.items + i;
     ++	}
     ++
     ++	string_list_sort(&list->sorted);
     ++
     ++	for (i = 0; i < list->sorted.nr; i++) {
     ++		struct string_list_item *sorted_item = list->sorted.items + i;
     ++		struct string_list_item *item = sorted_item->util;
     ++		size_t *len = item->util;
     ++
     ++		*len = 0;
     ++		while (*len < list->min_length) {
     ++			char c = item->string[(*len)++];
     ++
     ++			if (!c || !isascii(c)) {
     ++				*len = 0;
     ++				break;
     ++			}
     ++		}
     ++
     ++		if (i > 0)
     ++			extend_prefix_length(item, sorted_item[-1].string,
     ++					     list->max_length);
     ++		if (i + 1 < list->sorted.nr)
     ++			extend_prefix_length(item, sorted_item[1].string,
     ++					     list->max_length);
     ++	}
      +}
     - 
     ++
     ++static ssize_t find_unique(const char *string, struct prefix_item_list *list)
     ++{
     ++	int index = string_list_find_insert_index(&list->sorted, string, 1);
     ++	struct string_list_item *item;
     ++
     ++	if (list->items.nr != list->sorted.nr)
     ++		BUG("prefix_item_list in inconsistent state (%"PRIuMAX
     ++		    " vs %"PRIuMAX")",
     ++		    (uintmax_t)list->items.nr, (uintmax_t)list->sorted.nr);
     ++
     ++	if (index < 0)
     ++		item = list->sorted.items[-1 - index].util;
     ++	else if (index > 0 &&
     ++		 starts_with(list->sorted.items[index - 1].string, string))
     ++		return -1;
     ++	else if (index + 1 < list->sorted.nr &&
     ++		 starts_with(list->sorted.items[index + 1].string, string))
     ++		return -1;
     ++	else if (index < list->sorted.nr)
     ++		item = list->sorted.items[index].util;
     ++	else
     ++		return -1;
     ++	return item - list->items.items;
     ++}
     ++
       struct list_options {
       	int columns;
       	const char *header;
     --	void (*print_item)(int i, struct item *item, void *print_item_data);
     -+	void (*print_item)(int i, struct prefix_item *item,
     -+			   void *print_item_data);
     - 	void *print_item_data;
     - };
     - 
     --static void list(struct item **list, size_t nr,
     -+static void list(struct prefix_item **list, size_t nr,
     - 		 struct add_i_state *s, struct list_options *opts)
     - {
     - 	int i, last_lf = 0;
      @@
        * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
        * `LIST_AND_CHOOSE_QUIT` is returned.
        */
     --static ssize_t list_and_choose(struct item **items, size_t nr,
     -+static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
     - 			       struct add_i_state *s,
     +-static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
     ++static ssize_t list_and_choose(struct add_i_state *s,
     ++			       struct prefix_item_list *items,
       			       struct list_and_choose_options *opts)
       {
       	struct strbuf input = STRBUF_INIT;
       	ssize_t res = LIST_AND_CHOOSE_ERROR;
       
     -+	find_unique_prefixes(items, nr, 1, 4);
     ++	find_unique_prefixes(items);
      +
       	for (;;) {
       		char *p, *endp;
       
     + 		strbuf_reset(&input);
     + 
     +-		list(s, items, &opts->list_opts);
     ++		list(s, &items->items, &opts->list_opts);
     + 
     + 		printf("%s%s", opts->prompt, "> ");
     + 		fflush(stdout);
      @@
       			}
       
       			p[sep] = '\0';
     +-			if (index < 0 || index >= items->nr)
      +			if (index < 0)
     -+				index = find_unique(p, items, nr);
     ++				index = find_unique(p, items);
      +
     - 			if (index < 0 || index >= nr)
     ++			if (index < 0 || index >= items->items.nr)
       				printf(_("Huh (%s)?\n"), p);
       			else {
     -@@
     - 
     - struct file_list {
     - 	struct file_item {
     --		struct item item;
     -+		struct prefix_item item;
     - 		struct adddel index, worktree;
     - 	} **file;
     - 	size_t nr, alloc;
     + 				res = index;
      @@
       		strbuf_addstr(buf, no_changes);
       }
     @@ -122,50 +222,74 @@
       struct print_file_item_data {
       	const char *modified_fmt;
       	struct strbuf buf, index, worktree;
     - };
     - 
     --static void print_file_item(int i, struct item *item,
     -+static void print_file_item(int i, struct prefix_item *item,
     - 			    void *print_file_item_data)
     - {
     - 	struct file_item *c = (struct file_item *)item;
      @@
     - 		return -1;
     - 
     - 	if (files->nr)
     --		list((struct item **)files->file, files->nr, s, opts);
     -+		list((struct prefix_item **)files->file, files->nr, s, opts);
     - 	putchar('\n');
     + 			 struct string_list *files,
     + 			 struct list_options *opts);
       
     - 	return 0;
     - }
     - 
     --static void print_command_item(int i, struct item *item,
     -+static void print_command_item(int i, struct prefix_item *item,
     ++struct command_item {
     ++	size_t prefix_length;
     ++	command_t command;
     ++};
     ++
     + static void print_command_item(int i, struct string_list_item *item,
       			       void *print_command_item_data)
       {
     --	printf(" %2d: %s", i + 1, item->name);
     -+	if (!item->prefix_length ||
     -+	    !is_valid_prefix(item->name, item->prefix_length))
     -+		printf(" %2d: %s", i + 1, item->name);
     +-	printf(" %2d: %s", i + 1, item->string);
     ++	struct command_item *util = item->util;
     ++
     ++	if (!util->prefix_length ||
     ++	    !is_valid_prefix(item->string, util->prefix_length))
     ++		printf(" %2d: %s", i + 1, item->string);
      +	else
     -+		printf(" %3d: [%.*s]%s", i + 1,
     -+		       (int)item->prefix_length, item->name,
     -+		       item->name + item->prefix_length);
     ++		printf(" %2d: [%.*s]%s", i + 1,
     ++		       (int)util->prefix_length, item->string,
     ++		       item->string + util->prefix_length);
       }
       
     - struct command_item {
     --	struct item item;
     -+	struct prefix_item item;
     - 	int (*command)(struct add_i_state *s, const struct pathspec *ps,
     - 		       struct file_list *files, struct list_options *opts);
     - };
     + int run_add_i(struct repository *r, const struct pathspec *ps)
      @@
     - 		res = -1;
     + 	} command_list[] = {
     + 		{ "status", run_status },
     + 	};
     +-	struct string_list commands = STRING_LIST_INIT_NODUP;
     ++	struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
       
     - 	for (;;) {
     --		i = list_and_choose((struct item **)commands,
     -+		i = list_and_choose((struct prefix_item **)commands,
     - 				    ARRAY_SIZE(commands), &s, &main_loop_opts);
     - 		if (i == LIST_AND_CHOOSE_QUIT) {
     - 			printf(_("Bye.\n"));
     + 	struct print_file_item_data print_file_item_data = {
     + 		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
     +@@
     + 	ssize_t i;
     + 	int res = 0;
     + 
     +-	for (i = 0; i < ARRAY_SIZE(command_list); i++)
     +-		string_list_append(&commands, command_list[i].string)
     +-			->util = command_list[i].command;
     ++	for (i = 0; i < ARRAY_SIZE(command_list); i++) {
     ++		struct command_item *util = xcalloc(sizeof(*util), 1);
     ++		util->command = command_list[i].command;
     ++		string_list_append(&commands.items, command_list[i].string)
     ++			->util = util;
     ++	}
     + 
     + 	init_add_i_state(&s, r);
     + 
     +@@
     + 			break;
     + 		}
     + 		if (i != LIST_AND_CHOOSE_ERROR) {
     +-			command_t command = commands.items[i].util;
     +-			res = command(&s, ps, &files, &opts);
     ++			struct command_item *util =
     ++				commands.items.items[i].util;
     ++			res = util->command(&s, ps, &files, &opts);
     + 		}
     + 	}
     + 
     +@@
     + 	strbuf_release(&print_file_item_data.index);
     + 	strbuf_release(&print_file_item_data.worktree);
     + 	strbuf_release(&header);
     +-	string_list_clear(&commands, 0);
     ++	prefix_item_list_clear(&commands);
     + 
     + 	return res;
     + }
  9:  74f73e26b4 !  7:  77ad5f333a built-in add -i: support `?` (prompt help)
     @@ -24,9 +24,9 @@
       
       	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
      +	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
     - 
     - 	return 0;
       }
     + 
     + /*
      @@
       	struct list_options list_opts;
       
     @@ -48,8 +48,8 @@
       		for (;;) {
       			size_t sep = strcspn(p, " \t\r\n,");
      @@
     - 		       struct file_list *files, struct list_options *opts);
     - };
     + 		       item->string + util->prefix_length);
     + }
       
      +static void command_prompt_help(struct add_i_state *s)
      +{
     @@ -71,5 +71,5 @@
      -		N_("What now")
      +		N_("What now"), command_prompt_help
       	};
     - 	struct command_item
     - 		status = { { "status" }, run_status };
     + 	struct {
     + 		const char *string;
 10:  88001009bc !  8:  3d0b172a7f built-in add -i: use color in the main loop
     @@ -1,11 +1,11 @@
     -Author: Slavica Djukic <slawica92@hotmail.com>
     +Author: Slavica Đukić <slawica92@hotmail.com>
      
          built-in add -i: use color in the main loop
      
          The error messages as well as the unique prefixes are colored in `git
          add -i` by default; We need to do the same in the built-in version.
      
     -    Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
     +    Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
       diff --git a/add-interactive.c b/add-interactive.c
     @@ -28,12 +28,12 @@
      +	init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE);
      +	init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED);
      +	init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET);
     - 
     - 	return 0;
       }
     + 
     + /*
      @@
       
     - 		list(items, nr, s, &opts->list_opts);
     + 		list(s, &items->items, &opts->list_opts);
       
      -		printf("%s%s", opts->prompt, "> ");
      +		color_fprintf(stdout, s->prompt_color, "%s", opts->prompt);
     @@ -42,9 +42,9 @@
       
       		if (strbuf_getline(&input, stdin) == EOF) {
      @@
     - 				index = find_unique(p, items, nr);
     + 				index = find_unique(p, items);
       
     - 			if (index < 0 || index >= nr)
     + 			if (index < 0 || index >= items->items.nr)
      -				printf(_("Huh (%s)?\n"), p);
      +				color_fprintf_ln(stdout, s->error_color,
      +						 _("Huh (%s)?"), p);
     @@ -52,43 +52,46 @@
       				res = index;
       				break;
      @@
     - 	return 0;
     - }
     + 	command_t command;
     + };
       
      +struct print_command_item_data {
      +	const char *color, *reset;
      +};
      +
     - static void print_command_item(int i, struct prefix_item *item,
     + static void print_command_item(int i, struct string_list_item *item,
       			       void *print_command_item_data)
       {
      +	struct print_command_item_data *d = print_command_item_data;
     -+
     - 	if (!item->prefix_length ||
     - 	    !is_valid_prefix(item->name, item->prefix_length))
     - 		printf(" %2d: %s", i + 1, item->name);
     + 	struct command_item *util = item->util;
     + 
     + 	if (!util->prefix_length ||
     + 	    !is_valid_prefix(item->string, util->prefix_length))
     + 		printf(" %2d: %s", i + 1, item->string);
       	else
     --		printf(" %3d: [%.*s]%s", i + 1,
     --		       (int)item->prefix_length, item->name,
     +-		printf(" %2d: [%.*s]%s", i + 1,
     +-		       (int)util->prefix_length, item->string,
     +-		       item->string + util->prefix_length);
      +		printf(" %2d: %s%.*s%s%s", i + 1,
     -+		       d->color, (int)item->prefix_length, item->name, d->reset,
     - 		       item->name + item->prefix_length);
     ++		       d->color, (int)util->prefix_length, item->string,
     ++		       d->reset, item->string + util->prefix_length);
       }
       
     + static void command_prompt_help(struct add_i_state *s)
      @@
       int run_add_i(struct repository *r, const struct pathspec *ps)
       {
       	struct add_i_state s = { NULL };
     -+	struct print_command_item_data data;
     ++	struct print_command_item_data data = { "[", "]" };
       	struct list_and_choose_options main_loop_opts = {
      -		{ 4, N_("*** Commands ***"), print_command_item, NULL },
      +		{ 4, N_("*** Commands ***"), print_command_item, &data },
       		N_("What now"), command_prompt_help
       	};
     - 	struct command_item
     + 	struct {
      @@
     - 	if (init_add_i_state(r, &s))
     - 		return error("could not parse `add -i` config");
     + 
     + 	init_add_i_state(&s, r);
       
      +	/*
      +	 * When color was asked for, use the prompt color for
     @@ -97,9 +100,6 @@
      +	if (s.use_color) {
      +		data.color = s.prompt_color;
      +		data.reset = s.reset_color;
     -+	} else {
     -+		data.color = "[";
     -+		data.reset = "]";
      +	}
      +
       	strbuf_addstr(&header, "      ");
 11:  b27fbe289f !  9:  85e508ef11 built-in add -i: implement the `help` command
     @@ -1,4 +1,4 @@
     -Author: Johannes Schindelin <johannes.schindelin@gmx.de>
     +Author: Slavica Đukić <slawica92@hotmail.com>
      
          built-in add -i: implement the `help` command
      
     @@ -8,7 +8,7 @@
          To make sure that it renders exactly like the Perl version of `git add
          -i`, we also add a test case for that to `t3701-add-interactive.sh`.
      
     -    Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
     +    Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
       diff --git a/add-interactive.c b/add-interactive.c
     @@ -19,7 +19,7 @@
       }
       
      +static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,
     -+		    struct file_list *unused_files,
     ++		    struct string_list *unused_files,
      +		    struct list_options *unused_opts)
      +{
      +	color_fprintf_ln(stdout, s->help_color, "status        - %s",
     @@ -38,23 +38,17 @@
      +	return 0;
      +}
      +
     - struct print_command_item_data {
     - 	const char *color, *reset;
     - };
     + typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
     + 			 struct string_list *files,
     + 			 struct list_options *opts);
      @@
     - 		N_("What now"), command_prompt_help
     - 	};
     - 	struct command_item
     --		status = { { "status" }, run_status };
     -+		status = { { "status" }, run_status },
     -+		help = { { "help" }, run_help };
     - 	struct command_item *commands[] = {
     --		&status
     -+		&status,
     -+		&help
     + 		command_t command;
     + 	} command_list[] = {
     + 		{ "status", run_status },
     ++		{ "help", run_help },
       	};
     + 	struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
       
     - 	struct print_file_item_data print_file_item_data = {
      
       diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
       --- a/t/t3701-add-interactive.sh

-- 
gitgitgadget

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

* [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
  2019-11-04 12:15       ` [PATCH v5 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
@ 2019-11-04 12:15         ` Johannes Schindelin via GitGitGadget
  2019-11-08  4:49           ` Junio C Hamano
  2019-11-04 12:15         ` [PATCH v5 2/9] diff: export diffstat interface Daniel Ferreira via GitGitGadget
                           ` (8 subsequent siblings)
  9 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-11-04 12:15 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Johannes Schindelin

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

This is hardly the first conversion of a Git command that is implemented
as a script to a built-in. So far, the most successful strategy for such
conversions has been to add a built-in helper and call that for more and
more functionality from the script, as more and more parts are
converted.

With the interactive add, we choose a different strategy. The sole
reason for this is that on Windows (where such a conversion has the most
benefits in terms of speed and robustness) we face the very specific
problem that a `system()` call in Perl seems to close `stdin` in the
parent process when the spawned process consumes even one character from
`stdin`. And that just does not work for us here, as it would stop the
main loop as soon as any interactive command was performed by the
helper. Which is almost all of the commands in `git add -i`.

It is almost as if Perl told us once again that it does not want us to
use it on Windows.

Instead, we follow the opposite route where we start with a bare-bones
version of the built-in interactive add, guarded by the new
`add.interactive.useBuiltin` config variable, and then add more and more
functionality to it, until it is feature complete.

At this point, the built-in version of `git add -i` only states that it
cannot do anything yet ;-)

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/config/add.txt |  5 +++++
 Makefile                     |  1 +
 add-interactive.c            |  7 +++++++
 add-interactive.h            |  8 ++++++++
 builtin/add.c                | 10 ++++++++++
 t/README                     |  4 ++++
 6 files changed, 35 insertions(+)
 create mode 100644 add-interactive.c
 create mode 100644 add-interactive.h

diff --git a/Documentation/config/add.txt b/Documentation/config/add.txt
index 4d753f006e..c9f748f81c 100644
--- a/Documentation/config/add.txt
+++ b/Documentation/config/add.txt
@@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::
 	option of linkgit:git-add[1].  `add.ignore-errors` is deprecated,
 	as it does not follow the usual naming convention for configuration
 	variables.
+
+add.interactive.useBuiltin::
+	[EXPERIMENTAL] Set to `true` to use the experimental built-in
+	implementation of the interactive version of linkgit:git-add[1]
+	instead of the Perl script version. Is `false` by default.
diff --git a/Makefile b/Makefile
index 58b92af54b..6c4a1e0ee5 100644
--- a/Makefile
+++ b/Makefile
@@ -823,6 +823,7 @@ LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentat
 	-name '*.h' -print)))
 
 LIB_OBJS += abspath.o
+LIB_OBJS += add-interactive.o
 LIB_OBJS += advice.o
 LIB_OBJS += alias.o
 LIB_OBJS += alloc.o
diff --git a/add-interactive.c b/add-interactive.c
new file mode 100644
index 0000000000..482e458dc6
--- /dev/null
+++ b/add-interactive.c
@@ -0,0 +1,7 @@
+#include "cache.h"
+#include "add-interactive.h"
+
+int run_add_i(struct repository *r, const struct pathspec *ps)
+{
+	die(_("No commands are available in the built-in `git add -i` yet!"));
+}
diff --git a/add-interactive.h b/add-interactive.h
new file mode 100644
index 0000000000..7043b8741d
--- /dev/null
+++ b/add-interactive.h
@@ -0,0 +1,8 @@
+#ifndef ADD_INTERACTIVE_H
+#define ADD_INTERACTIVE_H
+
+struct repository;
+struct pathspec;
+int run_add_i(struct repository *r, const struct pathspec *ps);
+
+#endif
diff --git a/builtin/add.c b/builtin/add.c
index dd18e5c9b6..4f625691b5 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -20,6 +20,7 @@
 #include "bulk-checkin.h"
 #include "argv-array.h"
 #include "submodule.h"
+#include "add-interactive.h"
 
 static const char * const builtin_add_usage[] = {
 	N_("git add [<options>] [--] <pathspec>..."),
@@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,
 {
 	int status, i;
 	struct argv_array argv = ARGV_ARRAY_INIT;
+	int use_builtin_add_i =
+		git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
+	if (use_builtin_add_i < 0)
+		git_config_get_bool("add.interactive.usebuiltin",
+				    &use_builtin_add_i);
+
+	if (use_builtin_add_i == 1 && !patch_mode)
+		return !!run_add_i(the_repository, pathspec);
 
 	argv_array_push(&argv, "add--interactive");
 	if (patch_mode)
@@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb)
 		ignore_add_errors = git_config_bool(var, value);
 		return 0;
 	}
+
 	return git_default_config(var, value, cb);
 }
 
diff --git a/t/README b/t/README
index 60d5b77bcc..5132ec83f8 100644
--- a/t/README
+++ b/t/README
@@ -397,6 +397,10 @@ 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_ADD_I_USE_BUILTIN=<boolean>, when true, enables the
+built-in version of git add -i. See 'add.interactive.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
-- 
gitgitgadget


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

* [PATCH v5 2/9] diff: export diffstat interface
  2019-11-04 12:15       ` [PATCH v5 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  2019-11-04 12:15         ` [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
@ 2019-11-04 12:15         ` Daniel Ferreira via GitGitGadget
  2019-11-08  4:56           ` Junio C Hamano
  2019-11-04 12:15         ` [PATCH v5 3/9] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
                           ` (7 subsequent siblings)
  9 siblings, 1 reply; 124+ messages in thread
From: Daniel Ferreira via GitGitGadget @ 2019-11-04 12:15 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Daniel Ferreira

From: Daniel Ferreira <bnmvco@gmail.com>

Make the diffstat interface (namely, the diffstat_t struct and
compute_diffstat) no longer be internal to diff.c and allow it to be used
by other parts of git.

This is helpful for code that may want to easily extract information
from files using the diff machinery, while flushing it differently from
how the show_* functions used by diff_flush() do it. One example is the
builtin implementation of git-add--interactive's status.

Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 39 ++++++++++++++++-----------------------
 diff.h | 20 ++++++++++++++++++++
 2 files changed, 36 insertions(+), 23 deletions(-)

diff --git a/diff.c b/diff.c
index afe4400a60..5703a9b78f 100644
--- a/diff.c
+++ b/diff.c
@@ -2495,22 +2495,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)
 	}
 }
 
-struct diffstat_t {
-	int nr;
-	int alloc;
-	struct diffstat_file {
-		char *from_name;
-		char *name;
-		char *print_name;
-		const char *comments;
-		unsigned is_unmerged:1;
-		unsigned is_binary:1;
-		unsigned is_renamed:1;
-		unsigned is_interesting:1;
-		uintmax_t added, deleted;
-	} **files;
-};
-
 static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
 					  const char *name_a,
 					  const char *name_b)
@@ -3157,7 +3141,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o
 	gather_dirstat(options, &dir, changed, "", 0);
 }
 
-static void free_diffstat_info(struct diffstat_t *diffstat)
+void free_diffstat_info(struct diffstat_t *diffstat)
 {
 	int i;
 	for (i = 0; i < diffstat->nr; i++) {
@@ -6283,12 +6267,7 @@ void diff_flush(struct diff_options *options)
 	    dirstat_by_line) {
 		struct diffstat_t diffstat;
 
-		memset(&diffstat, 0, sizeof(struct diffstat_t));
-		for (i = 0; i < q->nr; i++) {
-			struct diff_filepair *p = q->queue[i];
-			if (check_pair_status(p))
-				diff_flush_stat(p, options, &diffstat);
-		}
+		compute_diffstat(options, &diffstat, q);
 		if (output_format & DIFF_FORMAT_NUMSTAT)
 			show_numstat(&diffstat, options);
 		if (output_format & DIFF_FORMAT_DIFFSTAT)
@@ -6621,6 +6600,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)
 	return ignored;
 }
 
+void compute_diffstat(struct diff_options *options,
+		      struct diffstat_t *diffstat,
+		      struct diff_queue_struct *q)
+{
+	int i;
+
+	memset(diffstat, 0, sizeof(struct diffstat_t));
+	for (i = 0; i < q->nr; i++) {
+		struct diff_filepair *p = q->queue[i];
+		if (check_pair_status(p))
+			diff_flush_stat(p, options, diffstat);
+	}
+}
+
 void diff_addremove(struct diff_options *options,
 		    int addremove, unsigned mode,
 		    const struct object_id *oid,
diff --git a/diff.h b/diff.h
index 7f8f024feb..d986ddc3b5 100644
--- a/diff.h
+++ b/diff.h
@@ -245,6 +245,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);
 void diff_emit_submodule_pipethrough(struct diff_options *o,
 				     const char *line, int len);
 
+struct diffstat_t {
+	int nr;
+	int alloc;
+	struct diffstat_file {
+		char *from_name;
+		char *name;
+		char *print_name;
+		const char *comments;
+		unsigned is_unmerged:1;
+		unsigned is_binary:1;
+		unsigned is_renamed:1;
+		unsigned is_interesting:1;
+		uintmax_t added, deleted;
+	} **files;
+};
+
 enum color_diff {
 	DIFF_RESET = 0,
 	DIFF_CONTEXT = 1,
@@ -334,6 +350,10 @@ void diff_change(struct diff_options *,
 
 struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);
 
+void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
+		      struct diff_queue_struct *q);
+void free_diffstat_info(struct diffstat_t *diffstat);
+
 #define DIFF_SETUP_REVERSE      	1
 #define DIFF_SETUP_USE_SIZE_CACHE	4
 
-- 
gitgitgadget


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

* [PATCH v5 3/9] built-in add -i: implement the `status` command
  2019-11-04 12:15       ` [PATCH v5 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  2019-11-04 12:15         ` [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
  2019-11-04 12:15         ` [PATCH v5 2/9] diff: export diffstat interface Daniel Ferreira via GitGitGadget
@ 2019-11-04 12:15         ` Daniel Ferreira via GitGitGadget
  2019-11-08  5:01           ` Junio C Hamano
  2019-11-04 12:15         ` [PATCH v5 4/9] built-in add -i: color the header in " Slavica Đukić via GitGitGadget
                           ` (6 subsequent siblings)
  9 siblings, 1 reply; 124+ messages in thread
From: Daniel Ferreira via GitGitGadget @ 2019-11-04 12:15 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Daniel Ferreira

From: Daniel Ferreira <bnmvco@gmail.com>

This implements the `status` command of `git add -i`. The data
structures introduced in this commit will be extended later, as needed.

At this point, we re-implement only part of the `list_and_choose()`
function of the Perl script `git-add--interactive.perl` and call it
`list()`. It does not yet color anything, or do columns, or allow user
input.

Over the course of the next commits, we will introduce a
`list_and_choose()` function that uses `list()` to display the list of
options and let the user choose one or more of the displayed items. This
will be used to implement the main loop of the built-in `git add -i`, at
which point the new `status` command can actually be used.

Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 251 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 250 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index 482e458dc6..aa35184d87 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -1,7 +1,256 @@
 #include "cache.h"
 #include "add-interactive.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "refs.h"
+#include "string-list.h"
+
+struct add_i_state {
+	struct repository *r;
+};
+
+static void init_add_i_state(struct add_i_state *s, struct repository *r)
+{
+       s->r = r;
+}
+
+struct list_options {
+	const char *header;
+	void (*print_item)(int i, struct string_list_item *item, void *print_item_data);
+	void *print_item_data;
+};
+
+static void list(struct string_list *list, struct list_options *opts)
+{
+	int i;
+
+	if (!list->nr)
+		return;
+
+	if (opts->header)
+		printf("%s\n", opts->header);
+
+	for (i = 0; i < list->nr; i++) {
+		opts->print_item(i, list->items + i, opts->print_item_data);
+		putchar('\n');
+	}
+}
+
+struct adddel {
+	uintmax_t add, del;
+	unsigned seen:1, binary:1;
+};
+
+struct file_item {
+	struct adddel index, worktree;
+};
+
+static void add_file_item(struct string_list *files, const char *name)
+{
+	struct file_item *item = xcalloc(sizeof(*item), 1);
+
+	string_list_append(files, name)->util = item;
+}
+
+struct pathname_entry {
+	struct hashmap_entry ent;
+	const char *name;
+	struct file_item *item;
+};
+
+static int pathname_entry_cmp(const void *unused_cmp_data,
+			      const struct hashmap_entry *he1,
+			      const struct hashmap_entry *he2,
+			      const void *name)
+{
+	const struct pathname_entry *e1 =
+		container_of(he1, const struct pathname_entry, ent);
+	const struct pathname_entry *e2 =
+		container_of(he2, const struct pathname_entry, ent);
+
+	return strcmp(e1->name, name ? (const char *)name : e2->name);
+}
+
+struct collection_status {
+	enum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase;
+
+	const char *reference;
+
+	struct string_list *files;
+	struct hashmap file_map;
+};
+
+static void collect_changes_cb(struct diff_queue_struct *q,
+			       struct diff_options *options,
+			       void *data)
+{
+	struct collection_status *s = data;
+	struct diffstat_t stat = { 0 };
+	int i;
+
+	if (!q->nr)
+		return;
+
+	compute_diffstat(options, &stat, q);
+
+	for (i = 0; i < stat.nr; i++) {
+		const char *name = stat.files[i]->name;
+		int hash = strhash(name);
+		struct pathname_entry *entry;
+		struct file_item *file_item;
+		struct adddel *adddel;
+
+		entry = hashmap_get_entry_from_hash(&s->file_map, hash, name,
+						    struct pathname_entry, ent);
+		if (!entry) {
+			add_file_item(s->files, name);
+
+			entry = xcalloc(sizeof(*entry), 1);
+			hashmap_entry_init(&entry->ent, hash);
+			entry->name = s->files->items[s->files->nr - 1].string;
+			entry->item = s->files->items[s->files->nr - 1].util;
+			hashmap_add(&s->file_map, &entry->ent);
+		}
+
+		file_item = entry->item;
+		adddel = s->phase == FROM_INDEX ?
+			&file_item->index : &file_item->worktree;
+		adddel->seen = 1;
+		adddel->add = stat.files[i]->added;
+		adddel->del = stat.files[i]->deleted;
+		if (stat.files[i]->is_binary)
+			adddel->binary = 1;
+	}
+	free_diffstat_info(&stat);
+}
+
+static int get_modified_files(struct repository *r, struct string_list *files,
+			      const struct pathspec *ps)
+{
+	struct object_id head_oid;
+	int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+					     &head_oid, NULL);
+	struct collection_status s = { FROM_WORKTREE };
+
+	if (discard_index(r->index) < 0 ||
+	    repo_read_index_preload(r, ps, 0) < 0)
+		return error(_("could not read index"));
+
+	string_list_clear(files, 1);
+	s.files = files;
+	hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);
+
+	for (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {
+		struct rev_info rev;
+		struct setup_revision_opt opt = { 0 };
+
+		opt.def = is_initial ?
+			empty_tree_oid_hex() : oid_to_hex(&head_oid);
+
+		init_revisions(&rev, NULL);
+		setup_revisions(0, NULL, &rev, &opt);
+
+		rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+		rev.diffopt.format_callback = collect_changes_cb;
+		rev.diffopt.format_callback_data = &s;
+
+		if (ps)
+			copy_pathspec(&rev.prune_data, ps);
+
+		if (s.phase == FROM_INDEX)
+			run_diff_index(&rev, 1);
+		else {
+			rev.diffopt.flags.ignore_dirty_submodules = 1;
+			run_diff_files(&rev, 0);
+		}
+	}
+	hashmap_free_entries(&s.file_map, struct pathname_entry, ent);
+
+	/* While the diffs are ordered already, we ran *two* diffs... */
+	string_list_sort(files);
+
+	return 0;
+}
+
+static void render_adddel(struct strbuf *buf,
+				struct adddel *ad, const char *no_changes)
+{
+	if (ad->binary)
+		strbuf_addstr(buf, _("binary"));
+	else if (ad->seen)
+		strbuf_addf(buf, "+%"PRIuMAX"/-%"PRIuMAX,
+			    (uintmax_t)ad->add, (uintmax_t)ad->del);
+	else
+		strbuf_addstr(buf, no_changes);
+}
+
+struct print_file_item_data {
+	const char *modified_fmt;
+	struct strbuf buf, index, worktree;
+};
+
+static void print_file_item(int i, struct string_list_item *item,
+			    void *print_file_item_data)
+{
+	struct file_item *c = item->util;
+	struct print_file_item_data *d = print_file_item_data;
+
+	strbuf_reset(&d->index);
+	strbuf_reset(&d->worktree);
+	strbuf_reset(&d->buf);
+
+	render_adddel(&d->worktree, &c->worktree, _("nothing"));
+	render_adddel(&d->index, &c->index, _("unchanged"));
+	strbuf_addf(&d->buf, d->modified_fmt,
+		    d->index.buf, d->worktree.buf, item->string);
+
+	printf(" %2d: %s", i + 1, d->buf.buf);
+}
+
+static int run_status(struct add_i_state *s, const struct pathspec *ps,
+		      struct string_list *files, struct list_options *opts)
+{
+	if (get_modified_files(s->r, files, ps) < 0)
+		return -1;
+
+	list(files, opts);
+	putchar('\n');
+
+	return 0;
+}
 
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
-	die(_("No commands are available in the built-in `git add -i` yet!"));
+	struct add_i_state s = { NULL };
+	struct print_file_item_data print_file_item_data = {
+		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+	};
+	struct list_options opts = {
+		NULL, print_file_item, &print_file_item_data
+	};
+	struct strbuf header = STRBUF_INIT;
+	struct string_list files = STRING_LIST_INIT_DUP;
+	int res = 0;
+
+	init_add_i_state(&s, r);
+	strbuf_addstr(&header, "      ");
+	strbuf_addf(&header, print_file_item_data.modified_fmt,
+		    _("staged"), _("unstaged"), _("path"));
+	opts.header = header.buf;
+
+	if (discard_index(r->index) < 0 ||
+	    repo_read_index(r) < 0 ||
+	    repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
+					 NULL, NULL, NULL) < 0)
+		warning(_("could not refresh index"));
+
+	res = run_status(&s, ps, &files, &opts);
+
+	string_list_clear(&files, 1);
+	strbuf_release(&print_file_item_data.buf);
+	strbuf_release(&print_file_item_data.index);
+	strbuf_release(&print_file_item_data.worktree);
+	strbuf_release(&header);
+
+	return res;
 }
-- 
gitgitgadget


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

* [PATCH v5 4/9] built-in add -i: color the header in the `status` command
  2019-11-04 12:15       ` [PATCH v5 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                           ` (2 preceding siblings ...)
  2019-11-04 12:15         ` [PATCH v5 3/9] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
@ 2019-11-04 12:15         ` Slavica Đukić via GitGitGadget
  2019-11-04 12:15         ` [PATCH v5 5/9] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
                           ` (5 subsequent siblings)
  9 siblings, 0 replies; 124+ messages in thread
From: Slavica Đukić via GitGitGadget @ 2019-11-04 12:15 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Slavica Đukić

From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>

For simplicity, we only implemented the `status` command without colors.
This patch starts adding color, matching what the Perl script
`git-add--interactive.perl` does.

Original-Patch-By: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 41 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 37 insertions(+), 4 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index aa35184d87..174e07ce83 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -1,5 +1,7 @@
 #include "cache.h"
 #include "add-interactive.h"
+#include "color.h"
+#include "config.h"
 #include "diffcore.h"
 #include "revision.h"
 #include "refs.h"
@@ -7,11 +9,40 @@
 
 struct add_i_state {
 	struct repository *r;
+	int use_color;
+	char header_color[COLOR_MAXLEN];
 };
 
+static void init_color(struct repository *r, struct add_i_state *s,
+		       const char *slot_name, char *dst,
+		       const char *default_color)
+{
+	char *key = xstrfmt("color.interactive.%s", slot_name);
+	const char *value;
+
+	if (!s->use_color)
+		dst[0] = '\0';
+	else if (repo_config_get_value(r, key, &value) ||
+		 color_parse(value, dst))
+		strlcpy(dst, default_color, COLOR_MAXLEN);
+
+	free(key);
+}
+
 static void init_add_i_state(struct add_i_state *s, struct repository *r)
 {
-       s->r = r;
+	const char *value;
+
+	s->r = r;
+
+	if (repo_config_get_value(r, "color.interactive", &value))
+		s->use_color = -1;
+	else
+		s->use_color =
+			git_config_colorbool("color.interactive", value);
+	s->use_color = want_color(s->use_color);
+
+	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
 }
 
 struct list_options {
@@ -20,7 +51,8 @@ struct list_options {
 	void *print_item_data;
 };
 
-static void list(struct string_list *list, struct list_options *opts)
+static void list(struct add_i_state *s, struct string_list *list,
+		 struct list_options *opts)
 {
 	int i;
 
@@ -28,7 +60,8 @@ static void list(struct string_list *list, struct list_options *opts)
 		return;
 
 	if (opts->header)
-		printf("%s\n", opts->header);
+		color_fprintf_ln(stdout, s->header_color,
+				 "%s", opts->header);
 
 	for (i = 0; i < list->nr; i++) {
 		opts->print_item(i, list->items + i, opts->print_item_data);
@@ -213,7 +246,7 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	if (get_modified_files(s->r, files, ps) < 0)
 		return -1;
 
-	list(files, opts);
+	list(s, files, opts);
 	putchar('\n');
 
 	return 0;
-- 
gitgitgadget


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

* [PATCH v5 5/9] built-in add -i: implement the main loop
  2019-11-04 12:15       ` [PATCH v5 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                           ` (3 preceding siblings ...)
  2019-11-04 12:15         ` [PATCH v5 4/9] built-in add -i: color the header in " Slavica Đukić via GitGitGadget
@ 2019-11-04 12:15         ` Johannes Schindelin via GitGitGadget
  2019-11-08  5:17           ` Junio C Hamano
  2019-11-04 12:15         ` [PATCH v5 6/9] built-in add -i: show unique prefixes of the commands Johannes Schindelin via GitGitGadget
                           ` (4 subsequent siblings)
  9 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-11-04 12:15 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Johannes Schindelin

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

The reason why we did not start with the main loop to begin with is that
it is the first user of `list_and_choose()`, which uses the `list()`
function that we conveniently introduced for use by the `status`
command.

Apart from the "and choose" part, there are more differences between the
way the `status` command calls the `list_and_choose()` function in the
Perl version of `git add -i` compared to the other callers of said
function. The most important ones:

- The list is not only shown, but the user is also asked to make a
  choice, possibly selecting multiple entries.

- The list of items is prefixed with a marker indicating what items have
  been selected, if multi-selection is allowed.

- Initially, for each item a unique prefix (if there exists any within
  the given parameters) is determined, and shown in the list, and
  accepted as a shortcut for the selection.

These features will be implemented later, except the part where the user
can choose a command. At this stage, though, the built-in `git add -i`
still only supports the `status` command, with the remaining commands to
follow over the course of the next commits.

In addition, we also modify `list()` to support displaying the commands
in columns, even if there is currently only one.

The Perl script `git-add--interactive.perl` mixed the purposes of the
"list" and the "and choose" part into the same function. In the C
version, we will keep them separate instead, calling the `list()`
function from the `list_and_choose()` function.

Note that we only have a prompt ending in a single ">" at this stage;
later commits will add commands that display a double ">>" to indicate
that the user is in a different loop than the main one.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 135 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 133 insertions(+), 2 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 174e07ce83..c6f7fbad36 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -46,6 +46,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)
 }
 
 struct list_options {
+	int columns;
 	const char *header;
 	void (*print_item)(int i, struct string_list_item *item, void *print_item_data);
 	void *print_item_data;
@@ -54,7 +55,7 @@ struct list_options {
 static void list(struct add_i_state *s, struct string_list *list,
 		 struct list_options *opts)
 {
-	int i;
+	int i, last_lf = 0;
 
 	if (!list->nr)
 		return;
@@ -65,8 +66,96 @@ static void list(struct add_i_state *s, struct string_list *list,
 
 	for (i = 0; i < list->nr; i++) {
 		opts->print_item(i, list->items + i, opts->print_item_data);
+
+		if ((opts->columns) && ((i + 1) % (opts->columns))) {
+			putchar('\t');
+			last_lf = 0;
+		}
+		else {
+			putchar('\n');
+			last_lf = 1;
+		}
+	}
+
+	if (!last_lf)
 		putchar('\n');
+}
+struct list_and_choose_options {
+	struct list_options list_opts;
+
+	const char *prompt;
+};
+
+#define LIST_AND_CHOOSE_ERROR (-1)
+#define LIST_AND_CHOOSE_QUIT  (-2)
+
+/*
+ * Returns the selected index.
+ *
+ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
+ * `LIST_AND_CHOOSE_QUIT` is returned.
+ */
+static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
+			       struct list_and_choose_options *opts)
+{
+	struct strbuf input = STRBUF_INIT;
+	ssize_t res = LIST_AND_CHOOSE_ERROR;
+
+	for (;;) {
+		char *p, *endp;
+
+		strbuf_reset(&input);
+
+		list(s, items, &opts->list_opts);
+
+		printf("%s%s", opts->prompt, "> ");
+		fflush(stdout);
+
+		if (strbuf_getline(&input, stdin) == EOF) {
+			putchar('\n');
+			res = LIST_AND_CHOOSE_QUIT;
+			break;
+		}
+		strbuf_trim(&input);
+
+		if (!input.len)
+			break;
+
+		p = input.buf;
+		for (;;) {
+			size_t sep = strcspn(p, " \t\r\n,");
+			ssize_t index = -1;
+
+			if (!sep) {
+				if (!*p)
+					break;
+				p++;
+				continue;
+			}
+
+			if (isdigit(*p)) {
+				index = strtoul(p, &endp, 10) - 1;
+				if (endp != p + sep)
+					index = -1;
+			}
+
+			p[sep] = '\0';
+			if (index < 0 || index >= items->nr)
+				printf(_("Huh (%s)?\n"), p);
+			else {
+				res = index;
+				break;
+			}
+
+			p += sep + 1;
+		}
+
+		if (res != LIST_AND_CHOOSE_ERROR)
+			break;
 	}
+
+	strbuf_release(&input);
+	return res;
 }
 
 struct adddel {
@@ -252,20 +341,48 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	return 0;
 }
 
+typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
+			 struct string_list *files,
+			 struct list_options *opts);
+
+static void print_command_item(int i, struct string_list_item *item,
+			       void *print_command_item_data)
+{
+	printf(" %2d: %s", i + 1, item->string);
+}
+
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
+	struct list_and_choose_options main_loop_opts = {
+		{ 4, N_("*** Commands ***"), print_command_item, NULL },
+		N_("What now")
+	};
+	struct {
+		const char *string;
+		command_t command;
+	} command_list[] = {
+		{ "status", run_status },
+	};
+	struct string_list commands = STRING_LIST_INIT_NODUP;
+
 	struct print_file_item_data print_file_item_data = {
 		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 	};
 	struct list_options opts = {
-		NULL, print_file_item, &print_file_item_data
+		0, NULL, print_file_item, &print_file_item_data
 	};
 	struct strbuf header = STRBUF_INIT;
 	struct string_list files = STRING_LIST_INIT_DUP;
+	ssize_t i;
 	int res = 0;
 
+	for (i = 0; i < ARRAY_SIZE(command_list); i++)
+		string_list_append(&commands, command_list[i].string)
+			->util = command_list[i].command;
+
 	init_add_i_state(&s, r);
+
 	strbuf_addstr(&header, "      ");
 	strbuf_addf(&header, print_file_item_data.modified_fmt,
 		    _("staged"), _("unstaged"), _("path"));
@@ -279,11 +396,25 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 
 	res = run_status(&s, ps, &files, &opts);
 
+	for (;;) {
+		i = list_and_choose(&s, &commands, &main_loop_opts);
+		if (i == LIST_AND_CHOOSE_QUIT) {
+			printf(_("Bye.\n"));
+			res = 0;
+			break;
+		}
+		if (i != LIST_AND_CHOOSE_ERROR) {
+			command_t command = commands.items[i].util;
+			res = command(&s, ps, &files, &opts);
+		}
+	}
+
 	string_list_clear(&files, 1);
 	strbuf_release(&print_file_item_data.buf);
 	strbuf_release(&print_file_item_data.index);
 	strbuf_release(&print_file_item_data.worktree);
 	strbuf_release(&header);
+	string_list_clear(&commands, 0);
 
 	return res;
 }
-- 
gitgitgadget


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

* [PATCH v5 6/9] built-in add -i: show unique prefixes of the commands
  2019-11-04 12:15       ` [PATCH v5 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                           ` (4 preceding siblings ...)
  2019-11-04 12:15         ` [PATCH v5 5/9] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
@ 2019-11-04 12:15         ` Johannes Schindelin via GitGitGadget
  2019-11-04 12:15         ` [PATCH v5 7/9] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
                           ` (3 subsequent siblings)
  9 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-11-04 12:15 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Johannes Schindelin

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

Just like in the Perl script `git-add--interactive.perl`, for each
command a unique prefix is determined (if there exists any within the
given parameters), and shown in the list, and accepted as a shortcut for
the command.

To determine the unique prefixes, as well as to look up the command in
question, we use a copy of the list and sort it.

While this might seem like overkill for a single command, it will make
much more sense when all the commands are implemented, and when we reuse
the same logic to present a list of files to edit, with convenient
unique prefixes.

At the start of the development of this patch series, a dedicated data
structure was introduced that imitated the Trie that the Perl version
implements. However, this was deemed overkill, and we now simply sort
the list before determining the length of the unique prefixes by looking
at each item's neighbor. As a bonus, we now use the same sorted list to
perform a binary search using the user-provided prefix as search key.

Original-patch-by: Slavica Đukić <slawica92@hotmail.com>
Helped-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
---
 add-interactive.c | 188 +++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 177 insertions(+), 11 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index c6f7fbad36..eb559555ad 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -45,6 +45,132 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)
 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
 }
 
+/*
+ * A "prefix item list" is a list of items that are identified by a string, and
+ * a unique prefix (if any) is determined for each item.
+ *
+ * It is implemented in the form of a pair of `string_list`s, the first one
+ * duplicating the strings, with the `util` field pointing at a structure whose
+ * first field must be `size_t prefix_length`.
+ *
+ * That `prefix_length` field will be computed by `find_unique_prefixes()`; It
+ * will be set to zero if no valid, unique prefix could be found.
+ *
+ * The second `string_list` is called `sorted` and does _not_ duplicate the
+ * strings but simply reuses the first one's, with the `util` field pointing at
+ * the `string_item_list` of the first `string_list`. It  will be populated and
+ * sorted by `find_unique_prefixes()`.
+ */
+struct prefix_item_list {
+	struct string_list items;
+	struct string_list sorted;
+	size_t min_length, max_length;
+};
+#define PREFIX_ITEM_LIST_INIT \
+	{ STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, 1, 4 }
+
+static void prefix_item_list_clear(struct prefix_item_list *list)
+{
+	string_list_clear(&list->items, 1);
+	string_list_clear(&list->sorted, 0);
+}
+
+static void extend_prefix_length(struct string_list_item *p,
+				 const char *other_string, size_t max_length)
+{
+	size_t *len = p->util;
+
+	if (!*len || memcmp(p->string, other_string, *len))
+		return;
+
+	for (;;) {
+		char c = p->string[*len];
+
+		/*
+		 * Is `p` a strict prefix of `other`? Or have we exhausted the
+		 * maximal length of the prefix? Or is the current character a
+		 * multi-byte UTF-8 one? If so, there is no valid, unique
+		 * prefix.
+		 */
+		if (!c || ++*len > max_length || !isascii(c)) {
+			*len = 0;
+			break;
+		}
+
+		if (c != other_string[*len - 1])
+			break;
+	}
+}
+
+static void find_unique_prefixes(struct prefix_item_list *list)
+{
+	size_t i;
+
+	if (list->sorted.nr == list->items.nr)
+		return;
+
+	string_list_clear(&list->sorted, 0);
+	/* Avoid reallocating incrementally */
+	list->sorted.items = xmalloc(st_mult(sizeof(*list->sorted.items),
+					     list->items.nr));
+	list->sorted.nr = list->sorted.alloc = list->items.nr;
+
+	for (i = 0; i < list->items.nr; i++) {
+		list->sorted.items[i].string = list->items.items[i].string;
+		list->sorted.items[i].util = list->items.items + i;
+	}
+
+	string_list_sort(&list->sorted);
+
+	for (i = 0; i < list->sorted.nr; i++) {
+		struct string_list_item *sorted_item = list->sorted.items + i;
+		struct string_list_item *item = sorted_item->util;
+		size_t *len = item->util;
+
+		*len = 0;
+		while (*len < list->min_length) {
+			char c = item->string[(*len)++];
+
+			if (!c || !isascii(c)) {
+				*len = 0;
+				break;
+			}
+		}
+
+		if (i > 0)
+			extend_prefix_length(item, sorted_item[-1].string,
+					     list->max_length);
+		if (i + 1 < list->sorted.nr)
+			extend_prefix_length(item, sorted_item[1].string,
+					     list->max_length);
+	}
+}
+
+static ssize_t find_unique(const char *string, struct prefix_item_list *list)
+{
+	int index = string_list_find_insert_index(&list->sorted, string, 1);
+	struct string_list_item *item;
+
+	if (list->items.nr != list->sorted.nr)
+		BUG("prefix_item_list in inconsistent state (%"PRIuMAX
+		    " vs %"PRIuMAX")",
+		    (uintmax_t)list->items.nr, (uintmax_t)list->sorted.nr);
+
+	if (index < 0)
+		item = list->sorted.items[-1 - index].util;
+	else if (index > 0 &&
+		 starts_with(list->sorted.items[index - 1].string, string))
+		return -1;
+	else if (index + 1 < list->sorted.nr &&
+		 starts_with(list->sorted.items[index + 1].string, string))
+		return -1;
+	else if (index < list->sorted.nr)
+		item = list->sorted.items[index].util;
+	else
+		return -1;
+	return item - list->items.items;
+}
+
 struct list_options {
 	int columns;
 	const char *header;
@@ -95,18 +221,21 @@ struct list_and_choose_options {
  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
  * `LIST_AND_CHOOSE_QUIT` is returned.
  */
-static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
+static ssize_t list_and_choose(struct add_i_state *s,
+			       struct prefix_item_list *items,
 			       struct list_and_choose_options *opts)
 {
 	struct strbuf input = STRBUF_INIT;
 	ssize_t res = LIST_AND_CHOOSE_ERROR;
 
+	find_unique_prefixes(items);
+
 	for (;;) {
 		char *p, *endp;
 
 		strbuf_reset(&input);
 
-		list(s, items, &opts->list_opts);
+		list(s, &items->items, &opts->list_opts);
 
 		printf("%s%s", opts->prompt, "> ");
 		fflush(stdout);
@@ -140,7 +269,10 @@ static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
 			}
 
 			p[sep] = '\0';
-			if (index < 0 || index >= items->nr)
+			if (index < 0)
+				index = find_unique(p, items);
+
+			if (index < 0 || index >= items->items.nr)
 				printf(_("Huh (%s)?\n"), p);
 			else {
 				res = index;
@@ -306,6 +438,23 @@ static void render_adddel(struct strbuf *buf,
 		strbuf_addstr(buf, no_changes);
 }
 
+/* filters out prefixes which have special meaning to list_and_choose() */
+static int is_valid_prefix(const char *prefix, size_t prefix_len)
+{
+	return prefix_len && prefix &&
+		/*
+		 * We expect `prefix` to be NUL terminated, therefore this
+		 * `strcspn()` call is okay, even if it might do much more
+		 * work than strictly necessary.
+		 */
+		strcspn(prefix, " \t\r\n,") >= prefix_len &&	/* separators */
+		*prefix != '-' &&				/* deselection */
+		!isdigit(*prefix) &&				/* selection */
+		(prefix_len != 1 ||
+		 (*prefix != '*' &&				/* "all" wildcard */
+		  *prefix != '?'));				/* prompt help */
+}
+
 struct print_file_item_data {
 	const char *modified_fmt;
 	struct strbuf buf, index, worktree;
@@ -345,10 +494,23 @@ typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
 			 struct string_list *files,
 			 struct list_options *opts);
 
+struct command_item {
+	size_t prefix_length;
+	command_t command;
+};
+
 static void print_command_item(int i, struct string_list_item *item,
 			       void *print_command_item_data)
 {
-	printf(" %2d: %s", i + 1, item->string);
+	struct command_item *util = item->util;
+
+	if (!util->prefix_length ||
+	    !is_valid_prefix(item->string, util->prefix_length))
+		printf(" %2d: %s", i + 1, item->string);
+	else
+		printf(" %2d: [%.*s]%s", i + 1,
+		       (int)util->prefix_length, item->string,
+		       item->string + util->prefix_length);
 }
 
 int run_add_i(struct repository *r, const struct pathspec *ps)
@@ -364,7 +526,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	} command_list[] = {
 		{ "status", run_status },
 	};
-	struct string_list commands = STRING_LIST_INIT_NODUP;
+	struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
 
 	struct print_file_item_data print_file_item_data = {
 		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
@@ -377,9 +539,12 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	ssize_t i;
 	int res = 0;
 
-	for (i = 0; i < ARRAY_SIZE(command_list); i++)
-		string_list_append(&commands, command_list[i].string)
-			->util = command_list[i].command;
+	for (i = 0; i < ARRAY_SIZE(command_list); i++) {
+		struct command_item *util = xcalloc(sizeof(*util), 1);
+		util->command = command_list[i].command;
+		string_list_append(&commands.items, command_list[i].string)
+			->util = util;
+	}
 
 	init_add_i_state(&s, r);
 
@@ -404,8 +569,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 			break;
 		}
 		if (i != LIST_AND_CHOOSE_ERROR) {
-			command_t command = commands.items[i].util;
-			res = command(&s, ps, &files, &opts);
+			struct command_item *util =
+				commands.items.items[i].util;
+			res = util->command(&s, ps, &files, &opts);
 		}
 	}
 
@@ -414,7 +580,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	strbuf_release(&print_file_item_data.index);
 	strbuf_release(&print_file_item_data.worktree);
 	strbuf_release(&header);
-	string_list_clear(&commands, 0);
+	prefix_item_list_clear(&commands);
 
 	return res;
 }
-- 
gitgitgadget


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

* [PATCH v5 7/9] built-in add -i: support `?` (prompt help)
  2019-11-04 12:15       ` [PATCH v5 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                           ` (5 preceding siblings ...)
  2019-11-04 12:15         ` [PATCH v5 6/9] built-in add -i: show unique prefixes of the commands Johannes Schindelin via GitGitGadget
@ 2019-11-04 12:15         ` Johannes Schindelin via GitGitGadget
  2019-11-04 12:15         ` [PATCH v5 8/9] built-in add -i: use color in the main loop Slavica Đukić via GitGitGadget
                           ` (2 subsequent siblings)
  9 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-11-04 12:15 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Johannes Schindelin

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

With this change, we print out the same colored help text that the
Perl-based `git add -i` prints in the main loop when question mark is
entered.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index eb559555ad..d96e18fce5 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -11,6 +11,7 @@ struct add_i_state {
 	struct repository *r;
 	int use_color;
 	char header_color[COLOR_MAXLEN];
+	char help_color[COLOR_MAXLEN];
 };
 
 static void init_color(struct repository *r, struct add_i_state *s,
@@ -43,6 +44,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)
 	s->use_color = want_color(s->use_color);
 
 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
+	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
 }
 
 /*
@@ -210,6 +212,7 @@ struct list_and_choose_options {
 	struct list_options list_opts;
 
 	const char *prompt;
+	void (*print_help)(struct add_i_state *s);
 };
 
 #define LIST_AND_CHOOSE_ERROR (-1)
@@ -250,6 +253,11 @@ static ssize_t list_and_choose(struct add_i_state *s,
 		if (!input.len)
 			break;
 
+		if (!strcmp(input.buf, "?")) {
+			opts->print_help(s);
+			continue;
+		}
+
 		p = input.buf;
 		for (;;) {
 			size_t sep = strcspn(p, " \t\r\n,");
@@ -513,12 +521,24 @@ static void print_command_item(int i, struct string_list_item *item,
 		       item->string + util->prefix_length);
 }
 
+static void command_prompt_help(struct add_i_state *s)
+{
+	const char *help_color = s->help_color;
+	color_fprintf_ln(stdout, help_color, "%s", _("Prompt help:"));
+	color_fprintf_ln(stdout, help_color, "1          - %s",
+			 _("select a numbered item"));
+	color_fprintf_ln(stdout, help_color, "foo        - %s",
+			 _("select item based on unique prefix"));
+	color_fprintf_ln(stdout, help_color, "           - %s",
+			 _("(empty) select nothing"));
+}
+
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
 	struct list_and_choose_options main_loop_opts = {
 		{ 4, N_("*** Commands ***"), print_command_item, NULL },
-		N_("What now")
+		N_("What now"), command_prompt_help
 	};
 	struct {
 		const char *string;
-- 
gitgitgadget


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

* [PATCH v5 8/9] built-in add -i: use color in the main loop
  2019-11-04 12:15       ` [PATCH v5 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                           ` (6 preceding siblings ...)
  2019-11-04 12:15         ` [PATCH v5 7/9] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
@ 2019-11-04 12:15         ` Slavica Đukić via GitGitGadget
  2019-11-04 12:15         ` [PATCH v5 9/9] built-in add -i: implement the `help` command Slavica Đukić via GitGitGadget
  2019-11-13 12:40         ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  9 siblings, 0 replies; 124+ messages in thread
From: Slavica Đukić via GitGitGadget @ 2019-11-04 12:15 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Slavica Đukić

From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>

The error messages as well as the unique prefixes are colored in `git
add -i` by default; We need to do the same in the built-in version.

Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 35 +++++++++++++++++++++++++++++------
 1 file changed, 29 insertions(+), 6 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index d96e18fce5..64d84ba1dc 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -12,6 +12,9 @@ struct add_i_state {
 	int use_color;
 	char header_color[COLOR_MAXLEN];
 	char help_color[COLOR_MAXLEN];
+	char prompt_color[COLOR_MAXLEN];
+	char error_color[COLOR_MAXLEN];
+	char reset_color[COLOR_MAXLEN];
 };
 
 static void init_color(struct repository *r, struct add_i_state *s,
@@ -45,6 +48,9 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)
 
 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
 	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
+	init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE);
+	init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED);
+	init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET);
 }
 
 /*
@@ -240,7 +246,8 @@ static ssize_t list_and_choose(struct add_i_state *s,
 
 		list(s, &items->items, &opts->list_opts);
 
-		printf("%s%s", opts->prompt, "> ");
+		color_fprintf(stdout, s->prompt_color, "%s", opts->prompt);
+		fputs("> ", stdout);
 		fflush(stdout);
 
 		if (strbuf_getline(&input, stdin) == EOF) {
@@ -281,7 +288,8 @@ static ssize_t list_and_choose(struct add_i_state *s,
 				index = find_unique(p, items);
 
 			if (index < 0 || index >= items->items.nr)
-				printf(_("Huh (%s)?\n"), p);
+				color_fprintf_ln(stdout, s->error_color,
+						 _("Huh (%s)?"), p);
 			else {
 				res = index;
 				break;
@@ -507,18 +515,23 @@ struct command_item {
 	command_t command;
 };
 
+struct print_command_item_data {
+	const char *color, *reset;
+};
+
 static void print_command_item(int i, struct string_list_item *item,
 			       void *print_command_item_data)
 {
+	struct print_command_item_data *d = print_command_item_data;
 	struct command_item *util = item->util;
 
 	if (!util->prefix_length ||
 	    !is_valid_prefix(item->string, util->prefix_length))
 		printf(" %2d: %s", i + 1, item->string);
 	else
-		printf(" %2d: [%.*s]%s", i + 1,
-		       (int)util->prefix_length, item->string,
-		       item->string + util->prefix_length);
+		printf(" %2d: %s%.*s%s%s", i + 1,
+		       d->color, (int)util->prefix_length, item->string,
+		       d->reset, item->string + util->prefix_length);
 }
 
 static void command_prompt_help(struct add_i_state *s)
@@ -536,8 +549,9 @@ static void command_prompt_help(struct add_i_state *s)
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
+	struct print_command_item_data data = { "[", "]" };
 	struct list_and_choose_options main_loop_opts = {
-		{ 4, N_("*** Commands ***"), print_command_item, NULL },
+		{ 4, N_("*** Commands ***"), print_command_item, &data },
 		N_("What now"), command_prompt_help
 	};
 	struct {
@@ -568,6 +582,15 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 
 	init_add_i_state(&s, r);
 
+	/*
+	 * When color was asked for, use the prompt color for
+	 * highlighting, otherwise use square brackets.
+	 */
+	if (s.use_color) {
+		data.color = s.prompt_color;
+		data.reset = s.reset_color;
+	}
+
 	strbuf_addstr(&header, "      ");
 	strbuf_addf(&header, print_file_item_data.modified_fmt,
 		    _("staged"), _("unstaged"), _("path"));
-- 
gitgitgadget


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

* [PATCH v5 9/9] built-in add -i: implement the `help` command
  2019-11-04 12:15       ` [PATCH v5 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                           ` (7 preceding siblings ...)
  2019-11-04 12:15         ` [PATCH v5 8/9] built-in add -i: use color in the main loop Slavica Đukić via GitGitGadget
@ 2019-11-04 12:15         ` Slavica Đukić via GitGitGadget
  2019-11-13 12:40         ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  9 siblings, 0 replies; 124+ messages in thread
From: Slavica Đukić via GitGitGadget @ 2019-11-04 12:15 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Slavica Đukić

From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>

This imitates the code to show the help text from the Perl script
`git-add--interactive.perl` in the built-in version.

To make sure that it renders exactly like the Perl version of `git add
-i`, we also add a test case for that to `t3701-add-interactive.sh`.

Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c          | 21 +++++++++++++++++++++
 t/t3701-add-interactive.sh | 25 +++++++++++++++++++++++++
 2 files changed, 46 insertions(+)

diff --git a/add-interactive.c b/add-interactive.c
index 64d84ba1dc..4e5241e865 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -506,6 +506,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	return 0;
 }
 
+static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,
+		    struct string_list *unused_files,
+		    struct list_options *unused_opts)
+{
+	color_fprintf_ln(stdout, s->help_color, "status        - %s",
+			 _("show paths with changes"));
+	color_fprintf_ln(stdout, s->help_color, "update        - %s",
+			 _("add working tree state to the staged set of changes"));
+	color_fprintf_ln(stdout, s->help_color, "revert        - %s",
+			 _("revert staged set of changes back to the HEAD version"));
+	color_fprintf_ln(stdout, s->help_color, "patch         - %s",
+			 _("pick hunks and update selectively"));
+	color_fprintf_ln(stdout, s->help_color, "diff          - %s",
+			 _("view diff between HEAD and index"));
+	color_fprintf_ln(stdout, s->help_color, "add untracked - %s",
+			 _("add contents of untracked files to the staged set of changes"));
+
+	return 0;
+}
+
 typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
 			 struct string_list *files,
 			 struct list_options *opts);
@@ -559,6 +579,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 		command_t command;
 	} command_list[] = {
 		{ "status", run_status },
+		{ "help", run_help },
 	};
 	struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
 
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index d50e165ca8..d4f9386621 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' '
 	test_write_lines a b a b a a b a b a >expect &&
 	test_cmp expect a
 '
+
+test_expect_success 'show help from add--helper' '
+	git reset --hard &&
+	cat >expect <<-EOF &&
+
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>
+	<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>
+	<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>
+	<BOLD;RED>patch         - pick hunks and update selectively<RESET>
+	<BOLD;RED>diff          - view diff between HEAD and index<RESET>
+	<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>>$SP
+	Bye.
+	EOF
+	test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&
+	test_decode_color <actual.colored >actual &&
+	test_i18ncmp expect actual
+'
+
 test_done
-- 
gitgitgadget

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

* Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
  2019-11-04 12:15         ` [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
@ 2019-11-08  4:49           ` Junio C Hamano
  2019-11-09 11:06             ` Johannes Schindelin
  0 siblings, 1 reply; 124+ messages in thread
From: Junio C Hamano @ 2019-11-08  4:49 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Jeff Hostetler, Jeff King, Johannes Schindelin

"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> From: Johannes Schindelin <johannes.schindelin@gmx.de>
>
> This is hardly the first conversion of a Git command that is implemented
> as a script to a built-in. So far, the most successful strategy for such
> conversions has been to add a built-in helper and call that for more and
> more functionality from the script, as more and more parts are
> converted.
>
> With the interactive add, we choose a different strategy....

This is hardly the first conversion that we took the "build the
whole program piece by piece and flip the whole thing on with
usebuiltin" conversion successfully.  Pratik's rebase-in-c series
comes to mind.

Personally, I do not think the first two paragraphs of the proposed
log message do not belong here.  Cover letter is a different story
and it may make sense to explain why the approach was taken there,
but here, I'd prefer to see it more succinctly tell what approach is
taken and go directly to describe what this step in that approach
does to the readers, which is more important.

> diff --git a/Documentation/config/add.txt b/Documentation/config/add.txt
>  	variables.
> +
> +add.interactive.useBuiltin::
> +	[EXPERIMENTAL] Set to `true` to use the experimental built-in
> +	implementation of the interactive version of linkgit:git-add[1]
> +	instead of the Perl script version. Is `false` by default.

Good.

> diff --git a/Makefile b/Makefile
> index 58b92af54b..6c4a1e0ee5 100644
> --- a/Makefile
> +++ b/Makefile
>  LIB_OBJS += abspath.o
> +LIB_OBJS += add-interactive.o
>  LIB_OBJS += advice.o
>  LIB_OBJS += alias.o
>  LIB_OBJS += alloc.o

OK.

> diff --git a/add-interactive.c b/add-interactive.c
> new file mode 100644
> index 0000000000..482e458dc6
> --- /dev/null
> +++ b/add-interactive.c
> @@ -0,0 +1,7 @@
> +#include "cache.h"
> +#include "add-interactive.h"
> +
> +int run_add_i(struct repository *r, const struct pathspec *ps)
> +{
> +	die(_("No commands are available in the built-in `git add -i` yet!"));
> +}

OK, with or without s/commands/sub&/;

> diff --git a/add-interactive.h b/add-interactive.h
> new file mode 100644
> index 0000000000..7043b8741d
> --- /dev/null
> +++ b/add-interactive.h

OK.

> diff --git a/builtin/add.c b/builtin/add.c
> index dd18e5c9b6..4f625691b5 100644
> --- a/builtin/add.c
> +++ b/builtin/add.c
> @@ -20,6 +20,7 @@
>  #include "bulk-checkin.h"
>  #include "argv-array.h"
>  #include "submodule.h"
> +#include "add-interactive.h"
>  
>  static const char * const builtin_add_usage[] = {
>  	N_("git add [<options>] [--] <pathspec>..."),
> @@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,
>  {
>  	int status, i;
>  	struct argv_array argv = ARGV_ARRAY_INIT;
> +	int use_builtin_add_i =
> +		git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);

Have blank here at the boundary between decl and stmt ...

> +	if (use_builtin_add_i < 0)
> +		git_config_get_bool("add.interactive.usebuiltin",
> +				    &use_builtin_add_i);
> +

... and lose it here (optional).

> +	if (use_builtin_add_i == 1 && !patch_mode)
> +		return !!run_add_i(the_repository, pathspec);
>  

Strictly speaking, we can bypass the probing of environment and
config when upon the entry of the function, where patch_mode is
already known.  I do not know offhand if rearranging the code to
take advantage of that fact would result in a flow that is also
easier to follow, but I suspect it would.

> +GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the
> +built-in version of git add -i. See 'add.interactive.useBuiltin' in
> +git-config(1).

Makes sense.

Thanks.

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

* Re: [PATCH v5 2/9] diff: export diffstat interface
  2019-11-04 12:15         ` [PATCH v5 2/9] diff: export diffstat interface Daniel Ferreira via GitGitGadget
@ 2019-11-08  4:56           ` Junio C Hamano
  0 siblings, 0 replies; 124+ messages in thread
From: Junio C Hamano @ 2019-11-08  4:56 UTC (permalink / raw)
  To: Daniel Ferreira via GitGitGadget
  Cc: git, Jeff Hostetler, Jeff King, Johannes Schindelin,
	Daniel Ferreira

"Daniel Ferreira via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Daniel Ferreira <bnmvco@gmail.com>
>
> Make the diffstat interface (namely, the diffstat_t struct and
> compute_diffstat) no longer be internal to diff.c and allow it to be used
> by other parts of git.
>
> This is helpful for code that may want to easily extract information
> from files using the diff machinery, while flushing it differently from
> how the show_* functions used by diff_flush() do it. One example is the
> builtin implementation of git-add--interactive's status.
>
> Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
> Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  diff.c | 39 ++++++++++++++++-----------------------
>  diff.h | 20 ++++++++++++++++++++
>  2 files changed, 36 insertions(+), 23 deletions(-)

Looks good.  The diffstat API seems to be quite well isolated to
allow this with minimum (eh, rather no) changes.

Thanks.

>
> diff --git a/diff.c b/diff.c
> index afe4400a60..5703a9b78f 100644
> --- a/diff.c
> +++ b/diff.c
> @@ -2495,22 +2495,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)
>  	}
>  }
>  
> -struct diffstat_t {
> -	int nr;
> -	int alloc;
> -	struct diffstat_file {
> -		char *from_name;
> -		char *name;
> -		char *print_name;
> -		const char *comments;
> -		unsigned is_unmerged:1;
> -		unsigned is_binary:1;
> -		unsigned is_renamed:1;
> -		unsigned is_interesting:1;
> -		uintmax_t added, deleted;
> -	} **files;
> -};
> -
>  static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
>  					  const char *name_a,
>  					  const char *name_b)
> @@ -3157,7 +3141,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o
>  	gather_dirstat(options, &dir, changed, "", 0);
>  }
>  
> -static void free_diffstat_info(struct diffstat_t *diffstat)
> +void free_diffstat_info(struct diffstat_t *diffstat)
>  {
>  	int i;
>  	for (i = 0; i < diffstat->nr; i++) {
> @@ -6283,12 +6267,7 @@ void diff_flush(struct diff_options *options)
>  	    dirstat_by_line) {
>  		struct diffstat_t diffstat;
>  
> -		memset(&diffstat, 0, sizeof(struct diffstat_t));
> -		for (i = 0; i < q->nr; i++) {
> -			struct diff_filepair *p = q->queue[i];
> -			if (check_pair_status(p))
> -				diff_flush_stat(p, options, &diffstat);
> -		}
> +		compute_diffstat(options, &diffstat, q);
>  		if (output_format & DIFF_FORMAT_NUMSTAT)
>  			show_numstat(&diffstat, options);
>  		if (output_format & DIFF_FORMAT_DIFFSTAT)
> @@ -6621,6 +6600,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)
>  	return ignored;
>  }
>  
> +void compute_diffstat(struct diff_options *options,
> +		      struct diffstat_t *diffstat,
> +		      struct diff_queue_struct *q)
> +{
> +	int i;
> +
> +	memset(diffstat, 0, sizeof(struct diffstat_t));
> +	for (i = 0; i < q->nr; i++) {
> +		struct diff_filepair *p = q->queue[i];
> +		if (check_pair_status(p))
> +			diff_flush_stat(p, options, diffstat);
> +	}
> +}
> +
>  void diff_addremove(struct diff_options *options,
>  		    int addremove, unsigned mode,
>  		    const struct object_id *oid,
> diff --git a/diff.h b/diff.h
> index 7f8f024feb..d986ddc3b5 100644
> --- a/diff.h
> +++ b/diff.h
> @@ -245,6 +245,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);
>  void diff_emit_submodule_pipethrough(struct diff_options *o,
>  				     const char *line, int len);
>  
> +struct diffstat_t {
> +	int nr;
> +	int alloc;
> +	struct diffstat_file {
> +		char *from_name;
> +		char *name;
> +		char *print_name;
> +		const char *comments;
> +		unsigned is_unmerged:1;
> +		unsigned is_binary:1;
> +		unsigned is_renamed:1;
> +		unsigned is_interesting:1;
> +		uintmax_t added, deleted;
> +	} **files;
> +};
> +
>  enum color_diff {
>  	DIFF_RESET = 0,
>  	DIFF_CONTEXT = 1,
> @@ -334,6 +350,10 @@ void diff_change(struct diff_options *,
>  
>  struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);
>  
> +void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
> +		      struct diff_queue_struct *q);
> +void free_diffstat_info(struct diffstat_t *diffstat);
> +
>  #define DIFF_SETUP_REVERSE      	1
>  #define DIFF_SETUP_USE_SIZE_CACHE	4

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

* Re: [PATCH v5 3/9] built-in add -i: implement the `status` command
  2019-11-04 12:15         ` [PATCH v5 3/9] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
@ 2019-11-08  5:01           ` Junio C Hamano
  0 siblings, 0 replies; 124+ messages in thread
From: Junio C Hamano @ 2019-11-08  5:01 UTC (permalink / raw)
  To: Daniel Ferreira via GitGitGadget
  Cc: git, Jeff Hostetler, Jeff King, Johannes Schindelin,
	Daniel Ferreira

"Daniel Ferreira via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Daniel Ferreira <bnmvco@gmail.com>
>
> This implements the `status` command of `git add -i`. The data
> structures introduced in this commit will be extended later, as needed.
>
> At this point, we re-implement only part of the `list_and_choose()`
> function of the Perl script `git-add--interactive.perl` and call it
> `list()`. It does not yet color anything, or do columns, or allow user
> input.

OK, so that is why we unconditionally do the status thing directly
inside run_add_i() and nothing else.  Makes sense.

> Over the course of the next commits, we will introduce a
> `list_and_choose()` function that uses `list()` to display the list of
> options and let the user choose one or more of the displayed items. This
> will be used to implement the main loop of the built-in `git add -i`, at
> which point the new `status` command can actually be used.

Very well written.

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

* Re: [PATCH v5 5/9] built-in add -i: implement the main loop
  2019-11-04 12:15         ` [PATCH v5 5/9] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
@ 2019-11-08  5:17           ` Junio C Hamano
  2019-11-09 11:21             ` Johannes Schindelin
  0 siblings, 1 reply; 124+ messages in thread
From: Junio C Hamano @ 2019-11-08  5:17 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Jeff Hostetler, Jeff King, Johannes Schindelin

"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> From: Johannes Schindelin <johannes.schindelin@gmx.de>
>
> The reason why we did not start with the main loop to begin with is that
> it is the first user of `list_and_choose()`, which uses the `list()`
> function that we conveniently introduced for use by the `status`
> command.
>
> Apart from the "and choose" part, there are more differences between the
> way the `status` command calls the `list_and_choose()` function in the
> Perl version of `git add -i` compared to the other callers of said
> function. The most important ones:
>
> - The list is not only shown, but the user is also asked to make a
>   choice, possibly selecting multiple entries.

The list_and_choose() we have here shows and lets users choose and
returns the choice, but the above makes it sound as if it only shows
and the caller is responsible for asking the end-user input.  Is
this description outdated or something?

Perl allows us to return multiple choices, where it is a bit hard to
express it in C (perhaps because we are passing in an array of
structs to be shown as choices, list_and_choose could set a bit in
these structs to signal "this one, that one and that other one was
chosen", returning how many are chosen in total, to extend the
version here to bring it to feature-parity?).  So at this step, it
only lets the user one choice (or abort or ask for help).  Isn't the
lack of multiple choice the only difference this bullet item wants
to highlight?

> The Perl script `git-add--interactive.perl` mixed the purposes of the
> "list" and the "and choose" part into the same function. In the C
> version, we will keep them separate instead, calling the `list()`
> function from the `list_and_choose()` function.

That makes sense.

> +static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
> +			       struct list_and_choose_options *opts)
> +{
> +	struct strbuf input = STRBUF_INIT;
> +	ssize_t res = LIST_AND_CHOOSE_ERROR;
> +
> +	for (;;) {
> +		char *p, *endp;

The scope of endp looks way too wide in this function, isn't it?
Even in the final state of the series, it only gets used to parse
an integer input using strtoul, inside a block of three lines.

Other than that, the code at this step was a pleasant read overall.

Thanks.

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

* Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
  2019-11-08  4:49           ` Junio C Hamano
@ 2019-11-09 11:06             ` Johannes Schindelin
  2019-11-10  7:18               ` Junio C Hamano
  0 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin @ 2019-11-09 11:06 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

Hi Junio,

On Fri, 8 Nov 2019, Junio C Hamano wrote:

> "Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
> writes:
>
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> >
> > This is hardly the first conversion of a Git command that is implemented
> > as a script to a built-in. So far, the most successful strategy for such
> > conversions has been to add a built-in helper and call that for more and
> > more functionality from the script, as more and more parts are
> > converted.
> >
> > With the interactive add, we choose a different strategy....
>
> This is hardly the first conversion that we took the "build the
> whole program piece by piece and flip the whole thing on with
> usebuiltin" conversion successfully.  Pratik's rebase-in-c series
> comes to mind.
>
> Personally, I do not think the first two paragraphs of the proposed
> log message do not belong here.  Cover letter is a different story
> and it may make sense to explain why the approach was taken there,
> but here, I'd prefer to see it more succinctly tell what approach is
> taken and go directly to describe what this step in that approach
> does to the readers, which is more important.

I reworded the commit message:

    Start to implement a built-in version of `git add --interactive`

    To convert the interactive `add` to C, we start with a bare-bones
    version of the built-in interactive add, guarded by the new
    `add.interactive.useBuiltin` config variable, and then add more and more
    functionality to it, until it is feature complete.

    This is in contrast to previous conversions to C, where we started with
    a built-in helper that spawns the script by default, but optionally
    executes the C code instead. The sole reason for this deviation from
    previous practice is that on Windows (where such a conversion has the
    most benefits in terms of speed and robustness) we face the very
    specific problem that a `system()` call in Perl seems to close `stdin`
    in the parent process when the spawned process consumes even one
    character from `stdin`. And that just does not work for us here, as it
    would stop the main loop as soon as any interactive command was
    performed by the helper. Which is almost all of the commands in `git add
    -i`.

    It is almost as if Perl told us once again that it does not want us to
    use it on Windows.

    At this point, the built-in version of `git add -i` only states that it
    cannot do anything yet ;-)

Hopefully you like this one better?

> > diff --git a/builtin/add.c b/builtin/add.c
> > index dd18e5c9b6..4f625691b5 100644
> > --- a/builtin/add.c
> > +++ b/builtin/add.c
> > @@ -20,6 +20,7 @@
> >  #include "bulk-checkin.h"
> >  #include "argv-array.h"
> >  #include "submodule.h"
> > +#include "add-interactive.h"
> >
> >  static const char * const builtin_add_usage[] = {
> >  	N_("git add [<options>] [--] <pathspec>..."),
> > @@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,
> >  {
> >  	int status, i;
> >  	struct argv_array argv = ARGV_ARRAY_INIT;
> > +	int use_builtin_add_i =
> > +		git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
>
> Have blank here at the boundary between decl and stmt ...
>
> > +	if (use_builtin_add_i < 0)
> > +		git_config_get_bool("add.interactive.usebuiltin",
> > +				    &use_builtin_add_i);
> > +
>
> ... and lose it here (optional).

Done.
>
> > +	if (use_builtin_add_i == 1 && !patch_mode)
> > +		return !!run_add_i(the_repository, pathspec);
> >
>
> Strictly speaking, we can bypass the probing of environment and
> config when upon the entry of the function, where patch_mode is
> already known.  I do not know offhand if rearranging the code to
> take advantage of that fact would result in a flow that is also
> easier to follow, but I suspect it would.

Okay. I changed it to:

	if (!patch_mode) {
		if (use_builtin_add_i < 0)
			git_config_get_bool("add.interactive.usebuiltin",
					    &use_builtin_add_i);
		if (use_builtin_add_i == 1)
			return !!run_add_i(the_repository, pathspec);
	}

Thanks,
Dscho

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

* Re: [PATCH v5 5/9] built-in add -i: implement the main loop
  2019-11-08  5:17           ` Junio C Hamano
@ 2019-11-09 11:21             ` Johannes Schindelin
  0 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin @ 2019-11-09 11:21 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

Hi Junio,

On Fri, 8 Nov 2019, Junio C Hamano wrote:

> "Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
> writes:
>
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> >
> > The reason why we did not start with the main loop to begin with is that
> > it is the first user of `list_and_choose()`, which uses the `list()`
> > function that we conveniently introduced for use by the `status`
> > command.
> >
> > Apart from the "and choose" part, there are more differences between the
> > way the `status` command calls the `list_and_choose()` function in the
> > Perl version of `git add -i` compared to the other callers of said
> > function. The most important ones:
> >
> > - The list is not only shown, but the user is also asked to make a
> >   choice, possibly selecting multiple entries.
>
> The list_and_choose() we have here shows and lets users choose and
> returns the choice, but the above makes it sound as if it only shows
> and the caller is responsible for asking the end-user input.  Is
> this description outdated or something?
>
> Perl allows us to return multiple choices, where it is a bit hard to
> express it in C (perhaps because we are passing in an array of
> structs to be shown as choices, list_and_choose could set a bit in
> these structs to signal "this one, that one and that other one was
> chosen", returning how many are chosen in total, to extend the
> version here to bring it to feature-parity?).  So at this step, it
> only lets the user one choice (or abort or ask for help).  Isn't the
> lack of multiple choice the only difference this bullet item wants
> to highlight?

I changed the commit message to:

    built-in add -i: implement the main loop

    The reason why we did not start with the main loop to begin with is that
    it is the first user of `list_and_choose()`, which uses the `list()`
    function that we conveniently introduced for use by the `status`
    command.

    In contrast to the Perl version, in the built-in interactive `add`, we
    will keep the `list()` function (which only displays items) and the
    `list_and_choose()` function (which uses `list()` to display the items,
    and only takes care of the "and choose" part) separate.

    The `list_and_choose()` function, as implemented in
    `git-add--interactive.perl` knows a few more tricks than the function we
    introduce in this patch:

    - There is a flag to let the user select multiple items.

    - In multi-select mode, the list of items is prefixed with a marker
      indicating what items have been selected.

    - Initially, for each item a unique prefix is determined (if there
      exists any within the given parameters), and shown in the list, and
      accepted as a shortcut for the selection.

    These features will be implemented in the C version later.

    This patch does not add any new main loop command, of course, the
    built-in `git add -i` still only supports the `status` command. The
    remaining commands to follow over the course of the next commits.

    To accommodate for listing the commands in columns, preparing for the
    commands that will be implemented over the course of the next
    patches/patch series, we teach the `list()` function to do precisely
    that.

    Note that we only have a prompt ending in a single ">" at this stage;
    later commits will add commands that display a double ">>" to indicate
    that the user is in a different loop than the main one.

> > The Perl script `git-add--interactive.perl` mixed the purposes of the
> > "list" and the "and choose" part into the same function. In the C
> > version, we will keep them separate instead, calling the `list()`
> > function from the `list_and_choose()` function.
>
> That makes sense.
>
> > +static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
> > +			       struct list_and_choose_options *opts)
> > +{
> > +	struct strbuf input = STRBUF_INIT;
> > +	ssize_t res = LIST_AND_CHOOSE_ERROR;
> > +
> > +	for (;;) {
> > +		char *p, *endp;
>
> The scope of endp looks way too wide in this function, isn't it?
> Even in the final state of the series, it only gets used to parse
> an integer input using strtoul, inside a block of three lines.

True. I moved the declaration of `endp` into that three-line (now
four-line) scope.

> Other than that, the code at this step was a pleasant read overall.

Thank you for your review!
Dscho

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

* Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
  2019-11-09 11:06             ` Johannes Schindelin
@ 2019-11-10  7:18               ` Junio C Hamano
  2019-11-11  9:15                 ` Johannes Schindelin
  0 siblings, 1 reply; 124+ messages in thread
From: Junio C Hamano @ 2019-11-10  7:18 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

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

> I reworded the commit message:
>
>     Start to implement a built-in version of `git add --interactive`
>
>     To convert the interactive `add` to C, we start with a bare-bones
>     version of the built-in interactive add, guarded by the new
>     `add.interactive.useBuiltin` config variable, and then add more and more
>     functionality to it, until it is feature complete.
>
>     This is in contrast to previous conversions to C, where we started with
>     a built-in helper that spawns the script by default, but optionally
>     executes the C code instead. The sole reason for this deviation from
>     previous practice is that on Windows (where such a conversion has the
>     most benefits in terms of speed and robustness) we face the very
>     specific problem that a `system()` call in Perl seems to close `stdin`
>     in the parent process when the spawned process consumes even one
>     character from `stdin`. And that just does not work for us here, as it
>     would stop the main loop as soon as any interactive command was
>     performed by the helper. Which is almost all of the commands in `git add
>     -i`.
>
>     It is almost as if Perl told us once again that it does not want us to
>     use it on Windows.
>
>     At this point, the built-in version of `git add -i` only states that it
>     cannot do anything yet ;-)
>
> Hopefully you like this one better?

Not really.  I find the "we could do the other way but we don't, and
I hate Perl" totally irrelevant and misleading.

Unless it is in GSoC or something that wants to avoid a total
failure of nothing to show at the end of the period, in which case a
piecemeal "we did not finish, but at least we have a handful of
subcommands rewritten to show for the consolation prize" might be a
way to have "something" that resembles "working".  But if we value
the quality of the final product over having to have something to
show in a set term (like you as a paid programmer working as a
professional), building piece by piece in the final framework
(i.e. "in C as a builtin") and flipping the "useBuiltin" to turn on
the whole thing at the end would be the preferrable way to do this
kind of thing, I would think.  Also, it is less wasteful, not having
to worry about the inter-language glue code.  Even if the original
is in shell, not in Perl, we have quite a lot of glue code to throw
values back and forth across the language boundary to imitate what
used to be mere assignments to global shell variables between shell
functions and their callers, and always somebody screws up quoting
there ;-)

>> > +	if (use_builtin_add_i == 1 && !patch_mode)
>> > +		return !!run_add_i(the_repository, pathspec);
>> >
>>
>> Strictly speaking, we can bypass the probing of environment and
>> config when upon the entry of the function, where patch_mode is
>> already known.  I do not know offhand if rearranging the code to
>> take advantage of that fact would result in a flow that is also
>> easier to follow, but I suspect it would.
>
> Okay. I changed it to:
>
> 	if (!patch_mode) {
> 		if (use_builtin_add_i < 0)
> 			git_config_get_bool("add.interactive.usebuiltin",
> 					    &use_builtin_add_i);
> 		if (use_builtin_add_i == 1)
> 			return !!run_add_i(the_repository, pathspec);
> 	}

Doesn't look so bad as I feared.

Thanks.

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

* Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
  2019-11-10  7:18               ` Junio C Hamano
@ 2019-11-11  9:15                 ` Johannes Schindelin
  2019-11-11 12:09                   ` Junio C Hamano
  0 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin @ 2019-11-11  9:15 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

Hi Junio,

On Sun, 10 Nov 2019, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
> > I reworded the commit message:
> >
> >     Start to implement a built-in version of `git add --interactive`
> >
> >     To convert the interactive `add` to C, we start with a bare-bones
> >     version of the built-in interactive add, guarded by the new
> >     `add.interactive.useBuiltin` config variable, and then add more and more
> >     functionality to it, until it is feature complete.
> >
> >     This is in contrast to previous conversions to C, where we started with
> >     a built-in helper that spawns the script by default, but optionally
> >     executes the C code instead. The sole reason for this deviation from
> >     previous practice is that on Windows (where such a conversion has the
> >     most benefits in terms of speed and robustness) we face the very
> >     specific problem that a `system()` call in Perl seems to close `stdin`
> >     in the parent process when the spawned process consumes even one
> >     character from `stdin`. And that just does not work for us here, as it
> >     would stop the main loop as soon as any interactive command was
> >     performed by the helper. Which is almost all of the commands in `git add
> >     -i`.
> >
> >     It is almost as if Perl told us once again that it does not want us to
> >     use it on Windows.
> >
> >     At this point, the built-in version of `git add -i` only states that it
> >     cannot do anything yet ;-)
> >
> > Hopefully you like this one better?
>
> Not really.  I find the "we could do the other way but we don't, and
> I hate Perl" totally irrelevant and misleading.

Okay, I understand that you take exception at my criticism of Git's use
of Perl, and I fully understand that you think I blame you for it
because you added most of it.

And I agree that this sidetrack is totally irrelevant for the patch
under discussion.

I do think, however, that the discussion of "we wanted to do it the
other way, but when we tried, it did not work" is relevant, even if I
shortened it to "we use a different approach than previous conversions,
because that previous approach would not work".

Truth be told: I would have _much rather_ stayed with the previous
`--helper` approach, as that would have made it possible to have a
passing test suite at every step, with and without
`GIT_TEST_ADD_I_USE_BUILTIN=true`.

The "let GIT_TEST_ADD_I_USE_BUILTIN=true use the built-in even for
functions we _know_ are not implemented" way only gives us the full
comfort of a passing test suite at the very end of all six patch series,
if which the patch series we are currently discussing is merely the
first.

If I was a reviewer of this patch series rather than the sender, I would
be a bit uncomfortable with the fact that `GIT_TEST_ADD_I_USE_BUILTIN`
cannot be added to the CI/PR builds' `linux-gcc` over-job, not until
much, much later. In fact, it can only be added as the very last patch
in the very last of the six patch series.

And as I write this, I realize that I never spelled that out in the
commit message, and it is a rather important point for reviewers to see
addressed pre-emptively, in my opinion.

Therefore I revised the commit message again:

    Start to implement a built-in version of `git add --interactive`

    Unlike previous conversions to C, where we started with a built-in
    helper, we start this conversion by adding an interception in the
    `run_add_interactive()` function when the new opt-in
    `add.interactive.useBuiltin` config knob is turned on (or the
    corresponding environment variable `GIT_TEST_ADD_I_USE_BUILTIN`), and
    calling the new internal API function `run_add_i()` that is implemented
    directly in libgit.a.

    At this point, the built-in version of `git add -i` only states that it
    cannot do anything yet. In subsequent patches/patch series, the
    `run_add_i()` function will gain more and more functionality, until it
    is feature complete. The whole arc of the conversion can be found in the
    PRs #170-175 at https://github.com/gitgitgadget/git.

    The "--helper approach" can unfortunately not be used here: on Windows
    we face the very specific problem that a `system()` call in
    Perl seems to close `stdin` in the parent process when the spawned
    process consumes even one character from `stdin`. Which prevents us from
    implementing the main loop in C and still trying to hand off to the Perl
    script.

    The very real downside of the approach we have to take here is that the
    test suite won't pass with `GIT_TEST_ADD_I_USE_BUILTIN=true` until the
    conversion is complete (the `--helper` approach would have let it pass,
    even at each of the incremental conversion steps).

Any suggestions how to improve that commit message?

Ciao,
Dscho

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

* Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
  2019-11-11  9:15                 ` Johannes Schindelin
@ 2019-11-11 12:09                   ` Junio C Hamano
  2019-11-12 15:03                     ` Johannes Schindelin
  0 siblings, 1 reply; 124+ messages in thread
From: Junio C Hamano @ 2019-11-11 12:09 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

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

> And I agree that this sidetrack is totally irrelevant for the patch
> under discussion.
>
> I do think, however, that the discussion of "we wanted to do it the
> other way, but when we tried, it did not work" is relevant, even if I
> shortened it to "we use a different approach than previous conversions,
> because that previous approach would not work".

Regardless of the language the scripted version was written in, I
think the '--helper' approach is always the poorer choice between
the two [*1*].  It limits the modular decomposition to what suits the
original language, the impedance mismatch between the original and
target language forces us to unnatural style of inter module
communication, and the unnatural interface layer, which we know has
to be discarded at the end, must be written [*2*].

So, I'd prefer to see "because this is a better way in the longer
term" over "because the --helper approach would not work".

[Footnote]

*1* In only one case I would recommend using "--helper" approach,
    though.  When you are not expecting the developer to be able to
    come up with a better split of the program into modules than how
    the scripted version is, and you want to ensure that the
    developer have something to show when they faild to complete the
    project after N weeks.  You are a more experienced developer
    than an average GSoC student, and there is no pencils-down time,
    so the exception would not apply.

*2* In "git submodule" for example it was quite natural for the
    module that gives a list of submodules with its traits the
    program cares about to be written as a shell function that
    writes the data to its standard output.  And consuming modules
    sit at the downstream of a pipe, accepting its output.  When you
    are writing these modules both in C, you wouldn't connect them
    with pipe to carry the list of submodules, but a piecemeal
    conversion using the "--helper" approach meant that there always
    remained _some_ consumer that wants to read from the pipe, so
    long after the module lister was rewritten in C, it still needed
    to support a mode where it sends its output to the pipe, instead
    of just passing an array of structures.

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

* Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
  2019-11-11 12:09                   ` Junio C Hamano
@ 2019-11-12 15:03                     ` Johannes Schindelin
  2019-11-13  3:54                       ` Junio C Hamano
  0 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin @ 2019-11-12 15:03 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

Hi Junio,

On Mon, 11 Nov 2019, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
> > And I agree that this sidetrack is totally irrelevant for the patch
> > under discussion.
> >
> > I do think, however, that the discussion of "we wanted to do it the
> > other way, but when we tried, it did not work" is relevant, even if I
> > shortened it to "we use a different approach than previous conversions,
> > because that previous approach would not work".
>
> Regardless of the language the scripted version was written in, I
> think the '--helper' approach is always the poorer choice between
> the two [*1*].  It limits the modular decomposition to what suits the
> original language, the impedance mismatch between the original and
> target language forces us to unnatural style of inter module
> communication, and the unnatural interface layer, which we know has
> to be discarded at the end, must be written [*2*].
>
> So, I'd prefer to see "because this is a better way in the longer
> term" over "because the --helper approach would not work".

Hmm. I feel distinctly unheard.

It may appear compelling, conceptually, to shun the `--helper` approach,
but the more important reality is that it is the only one that makes an
incremental conversion possible at all.

It took an entire month of 60-hour weeks to complete the conversion of
`git add -i`/`git add -p` to C, and only at the very end was I able to
run the test suite with `GIT_TEST_ADD_I_USE_BUILTIN=true` and see it
pass.

That is an awfully long time, and you know fully well that this amount
of work equates to three to four Outreachy/GSoC seasons. That would be
an insane amount of time to go without the confidence of a passing test
suite.

In contrast, we were able to complete the conversions of the interactive
rebase as well as of `git stash` within _a single season_. I attribute a
large part of that success to the ability to keep the tests green during
the incremental conversion _because of_ the `--helper` approach.

So no, I do not think that your suggestion to reword the commit message
is something we want to do.  Instead, I think the commit message needs
to be rephrased until I get the point across clearly.

It is indeed _in spite of_ the success of the `--helper` approach that
we cannot use it here.

Ciao,
Dscho

>
> [Footnote]
>
> *1* In only one case I would recommend using "--helper" approach,
>     though.  When you are not expecting the developer to be able to
>     come up with a better split of the program into modules than how
>     the scripted version is, and you want to ensure that the
>     developer have something to show when they faild to complete the
>     project after N weeks.  You are a more experienced developer
>     than an average GSoC student, and there is no pencils-down time,
>     so the exception would not apply.
>
> *2* In "git submodule" for example it was quite natural for the
>     module that gives a list of submodules with its traits the
>     program cares about to be written as a shell function that
>     writes the data to its standard output.  And consuming modules
>     sit at the downstream of a pipe, accepting its output.  When you
>     are writing these modules both in C, you wouldn't connect them
>     with pipe to carry the list of submodules, but a piecemeal
>     conversion using the "--helper" approach meant that there always
>     remained _some_ consumer that wants to read from the pipe, so
>     long after the module lister was rewritten in C, it still needed
>     to support a mode where it sends its output to the pipe, instead
>     of just passing an array of structures.
>

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

* Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
  2019-11-12 15:03                     ` Johannes Schindelin
@ 2019-11-13  3:54                       ` Junio C Hamano
  2019-11-13 12:30                         ` Johannes Schindelin
  0 siblings, 1 reply; 124+ messages in thread
From: Junio C Hamano @ 2019-11-13  3:54 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

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

>> Regardless of the language the scripted version was written in, I
>> think the '--helper' approach is always the poorer choice between
>> the two [*1*].  It limits the modular decomposition to what suits the
>> original language, the impedance mismatch between the original and
>> target language forces us to unnatural style of inter module
>> communication, and the unnatural interface layer, which we know has
>> to be discarded at the end, must be written [*2*].
>>
>> So, I'd prefer to see "because this is a better way in the longer
>> term" over "because the --helper approach would not work".
>
> Hmm. I feel distinctly unheard.

The feeling is mutual ;-)

> It may appear compelling, conceptually, to shun the `--helper` approach,
> but the more important reality is that it is the only one that makes an
> incremental conversion possible at all.
>
> It took an entire month of 60-hour weeks to complete the conversion of
> `git add -i`/`git add -p` to C, and only at the very end was I able to
> run the test suite with `GIT_TEST_ADD_I_USE_BUILTIN=true` and see it
> pass.

Yeah, that is developer comfort, and of course it is nice to have
than not to have it.

But compared to the downside impact to the quality of end result
that is inherent to the '--helper' approach, I'd prioritize the
quality of the end result over developer comfort.

> It is indeed _in spite of_ the success of the `--helper` approach that
> we cannot use it here.

As I do not see those past '--helper' ones necessarily successes, we
must agree to disagree here.

In any case, the log message needs to express why _you_ ended up
taking the non-helper approach.  Even though it is far less
relevant, compared to that, what other approach you instead wanted
to take, I do not veto you from having your own opinion.

>> [Footnote]
>>
>> *1* In only one case I would recommend using "--helper" approach,
>>     though.  When you are not expecting the developer to be able to
>>     come up with a better split of the program into modules than how
>>     the scripted version is, and you want to ensure that the
>>     developer have something to show when they faild to complete the
>>     project after N weeks.  You are a more experienced developer
>>     than an average GSoC student, and there is no pencils-down time,
>>     so the exception would not apply.
>>
>> *2* In "git submodule" for example it was quite natural for the
>>     module that gives a list of submodules with its traits the
>>     program cares about to be written as a shell function that
>>     writes the data to its standard output.  And consuming modules
>>     sit at the downstream of a pipe, accepting its output.  When you
>>     are writing these modules both in C, you wouldn't connect them
>>     with pipe to carry the list of submodules, but a piecemeal
>>     conversion using the "--helper" approach meant that there always
>>     remained _some_ consumer that wants to read from the pipe, so
>>     long after the module lister was rewritten in C, it still needed
>>     to support a mode where it sends its output to the pipe, instead
>>     of just passing an array of structures.
>>

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

* Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
  2019-11-13  3:54                       ` Junio C Hamano
@ 2019-11-13 12:30                         ` Johannes Schindelin
  2019-11-13 14:01                           ` Junio C Hamano
  0 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin @ 2019-11-13 12:30 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

Hi Junio,

On Wed, 13 Nov 2019, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
> >> Regardless of the language the scripted version was written in, I
> >> think the '--helper' approach is always the poorer choice between
> >> the two [*1*].  It limits the modular decomposition to what suits the
> >> original language, the impedance mismatch between the original and
> >> target language forces us to unnatural style of inter module
> >> communication, and the unnatural interface layer, which we know has
> >> to be discarded at the end, must be written [*2*].
> >>
> >> So, I'd prefer to see "because this is a better way in the longer
> >> term" over "because the --helper approach would not work".
> >
> > Hmm. I feel distinctly unheard.
>
> The feeling is mutual ;-)

Maybe I could take your comments more seriously if you acknowledged the
fact that I am _very_ familiar with the vagaries of converting scripts
to C. Like, very, very, very familiar.

> > It may appear compelling, conceptually, to shun the `--helper` approach,
> > but the more important reality is that it is the only one that makes an
> > incremental conversion possible at all.
> >
> > It took an entire month of 60-hour weeks to complete the conversion of
> > `git add -i`/`git add -p` to C, and only at the very end was I able to
> > run the test suite with `GIT_TEST_ADD_I_USE_BUILTIN=true` and see it
> > pass.
>
> Yeah, that is developer comfort, and of course it is nice to have
> than not to have it.

Comfort has little to do with it. Driving out bugs has a lot more to do
with it. Which is the point I am trying to get across the entire time.

> But compared to the downside impact to the quality of end result
> that is inherent to the '--helper' approach, I'd prioritize the
> quality of the end result over developer comfort.
>
> > It is indeed _in spite of_ the success of the `--helper` approach that
> > we cannot use it here.
>
> As I do not see those past '--helper' ones necessarily successes, we
> must agree to disagree here.

Right. But if I recall, you never even saw the need for the conversions
in the first place. Maybe you still don't?

> In any case, the log message needs to express why _you_ ended up
> taking the non-helper approach.  Even though it is far less
> relevant, compared to that, what other approach you instead wanted
> to take, I do not veto you from having your own opinion.

Okay. I will take that as an indication that I can go forward with the
latest proposal. After all, I described pretty well, I think, why _I_
ended up taking the non-helper approach.

Thanks,
Dscho

> >> [Footnote]
> >>
> >> *1* In only one case I would recommend using "--helper" approach,
> >>     though.  When you are not expecting the developer to be able to
> >>     come up with a better split of the program into modules than how
> >>     the scripted version is, and you want to ensure that the
> >>     developer have something to show when they faild to complete the
> >>     project after N weeks.  You are a more experienced developer
> >>     than an average GSoC student, and there is no pencils-down time,
> >>     so the exception would not apply.
> >>
> >> *2* In "git submodule" for example it was quite natural for the
> >>     module that gives a list of submodules with its traits the
> >>     program cares about to be written as a shell function that
> >>     writes the data to its standard output.  And consuming modules
> >>     sit at the downstream of a pipe, accepting its output.  When you
> >>     are writing these modules both in C, you wouldn't connect them
> >>     with pipe to carry the list of submodules, but a piecemeal
> >>     conversion using the "--helper" approach meant that there always
> >>     remained _some_ consumer that wants to read from the pipe, so
> >>     long after the module lister was rewritten in C, it still needed
> >>     to support a mode where it sends its output to the pipe, instead
> >>     of just passing an array of structures.
> >>
>

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

* [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help  so far)
  2019-11-04 12:15       ` [PATCH v5 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                           ` (8 preceding siblings ...)
  2019-11-04 12:15         ` [PATCH v5 9/9] built-in add -i: implement the `help` command Slavica Đukić via GitGitGadget
@ 2019-11-13 12:40         ` Johannes Schindelin via GitGitGadget
  2019-11-13 12:40           ` [PATCH v6 1/9] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
                             ` (10 more replies)
  9 siblings, 11 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-11-13 12:40 UTC (permalink / raw)
  To: git; +Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano

This is the first leg on the long journey to a fully built-in git add -i 
(next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3
[https://github.com/gitgitgadget/git/pull/172], 4
[https://github.com/gitgitgadget/git/pull/173], 5
[https://github.com/gitgitgadget/git/pull/174], and 6
[https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are
not necessarily up to date, and will be re-targeted to the appropriate
branches in https://github.com/gitster/git as soon as Junio picks them up.

This here patch series reflects the part that was submitted a couple of
times (see https://github.com/gitgitgadget/git/pull/103) during the
Outreachy project by Slavica Ðukic that continued the journey based on an
initial patch series by Daniel Ferreira.

It only implements the status and the help part, in the interest of making
the review remotely more reviewable.

As I am a heavy user of git add -p myself and use a patched version for
several months already (it is so nice to not suffer over one second startup
until the MSYS2 Perl finally shows me anything, instead it feels
instantaneous), I integrated these patch series into Git for Windows
already, as an opt-in feature guarded by the config variable 
add.interactive.useBuiltin (and Git for Windows' installer knows to detect
this version and offer the option in the graphical user interface).

Changes since v4:

 * Rebased onto current master to make use of Thomas Gummerer's 
   repo_refresh_and_write_index() as well as to avoid merge conflicts with
   Eric Wong's work on struct hashmap.
 * Instead of rolling a dedicated data struct to simulate a Trie, we now use 
   string-list extensively (an unsorted copy and a sorted one, the latter to
   determine unique prefixes). This had massive ramifications on the rest of
   the patches... For example, the struct command_item structure no longer
   contains the name field, but is intended to be a util in a string_list.
 * Changed the commit messages and author lines to reflect Slavica's name
   correctly.
 * Touched up a couple commit messages.

Changes since v3:

 * Rebased to v2.23.0 to reduce friction.
 * free_diffstat_info() is now made public as well, and used, to avoid a
   memory leak.
 * Prepared the patches for ew/hashmap (which is strict about the hashmap
   entries' type in hashmap_entry_init() and friends).
 * The private data types have been moved from prefix-map.h to prefix-map.c.
 * A lot of int types were converted to more appropriate size_t in 
   prefix-map.c.
 * A misleading parameter name list was renamed to the correct array.
 * The code comment above find_unique_prefixes() was (hopefully) improved.
 * The run_help() function's signature now reflects that most of the
   parameters are actually unused.

Changes since v2:

 * Rebased to master to avoid merge conflicts.
 * Renumbered the prefix-map test to avoid conflicts with two patch series
   that are currently in-flight in pu.

Changes since v1:

 * The config machinery was reworked completely, to not use a callback to 
   git_config(), but instead to query the config via the repo_config_get_*() 
   functions. This also prevents a future "Huh???" moment: the internal add
   --interactive API accepts a parameter of type struct repository *r, but
   the previous configuration did not use that to query the config (and
   could in the future be a repository other than the_repository).
   
   
 * As a consequence, the color sequences are no longer stored in file-local
   variables, but passed around via a struct.
   
   
 * Instead of using the magical constant -2 to quit the main loop, it is now
   defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is
   defined as -1 and used where appropriate).
   
   
 * Improved the add_prefix_item() function by avoiding buffer overruns, not
   reusing the struct that is used for lookup also for adding the new item,
   and by strengthening the bug check.

Daniel Ferreira (2):
  diff: export diffstat interface
  built-in add -i: implement the `status` command

Johannes Schindelin (4):
  Start to implement a built-in version of `git add --interactive`
  built-in add -i: implement the main loop
  built-in add -i: show unique prefixes of the commands
  built-in add -i: support `?` (prompt help)

Slavica Đukić (3):
  built-in add -i: color the header in the `status` command
  built-in add -i: use color in the main loop
  built-in add -i: implement the `help` command

 Documentation/config/add.txt |   5 +
 Makefile                     |   1 +
 add-interactive.c            | 651 +++++++++++++++++++++++++++++++++++
 add-interactive.h            |   8 +
 builtin/add.c                |  12 +
 diff.c                       |  39 +--
 diff.h                       |  20 ++
 t/README                     |   4 +
 t/t3701-add-interactive.sh   |  25 ++
 9 files changed, 742 insertions(+), 23 deletions(-)
 create mode 100644 add-interactive.c
 create mode 100644 add-interactive.h


base-commit: 566a1439f6f56c2171b8853ddbca0ad3f5098770
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v6
Pull-Request: https://github.com/gitgitgadget/git/pull/170

Range-diff vs v5:

  1:  ff59d2d0b3 !  1:  5d9962d434 Start to implement a built-in version of `git add --interactive`
     @@ -2,31 +2,31 @@
      
          Start to implement a built-in version of `git add --interactive`
      
     -    This is hardly the first conversion of a Git command that is implemented
     -    as a script to a built-in. So far, the most successful strategy for such
     -    conversions has been to add a built-in helper and call that for more and
     -    more functionality from the script, as more and more parts are
     -    converted.
     +    Unlike previous conversions to C, where we started with a built-in
     +    helper, we start this conversion by adding an interception in the
     +    `run_add_interactive()` function when the new opt-in
     +    `add.interactive.useBuiltin` config knob is turned on (or the
     +    corresponding environment variable `GIT_TEST_ADD_I_USE_BUILTIN`), and
     +    calling the new internal API function `run_add_i()` that is implemented
     +    directly in libgit.a.
      
     -    With the interactive add, we choose a different strategy. The sole
     -    reason for this is that on Windows (where such a conversion has the most
     -    benefits in terms of speed and robustness) we face the very specific
     -    problem that a `system()` call in Perl seems to close `stdin` in the
     -    parent process when the spawned process consumes even one character from
     -    `stdin`. And that just does not work for us here, as it would stop the
     -    main loop as soon as any interactive command was performed by the
     -    helper. Which is almost all of the commands in `git add -i`.
     -
     -    It is almost as if Perl told us once again that it does not want us to
     -    use it on Windows.
     +    At this point, the built-in version of `git add -i` only states that it
     +    cannot do anything yet. In subsequent patches/patch series, the
     +    `run_add_i()` function will gain more and more functionality, until it
     +    is feature complete. The whole arc of the conversion can be found in the
     +    PRs #170-175 at https://github.com/gitgitgadget/git.
      
     -    Instead, we follow the opposite route where we start with a bare-bones
     -    version of the built-in interactive add, guarded by the new
     -    `add.interactive.useBuiltin` config variable, and then add more and more
     -    functionality to it, until it is feature complete.
     +    The "--helper approach" can unfortunately not be used here: on Windows
     +    we face the very specific problem that a `system()` call in
     +    Perl seems to close `stdin` in the parent process when the spawned
     +    process consumes even one character from `stdin`. Which prevents us from
     +    implementing the main loop in C and still trying to hand off to the Perl
     +    script.
      
     -    At this point, the built-in version of `git add -i` only states that it
     -    cannot do anything yet ;-)
     +    The very real downside of the approach we have to take here is that the
     +    test suite won't pass with `GIT_TEST_ADD_I_USE_BUILTIN=true` until the
     +    conversion is complete (the `--helper` approach would have let it pass,
     +    even at each of the incremental conversion steps).
      
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
     @@ -99,12 +99,14 @@
       	struct argv_array argv = ARGV_ARRAY_INIT;
      +	int use_builtin_add_i =
      +		git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
     -+	if (use_builtin_add_i < 0)
     -+		git_config_get_bool("add.interactive.usebuiltin",
     -+				    &use_builtin_add_i);
      +
     -+	if (use_builtin_add_i == 1 && !patch_mode)
     -+		return !!run_add_i(the_repository, pathspec);
     ++	if (!patch_mode) {
     ++		if (use_builtin_add_i < 0)
     ++			git_config_get_bool("add.interactive.usebuiltin",
     ++					    &use_builtin_add_i);
     ++		if (use_builtin_add_i == 1)
     ++			return !!run_add_i(the_repository, pathspec);
     ++	}
       
       	argv_array_push(&argv, "add--interactive");
       	if (patch_mode)
  2:  2fc8cc3546 =  2:  f42d7b1310 diff: export diffstat interface
  3:  6aaa0de4f4 =  3:  4836191271 built-in add -i: implement the `status` command
  4:  e405f07110 =  4:  d61cf9daeb built-in add -i: color the header in the `status` command
  5:  25590fbbbe !  5:  b0c04e6ec6 built-in add -i: implement the main loop
     @@ -7,33 +7,34 @@
          function that we conveniently introduced for use by the `status`
          command.
      
     -    Apart from the "and choose" part, there are more differences between the
     -    way the `status` command calls the `list_and_choose()` function in the
     -    Perl version of `git add -i` compared to the other callers of said
     -    function. The most important ones:
     +    In contrast to the Perl version, in the built-in interactive `add`, we
     +    will keep the `list()` function (which only displays items) and the
     +    `list_and_choose()` function (which uses `list()` to display the items,
     +    and only takes care of the "and choose" part) separate.
      
     -    - The list is not only shown, but the user is also asked to make a
     -      choice, possibly selecting multiple entries.
     +    The `list_and_choose()` function, as implemented in
     +    `git-add--interactive.perl` knows a few more tricks than the function we
     +    introduce in this patch:
      
     -    - The list of items is prefixed with a marker indicating what items have
     -      been selected, if multi-selection is allowed.
     +    - There is a flag to let the user select multiple items.
      
     -    - Initially, for each item a unique prefix (if there exists any within
     -      the given parameters) is determined, and shown in the list, and
     +    - In multi-select mode, the list of items is prefixed with a marker
     +      indicating what items have been selected.
     +
     +    - Initially, for each item a unique prefix is determined (if there
     +      exists any within the given parameters), and shown in the list, and
            accepted as a shortcut for the selection.
      
     -    These features will be implemented later, except the part where the user
     -    can choose a command. At this stage, though, the built-in `git add -i`
     -    still only supports the `status` command, with the remaining commands to
     -    follow over the course of the next commits.
     +    These features will be implemented in the C version later.
      
     -    In addition, we also modify `list()` to support displaying the commands
     -    in columns, even if there is currently only one.
     +    This patch does not add any new main loop command, of course, the
     +    built-in `git add -i` still only supports the `status` command. The
     +    remaining commands to follow over the course of the next commits.
      
     -    The Perl script `git-add--interactive.perl` mixed the purposes of the
     -    "list" and the "and choose" part into the same function. In the C
     -    version, we will keep them separate instead, calling the `list()`
     -    function from the `list_and_choose()` function.
     +    To accommodate for listing the commands in columns, preparing for the
     +    commands that will be implemented over the course of the next
     +    patches/patch series, we teach the `list()` function to do precisely
     +    that.
      
          Note that we only have a prompt ending in a single ">" at this stage;
          later commits will add commands that display a double ">>" to indicate
     @@ -101,7 +102,7 @@
      +	ssize_t res = LIST_AND_CHOOSE_ERROR;
      +
      +	for (;;) {
     -+		char *p, *endp;
     ++		char *p;
      +
      +		strbuf_reset(&input);
      +
     @@ -133,6 +134,7 @@
      +			}
      +
      +			if (isdigit(*p)) {
     ++				char *endp;
      +				index = strtoul(p, &endp, 10) - 1;
      +				if (endp != p + sep)
      +					index = -1;
  6:  57fdc01463 !  6:  b6459be5eb built-in add -i: show unique prefixes of the commands
     @@ -177,7 +177,7 @@
      +	find_unique_prefixes(items);
      +
       	for (;;) {
     - 		char *p, *endp;
     + 		char *p;
       
       		strbuf_reset(&input);
       
  7:  77ad5f333a =  7:  bdf9058d9e built-in add -i: support `?` (prompt help)
  8:  3d0b172a7f =  8:  eafeedc49b built-in add -i: use color in the main loop
  9:  85e508ef11 =  9:  7fda76255a built-in add -i: implement the `help` command

-- 
gitgitgadget

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

* [PATCH v6 1/9] Start to implement a built-in version of `git add --interactive`
  2019-11-13 12:40         ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
@ 2019-11-13 12:40           ` Johannes Schindelin via GitGitGadget
  2019-11-14  2:15             ` Junio C Hamano
  2019-11-13 12:40           ` [PATCH v6 2/9] diff: export diffstat interface Daniel Ferreira via GitGitGadget
                             ` (9 subsequent siblings)
  10 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-11-13 12:40 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Johannes Schindelin

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

Unlike previous conversions to C, where we started with a built-in
helper, we start this conversion by adding an interception in the
`run_add_interactive()` function when the new opt-in
`add.interactive.useBuiltin` config knob is turned on (or the
corresponding environment variable `GIT_TEST_ADD_I_USE_BUILTIN`), and
calling the new internal API function `run_add_i()` that is implemented
directly in libgit.a.

At this point, the built-in version of `git add -i` only states that it
cannot do anything yet. In subsequent patches/patch series, the
`run_add_i()` function will gain more and more functionality, until it
is feature complete. The whole arc of the conversion can be found in the
PRs #170-175 at https://github.com/gitgitgadget/git.

The "--helper approach" can unfortunately not be used here: on Windows
we face the very specific problem that a `system()` call in
Perl seems to close `stdin` in the parent process when the spawned
process consumes even one character from `stdin`. Which prevents us from
implementing the main loop in C and still trying to hand off to the Perl
script.

The very real downside of the approach we have to take here is that the
test suite won't pass with `GIT_TEST_ADD_I_USE_BUILTIN=true` until the
conversion is complete (the `--helper` approach would have let it pass,
even at each of the incremental conversion steps).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/config/add.txt |  5 +++++
 Makefile                     |  1 +
 add-interactive.c            |  7 +++++++
 add-interactive.h            |  8 ++++++++
 builtin/add.c                | 12 ++++++++++++
 t/README                     |  4 ++++
 6 files changed, 37 insertions(+)
 create mode 100644 add-interactive.c
 create mode 100644 add-interactive.h

diff --git a/Documentation/config/add.txt b/Documentation/config/add.txt
index 4d753f006e..c9f748f81c 100644
--- a/Documentation/config/add.txt
+++ b/Documentation/config/add.txt
@@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::
 	option of linkgit:git-add[1].  `add.ignore-errors` is deprecated,
 	as it does not follow the usual naming convention for configuration
 	variables.
+
+add.interactive.useBuiltin::
+	[EXPERIMENTAL] Set to `true` to use the experimental built-in
+	implementation of the interactive version of linkgit:git-add[1]
+	instead of the Perl script version. Is `false` by default.
diff --git a/Makefile b/Makefile
index 58b92af54b..6c4a1e0ee5 100644
--- a/Makefile
+++ b/Makefile
@@ -823,6 +823,7 @@ LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentat
 	-name '*.h' -print)))
 
 LIB_OBJS += abspath.o
+LIB_OBJS += add-interactive.o
 LIB_OBJS += advice.o
 LIB_OBJS += alias.o
 LIB_OBJS += alloc.o
diff --git a/add-interactive.c b/add-interactive.c
new file mode 100644
index 0000000000..482e458dc6
--- /dev/null
+++ b/add-interactive.c
@@ -0,0 +1,7 @@
+#include "cache.h"
+#include "add-interactive.h"
+
+int run_add_i(struct repository *r, const struct pathspec *ps)
+{
+	die(_("No commands are available in the built-in `git add -i` yet!"));
+}
diff --git a/add-interactive.h b/add-interactive.h
new file mode 100644
index 0000000000..7043b8741d
--- /dev/null
+++ b/add-interactive.h
@@ -0,0 +1,8 @@
+#ifndef ADD_INTERACTIVE_H
+#define ADD_INTERACTIVE_H
+
+struct repository;
+struct pathspec;
+int run_add_i(struct repository *r, const struct pathspec *ps);
+
+#endif
diff --git a/builtin/add.c b/builtin/add.c
index dd18e5c9b6..d4686d5218 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -20,6 +20,7 @@
 #include "bulk-checkin.h"
 #include "argv-array.h"
 #include "submodule.h"
+#include "add-interactive.h"
 
 static const char * const builtin_add_usage[] = {
 	N_("git add [<options>] [--] <pathspec>..."),
@@ -185,6 +186,16 @@ int run_add_interactive(const char *revision, const char *patch_mode,
 {
 	int status, i;
 	struct argv_array argv = ARGV_ARRAY_INIT;
+	int use_builtin_add_i =
+		git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
+
+	if (!patch_mode) {
+		if (use_builtin_add_i < 0)
+			git_config_get_bool("add.interactive.usebuiltin",
+					    &use_builtin_add_i);
+		if (use_builtin_add_i == 1)
+			return !!run_add_i(the_repository, pathspec);
+	}
 
 	argv_array_push(&argv, "add--interactive");
 	if (patch_mode)
@@ -319,6 +330,7 @@ static int add_config(const char *var, const char *value, void *cb)
 		ignore_add_errors = git_config_bool(var, value);
 		return 0;
 	}
+
 	return git_default_config(var, value, cb);
 }
 
diff --git a/t/README b/t/README
index 60d5b77bcc..5132ec83f8 100644
--- a/t/README
+++ b/t/README
@@ -397,6 +397,10 @@ 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_ADD_I_USE_BUILTIN=<boolean>, when true, enables the
+built-in version of git add -i. See 'add.interactive.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
-- 
gitgitgadget


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

* [PATCH v6 2/9] diff: export diffstat interface
  2019-11-13 12:40         ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  2019-11-13 12:40           ` [PATCH v6 1/9] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
@ 2019-11-13 12:40           ` Daniel Ferreira via GitGitGadget
  2019-11-13 12:40           ` [PATCH v6 3/9] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
                             ` (8 subsequent siblings)
  10 siblings, 0 replies; 124+ messages in thread
From: Daniel Ferreira via GitGitGadget @ 2019-11-13 12:40 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Daniel Ferreira

From: Daniel Ferreira <bnmvco@gmail.com>

Make the diffstat interface (namely, the diffstat_t struct and
compute_diffstat) no longer be internal to diff.c and allow it to be used
by other parts of git.

This is helpful for code that may want to easily extract information
from files using the diff machinery, while flushing it differently from
how the show_* functions used by diff_flush() do it. One example is the
builtin implementation of git-add--interactive's status.

Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 39 ++++++++++++++++-----------------------
 diff.h | 20 ++++++++++++++++++++
 2 files changed, 36 insertions(+), 23 deletions(-)

diff --git a/diff.c b/diff.c
index afe4400a60..5703a9b78f 100644
--- a/diff.c
+++ b/diff.c
@@ -2495,22 +2495,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)
 	}
 }
 
-struct diffstat_t {
-	int nr;
-	int alloc;
-	struct diffstat_file {
-		char *from_name;
-		char *name;
-		char *print_name;
-		const char *comments;
-		unsigned is_unmerged:1;
-		unsigned is_binary:1;
-		unsigned is_renamed:1;
-		unsigned is_interesting:1;
-		uintmax_t added, deleted;
-	} **files;
-};
-
 static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
 					  const char *name_a,
 					  const char *name_b)
@@ -3157,7 +3141,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o
 	gather_dirstat(options, &dir, changed, "", 0);
 }
 
-static void free_diffstat_info(struct diffstat_t *diffstat)
+void free_diffstat_info(struct diffstat_t *diffstat)
 {
 	int i;
 	for (i = 0; i < diffstat->nr; i++) {
@@ -6283,12 +6267,7 @@ void diff_flush(struct diff_options *options)
 	    dirstat_by_line) {
 		struct diffstat_t diffstat;
 
-		memset(&diffstat, 0, sizeof(struct diffstat_t));
-		for (i = 0; i < q->nr; i++) {
-			struct diff_filepair *p = q->queue[i];
-			if (check_pair_status(p))
-				diff_flush_stat(p, options, &diffstat);
-		}
+		compute_diffstat(options, &diffstat, q);
 		if (output_format & DIFF_FORMAT_NUMSTAT)
 			show_numstat(&diffstat, options);
 		if (output_format & DIFF_FORMAT_DIFFSTAT)
@@ -6621,6 +6600,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)
 	return ignored;
 }
 
+void compute_diffstat(struct diff_options *options,
+		      struct diffstat_t *diffstat,
+		      struct diff_queue_struct *q)
+{
+	int i;
+
+	memset(diffstat, 0, sizeof(struct diffstat_t));
+	for (i = 0; i < q->nr; i++) {
+		struct diff_filepair *p = q->queue[i];
+		if (check_pair_status(p))
+			diff_flush_stat(p, options, diffstat);
+	}
+}
+
 void diff_addremove(struct diff_options *options,
 		    int addremove, unsigned mode,
 		    const struct object_id *oid,
diff --git a/diff.h b/diff.h
index 7f8f024feb..d986ddc3b5 100644
--- a/diff.h
+++ b/diff.h
@@ -245,6 +245,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);
 void diff_emit_submodule_pipethrough(struct diff_options *o,
 				     const char *line, int len);
 
+struct diffstat_t {
+	int nr;
+	int alloc;
+	struct diffstat_file {
+		char *from_name;
+		char *name;
+		char *print_name;
+		const char *comments;
+		unsigned is_unmerged:1;
+		unsigned is_binary:1;
+		unsigned is_renamed:1;
+		unsigned is_interesting:1;
+		uintmax_t added, deleted;
+	} **files;
+};
+
 enum color_diff {
 	DIFF_RESET = 0,
 	DIFF_CONTEXT = 1,
@@ -334,6 +350,10 @@ void diff_change(struct diff_options *,
 
 struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);
 
+void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
+		      struct diff_queue_struct *q);
+void free_diffstat_info(struct diffstat_t *diffstat);
+
 #define DIFF_SETUP_REVERSE      	1
 #define DIFF_SETUP_USE_SIZE_CACHE	4
 
-- 
gitgitgadget


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

* [PATCH v6 3/9] built-in add -i: implement the `status` command
  2019-11-13 12:40         ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
  2019-11-13 12:40           ` [PATCH v6 1/9] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
  2019-11-13 12:40           ` [PATCH v6 2/9] diff: export diffstat interface Daniel Ferreira via GitGitGadget
@ 2019-11-13 12:40           ` Daniel Ferreira via GitGitGadget
  2019-11-13 12:41           ` [PATCH v6 4/9] built-in add -i: color the header in " Slavica Đukić via GitGitGadget
                             ` (7 subsequent siblings)
  10 siblings, 0 replies; 124+ messages in thread
From: Daniel Ferreira via GitGitGadget @ 2019-11-13 12:40 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Daniel Ferreira

From: Daniel Ferreira <bnmvco@gmail.com>

This implements the `status` command of `git add -i`. The data
structures introduced in this commit will be extended later, as needed.

At this point, we re-implement only part of the `list_and_choose()`
function of the Perl script `git-add--interactive.perl` and call it
`list()`. It does not yet color anything, or do columns, or allow user
input.

Over the course of the next commits, we will introduce a
`list_and_choose()` function that uses `list()` to display the list of
options and let the user choose one or more of the displayed items. This
will be used to implement the main loop of the built-in `git add -i`, at
which point the new `status` command can actually be used.

Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 251 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 250 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index 482e458dc6..aa35184d87 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -1,7 +1,256 @@
 #include "cache.h"
 #include "add-interactive.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "refs.h"
+#include "string-list.h"
+
+struct add_i_state {
+	struct repository *r;
+};
+
+static void init_add_i_state(struct add_i_state *s, struct repository *r)
+{
+       s->r = r;
+}
+
+struct list_options {
+	const char *header;
+	void (*print_item)(int i, struct string_list_item *item, void *print_item_data);
+	void *print_item_data;
+};
+
+static void list(struct string_list *list, struct list_options *opts)
+{
+	int i;
+
+	if (!list->nr)
+		return;
+
+	if (opts->header)
+		printf("%s\n", opts->header);
+
+	for (i = 0; i < list->nr; i++) {
+		opts->print_item(i, list->items + i, opts->print_item_data);
+		putchar('\n');
+	}
+}
+
+struct adddel {
+	uintmax_t add, del;
+	unsigned seen:1, binary:1;
+};
+
+struct file_item {
+	struct adddel index, worktree;
+};
+
+static void add_file_item(struct string_list *files, const char *name)
+{
+	struct file_item *item = xcalloc(sizeof(*item), 1);
+
+	string_list_append(files, name)->util = item;
+}
+
+struct pathname_entry {
+	struct hashmap_entry ent;
+	const char *name;
+	struct file_item *item;
+};
+
+static int pathname_entry_cmp(const void *unused_cmp_data,
+			      const struct hashmap_entry *he1,
+			      const struct hashmap_entry *he2,
+			      const void *name)
+{
+	const struct pathname_entry *e1 =
+		container_of(he1, const struct pathname_entry, ent);
+	const struct pathname_entry *e2 =
+		container_of(he2, const struct pathname_entry, ent);
+
+	return strcmp(e1->name, name ? (const char *)name : e2->name);
+}
+
+struct collection_status {
+	enum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase;
+
+	const char *reference;
+
+	struct string_list *files;
+	struct hashmap file_map;
+};
+
+static void collect_changes_cb(struct diff_queue_struct *q,
+			       struct diff_options *options,
+			       void *data)
+{
+	struct collection_status *s = data;
+	struct diffstat_t stat = { 0 };
+	int i;
+
+	if (!q->nr)
+		return;
+
+	compute_diffstat(options, &stat, q);
+
+	for (i = 0; i < stat.nr; i++) {
+		const char *name = stat.files[i]->name;
+		int hash = strhash(name);
+		struct pathname_entry *entry;
+		struct file_item *file_item;
+		struct adddel *adddel;
+
+		entry = hashmap_get_entry_from_hash(&s->file_map, hash, name,
+						    struct pathname_entry, ent);
+		if (!entry) {
+			add_file_item(s->files, name);
+
+			entry = xcalloc(sizeof(*entry), 1);
+			hashmap_entry_init(&entry->ent, hash);
+			entry->name = s->files->items[s->files->nr - 1].string;
+			entry->item = s->files->items[s->files->nr - 1].util;
+			hashmap_add(&s->file_map, &entry->ent);
+		}
+
+		file_item = entry->item;
+		adddel = s->phase == FROM_INDEX ?
+			&file_item->index : &file_item->worktree;
+		adddel->seen = 1;
+		adddel->add = stat.files[i]->added;
+		adddel->del = stat.files[i]->deleted;
+		if (stat.files[i]->is_binary)
+			adddel->binary = 1;
+	}
+	free_diffstat_info(&stat);
+}
+
+static int get_modified_files(struct repository *r, struct string_list *files,
+			      const struct pathspec *ps)
+{
+	struct object_id head_oid;
+	int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+					     &head_oid, NULL);
+	struct collection_status s = { FROM_WORKTREE };
+
+	if (discard_index(r->index) < 0 ||
+	    repo_read_index_preload(r, ps, 0) < 0)
+		return error(_("could not read index"));
+
+	string_list_clear(files, 1);
+	s.files = files;
+	hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);
+
+	for (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {
+		struct rev_info rev;
+		struct setup_revision_opt opt = { 0 };
+
+		opt.def = is_initial ?
+			empty_tree_oid_hex() : oid_to_hex(&head_oid);
+
+		init_revisions(&rev, NULL);
+		setup_revisions(0, NULL, &rev, &opt);
+
+		rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+		rev.diffopt.format_callback = collect_changes_cb;
+		rev.diffopt.format_callback_data = &s;
+
+		if (ps)
+			copy_pathspec(&rev.prune_data, ps);
+
+		if (s.phase == FROM_INDEX)
+			run_diff_index(&rev, 1);
+		else {
+			rev.diffopt.flags.ignore_dirty_submodules = 1;
+			run_diff_files(&rev, 0);
+		}
+	}
+	hashmap_free_entries(&s.file_map, struct pathname_entry, ent);
+
+	/* While the diffs are ordered already, we ran *two* diffs... */
+	string_list_sort(files);
+
+	return 0;
+}
+
+static void render_adddel(struct strbuf *buf,
+				struct adddel *ad, const char *no_changes)
+{
+	if (ad->binary)
+		strbuf_addstr(buf, _("binary"));
+	else if (ad->seen)
+		strbuf_addf(buf, "+%"PRIuMAX"/-%"PRIuMAX,
+			    (uintmax_t)ad->add, (uintmax_t)ad->del);
+	else
+		strbuf_addstr(buf, no_changes);
+}
+
+struct print_file_item_data {
+	const char *modified_fmt;
+	struct strbuf buf, index, worktree;
+};
+
+static void print_file_item(int i, struct string_list_item *item,
+			    void *print_file_item_data)
+{
+	struct file_item *c = item->util;
+	struct print_file_item_data *d = print_file_item_data;
+
+	strbuf_reset(&d->index);
+	strbuf_reset(&d->worktree);
+	strbuf_reset(&d->buf);
+
+	render_adddel(&d->worktree, &c->worktree, _("nothing"));
+	render_adddel(&d->index, &c->index, _("unchanged"));
+	strbuf_addf(&d->buf, d->modified_fmt,
+		    d->index.buf, d->worktree.buf, item->string);
+
+	printf(" %2d: %s", i + 1, d->buf.buf);
+}
+
+static int run_status(struct add_i_state *s, const struct pathspec *ps,
+		      struct string_list *files, struct list_options *opts)
+{
+	if (get_modified_files(s->r, files, ps) < 0)
+		return -1;
+
+	list(files, opts);
+	putchar('\n');
+
+	return 0;
+}
 
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
-	die(_("No commands are available in the built-in `git add -i` yet!"));
+	struct add_i_state s = { NULL };
+	struct print_file_item_data print_file_item_data = {
+		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+	};
+	struct list_options opts = {
+		NULL, print_file_item, &print_file_item_data
+	};
+	struct strbuf header = STRBUF_INIT;
+	struct string_list files = STRING_LIST_INIT_DUP;
+	int res = 0;
+
+	init_add_i_state(&s, r);
+	strbuf_addstr(&header, "      ");
+	strbuf_addf(&header, print_file_item_data.modified_fmt,
+		    _("staged"), _("unstaged"), _("path"));
+	opts.header = header.buf;
+
+	if (discard_index(r->index) < 0 ||
+	    repo_read_index(r) < 0 ||
+	    repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
+					 NULL, NULL, NULL) < 0)
+		warning(_("could not refresh index"));
+
+	res = run_status(&s, ps, &files, &opts);
+
+	string_list_clear(&files, 1);
+	strbuf_release(&print_file_item_data.buf);
+	strbuf_release(&print_file_item_data.index);
+	strbuf_release(&print_file_item_data.worktree);
+	strbuf_release(&header);
+
+	return res;
 }
-- 
gitgitgadget


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

* [PATCH v6 4/9] built-in add -i: color the header in the `status` command
  2019-11-13 12:40         ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                             ` (2 preceding siblings ...)
  2019-11-13 12:40           ` [PATCH v6 3/9] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
@ 2019-11-13 12:41           ` Slavica Đukić via GitGitGadget
  2019-11-13 12:41           ` [PATCH v6 5/9] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
                             ` (6 subsequent siblings)
  10 siblings, 0 replies; 124+ messages in thread
From: Slavica Đukić via GitGitGadget @ 2019-11-13 12:41 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Slavica Đukić

From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>

For simplicity, we only implemented the `status` command without colors.
This patch starts adding color, matching what the Perl script
`git-add--interactive.perl` does.

Original-Patch-By: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 41 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 37 insertions(+), 4 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index aa35184d87..174e07ce83 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -1,5 +1,7 @@
 #include "cache.h"
 #include "add-interactive.h"
+#include "color.h"
+#include "config.h"
 #include "diffcore.h"
 #include "revision.h"
 #include "refs.h"
@@ -7,11 +9,40 @@
 
 struct add_i_state {
 	struct repository *r;
+	int use_color;
+	char header_color[COLOR_MAXLEN];
 };
 
+static void init_color(struct repository *r, struct add_i_state *s,
+		       const char *slot_name, char *dst,
+		       const char *default_color)
+{
+	char *key = xstrfmt("color.interactive.%s", slot_name);
+	const char *value;
+
+	if (!s->use_color)
+		dst[0] = '\0';
+	else if (repo_config_get_value(r, key, &value) ||
+		 color_parse(value, dst))
+		strlcpy(dst, default_color, COLOR_MAXLEN);
+
+	free(key);
+}
+
 static void init_add_i_state(struct add_i_state *s, struct repository *r)
 {
-       s->r = r;
+	const char *value;
+
+	s->r = r;
+
+	if (repo_config_get_value(r, "color.interactive", &value))
+		s->use_color = -1;
+	else
+		s->use_color =
+			git_config_colorbool("color.interactive", value);
+	s->use_color = want_color(s->use_color);
+
+	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
 }
 
 struct list_options {
@@ -20,7 +51,8 @@ struct list_options {
 	void *print_item_data;
 };
 
-static void list(struct string_list *list, struct list_options *opts)
+static void list(struct add_i_state *s, struct string_list *list,
+		 struct list_options *opts)
 {
 	int i;
 
@@ -28,7 +60,8 @@ static void list(struct string_list *list, struct list_options *opts)
 		return;
 
 	if (opts->header)
-		printf("%s\n", opts->header);
+		color_fprintf_ln(stdout, s->header_color,
+				 "%s", opts->header);
 
 	for (i = 0; i < list->nr; i++) {
 		opts->print_item(i, list->items + i, opts->print_item_data);
@@ -213,7 +246,7 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	if (get_modified_files(s->r, files, ps) < 0)
 		return -1;
 
-	list(files, opts);
+	list(s, files, opts);
 	putchar('\n');
 
 	return 0;
-- 
gitgitgadget


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

* [PATCH v6 5/9] built-in add -i: implement the main loop
  2019-11-13 12:40         ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                             ` (3 preceding siblings ...)
  2019-11-13 12:41           ` [PATCH v6 4/9] built-in add -i: color the header in " Slavica Đukić via GitGitGadget
@ 2019-11-13 12:41           ` Johannes Schindelin via GitGitGadget
  2019-11-13 12:41           ` [PATCH v6 6/9] built-in add -i: show unique prefixes of the commands Johannes Schindelin via GitGitGadget
                             ` (5 subsequent siblings)
  10 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-11-13 12:41 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Johannes Schindelin

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

The reason why we did not start with the main loop to begin with is that
it is the first user of `list_and_choose()`, which uses the `list()`
function that we conveniently introduced for use by the `status`
command.

In contrast to the Perl version, in the built-in interactive `add`, we
will keep the `list()` function (which only displays items) and the
`list_and_choose()` function (which uses `list()` to display the items,
and only takes care of the "and choose" part) separate.

The `list_and_choose()` function, as implemented in
`git-add--interactive.perl` knows a few more tricks than the function we
introduce in this patch:

- There is a flag to let the user select multiple items.

- In multi-select mode, the list of items is prefixed with a marker
  indicating what items have been selected.

- Initially, for each item a unique prefix is determined (if there
  exists any within the given parameters), and shown in the list, and
  accepted as a shortcut for the selection.

These features will be implemented in the C version later.

This patch does not add any new main loop command, of course, the
built-in `git add -i` still only supports the `status` command. The
remaining commands to follow over the course of the next commits.

To accommodate for listing the commands in columns, preparing for the
commands that will be implemented over the course of the next
patches/patch series, we teach the `list()` function to do precisely
that.

Note that we only have a prompt ending in a single ">" at this stage;
later commits will add commands that display a double ">>" to indicate
that the user is in a different loop than the main one.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 136 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 134 insertions(+), 2 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 174e07ce83..0f99a52a72 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -46,6 +46,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)
 }
 
 struct list_options {
+	int columns;
 	const char *header;
 	void (*print_item)(int i, struct string_list_item *item, void *print_item_data);
 	void *print_item_data;
@@ -54,7 +55,7 @@ struct list_options {
 static void list(struct add_i_state *s, struct string_list *list,
 		 struct list_options *opts)
 {
-	int i;
+	int i, last_lf = 0;
 
 	if (!list->nr)
 		return;
@@ -65,8 +66,97 @@ static void list(struct add_i_state *s, struct string_list *list,
 
 	for (i = 0; i < list->nr; i++) {
 		opts->print_item(i, list->items + i, opts->print_item_data);
+
+		if ((opts->columns) && ((i + 1) % (opts->columns))) {
+			putchar('\t');
+			last_lf = 0;
+		}
+		else {
+			putchar('\n');
+			last_lf = 1;
+		}
+	}
+
+	if (!last_lf)
 		putchar('\n');
+}
+struct list_and_choose_options {
+	struct list_options list_opts;
+
+	const char *prompt;
+};
+
+#define LIST_AND_CHOOSE_ERROR (-1)
+#define LIST_AND_CHOOSE_QUIT  (-2)
+
+/*
+ * Returns the selected index.
+ *
+ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
+ * `LIST_AND_CHOOSE_QUIT` is returned.
+ */
+static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
+			       struct list_and_choose_options *opts)
+{
+	struct strbuf input = STRBUF_INIT;
+	ssize_t res = LIST_AND_CHOOSE_ERROR;
+
+	for (;;) {
+		char *p;
+
+		strbuf_reset(&input);
+
+		list(s, items, &opts->list_opts);
+
+		printf("%s%s", opts->prompt, "> ");
+		fflush(stdout);
+
+		if (strbuf_getline(&input, stdin) == EOF) {
+			putchar('\n');
+			res = LIST_AND_CHOOSE_QUIT;
+			break;
+		}
+		strbuf_trim(&input);
+
+		if (!input.len)
+			break;
+
+		p = input.buf;
+		for (;;) {
+			size_t sep = strcspn(p, " \t\r\n,");
+			ssize_t index = -1;
+
+			if (!sep) {
+				if (!*p)
+					break;
+				p++;
+				continue;
+			}
+
+			if (isdigit(*p)) {
+				char *endp;
+				index = strtoul(p, &endp, 10) - 1;
+				if (endp != p + sep)
+					index = -1;
+			}
+
+			p[sep] = '\0';
+			if (index < 0 || index >= items->nr)
+				printf(_("Huh (%s)?\n"), p);
+			else {
+				res = index;
+				break;
+			}
+
+			p += sep + 1;
+		}
+
+		if (res != LIST_AND_CHOOSE_ERROR)
+			break;
 	}
+
+	strbuf_release(&input);
+	return res;
 }
 
 struct adddel {
@@ -252,20 +342,48 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	return 0;
 }
 
+typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
+			 struct string_list *files,
+			 struct list_options *opts);
+
+static void print_command_item(int i, struct string_list_item *item,
+			       void *print_command_item_data)
+{
+	printf(" %2d: %s", i + 1, item->string);
+}
+
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
+	struct list_and_choose_options main_loop_opts = {
+		{ 4, N_("*** Commands ***"), print_command_item, NULL },
+		N_("What now")
+	};
+	struct {
+		const char *string;
+		command_t command;
+	} command_list[] = {
+		{ "status", run_status },
+	};
+	struct string_list commands = STRING_LIST_INIT_NODUP;
+
 	struct print_file_item_data print_file_item_data = {
 		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 	};
 	struct list_options opts = {
-		NULL, print_file_item, &print_file_item_data
+		0, NULL, print_file_item, &print_file_item_data
 	};
 	struct strbuf header = STRBUF_INIT;
 	struct string_list files = STRING_LIST_INIT_DUP;
+	ssize_t i;
 	int res = 0;
 
+	for (i = 0; i < ARRAY_SIZE(command_list); i++)
+		string_list_append(&commands, command_list[i].string)
+			->util = command_list[i].command;
+
 	init_add_i_state(&s, r);
+
 	strbuf_addstr(&header, "      ");
 	strbuf_addf(&header, print_file_item_data.modified_fmt,
 		    _("staged"), _("unstaged"), _("path"));
@@ -279,11 +397,25 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 
 	res = run_status(&s, ps, &files, &opts);
 
+	for (;;) {
+		i = list_and_choose(&s, &commands, &main_loop_opts);
+		if (i == LIST_AND_CHOOSE_QUIT) {
+			printf(_("Bye.\n"));
+			res = 0;
+			break;
+		}
+		if (i != LIST_AND_CHOOSE_ERROR) {
+			command_t command = commands.items[i].util;
+			res = command(&s, ps, &files, &opts);
+		}
+	}
+
 	string_list_clear(&files, 1);
 	strbuf_release(&print_file_item_data.buf);
 	strbuf_release(&print_file_item_data.index);
 	strbuf_release(&print_file_item_data.worktree);
 	strbuf_release(&header);
+	string_list_clear(&commands, 0);
 
 	return res;
 }
-- 
gitgitgadget


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

* [PATCH v6 6/9] built-in add -i: show unique prefixes of the commands
  2019-11-13 12:40         ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                             ` (4 preceding siblings ...)
  2019-11-13 12:41           ` [PATCH v6 5/9] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
@ 2019-11-13 12:41           ` Johannes Schindelin via GitGitGadget
  2019-11-13 12:41           ` [PATCH v6 7/9] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
                             ` (4 subsequent siblings)
  10 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-11-13 12:41 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Johannes Schindelin

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

Just like in the Perl script `git-add--interactive.perl`, for each
command a unique prefix is determined (if there exists any within the
given parameters), and shown in the list, and accepted as a shortcut for
the command.

To determine the unique prefixes, as well as to look up the command in
question, we use a copy of the list and sort it.

While this might seem like overkill for a single command, it will make
much more sense when all the commands are implemented, and when we reuse
the same logic to present a list of files to edit, with convenient
unique prefixes.

At the start of the development of this patch series, a dedicated data
structure was introduced that imitated the Trie that the Perl version
implements. However, this was deemed overkill, and we now simply sort
the list before determining the length of the unique prefixes by looking
at each item's neighbor. As a bonus, we now use the same sorted list to
perform a binary search using the user-provided prefix as search key.

Original-patch-by: Slavica Đukić <slawica92@hotmail.com>
Helped-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
---
 add-interactive.c | 188 +++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 177 insertions(+), 11 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 0f99a52a72..7b6bcf6f8a 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -45,6 +45,132 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)
 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
 }
 
+/*
+ * A "prefix item list" is a list of items that are identified by a string, and
+ * a unique prefix (if any) is determined for each item.
+ *
+ * It is implemented in the form of a pair of `string_list`s, the first one
+ * duplicating the strings, with the `util` field pointing at a structure whose
+ * first field must be `size_t prefix_length`.
+ *
+ * That `prefix_length` field will be computed by `find_unique_prefixes()`; It
+ * will be set to zero if no valid, unique prefix could be found.
+ *
+ * The second `string_list` is called `sorted` and does _not_ duplicate the
+ * strings but simply reuses the first one's, with the `util` field pointing at
+ * the `string_item_list` of the first `string_list`. It  will be populated and
+ * sorted by `find_unique_prefixes()`.
+ */
+struct prefix_item_list {
+	struct string_list items;
+	struct string_list sorted;
+	size_t min_length, max_length;
+};
+#define PREFIX_ITEM_LIST_INIT \
+	{ STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, 1, 4 }
+
+static void prefix_item_list_clear(struct prefix_item_list *list)
+{
+	string_list_clear(&list->items, 1);
+	string_list_clear(&list->sorted, 0);
+}
+
+static void extend_prefix_length(struct string_list_item *p,
+				 const char *other_string, size_t max_length)
+{
+	size_t *len = p->util;
+
+	if (!*len || memcmp(p->string, other_string, *len))
+		return;
+
+	for (;;) {
+		char c = p->string[*len];
+
+		/*
+		 * Is `p` a strict prefix of `other`? Or have we exhausted the
+		 * maximal length of the prefix? Or is the current character a
+		 * multi-byte UTF-8 one? If so, there is no valid, unique
+		 * prefix.
+		 */
+		if (!c || ++*len > max_length || !isascii(c)) {
+			*len = 0;
+			break;
+		}
+
+		if (c != other_string[*len - 1])
+			break;
+	}
+}
+
+static void find_unique_prefixes(struct prefix_item_list *list)
+{
+	size_t i;
+
+	if (list->sorted.nr == list->items.nr)
+		return;
+
+	string_list_clear(&list->sorted, 0);
+	/* Avoid reallocating incrementally */
+	list->sorted.items = xmalloc(st_mult(sizeof(*list->sorted.items),
+					     list->items.nr));
+	list->sorted.nr = list->sorted.alloc = list->items.nr;
+
+	for (i = 0; i < list->items.nr; i++) {
+		list->sorted.items[i].string = list->items.items[i].string;
+		list->sorted.items[i].util = list->items.items + i;
+	}
+
+	string_list_sort(&list->sorted);
+
+	for (i = 0; i < list->sorted.nr; i++) {
+		struct string_list_item *sorted_item = list->sorted.items + i;
+		struct string_list_item *item = sorted_item->util;
+		size_t *len = item->util;
+
+		*len = 0;
+		while (*len < list->min_length) {
+			char c = item->string[(*len)++];
+
+			if (!c || !isascii(c)) {
+				*len = 0;
+				break;
+			}
+		}
+
+		if (i > 0)
+			extend_prefix_length(item, sorted_item[-1].string,
+					     list->max_length);
+		if (i + 1 < list->sorted.nr)
+			extend_prefix_length(item, sorted_item[1].string,
+					     list->max_length);
+	}
+}
+
+static ssize_t find_unique(const char *string, struct prefix_item_list *list)
+{
+	int index = string_list_find_insert_index(&list->sorted, string, 1);
+	struct string_list_item *item;
+
+	if (list->items.nr != list->sorted.nr)
+		BUG("prefix_item_list in inconsistent state (%"PRIuMAX
+		    " vs %"PRIuMAX")",
+		    (uintmax_t)list->items.nr, (uintmax_t)list->sorted.nr);
+
+	if (index < 0)
+		item = list->sorted.items[-1 - index].util;
+	else if (index > 0 &&
+		 starts_with(list->sorted.items[index - 1].string, string))
+		return -1;
+	else if (index + 1 < list->sorted.nr &&
+		 starts_with(list->sorted.items[index + 1].string, string))
+		return -1;
+	else if (index < list->sorted.nr)
+		item = list->sorted.items[index].util;
+	else
+		return -1;
+	return item - list->items.items;
+}
+
 struct list_options {
 	int columns;
 	const char *header;
@@ -95,18 +221,21 @@ struct list_and_choose_options {
  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
  * `LIST_AND_CHOOSE_QUIT` is returned.
  */
-static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
+static ssize_t list_and_choose(struct add_i_state *s,
+			       struct prefix_item_list *items,
 			       struct list_and_choose_options *opts)
 {
 	struct strbuf input = STRBUF_INIT;
 	ssize_t res = LIST_AND_CHOOSE_ERROR;
 
+	find_unique_prefixes(items);
+
 	for (;;) {
 		char *p;
 
 		strbuf_reset(&input);
 
-		list(s, items, &opts->list_opts);
+		list(s, &items->items, &opts->list_opts);
 
 		printf("%s%s", opts->prompt, "> ");
 		fflush(stdout);
@@ -141,7 +270,10 @@ static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
 			}
 
 			p[sep] = '\0';
-			if (index < 0 || index >= items->nr)
+			if (index < 0)
+				index = find_unique(p, items);
+
+			if (index < 0 || index >= items->items.nr)
 				printf(_("Huh (%s)?\n"), p);
 			else {
 				res = index;
@@ -307,6 +439,23 @@ static void render_adddel(struct strbuf *buf,
 		strbuf_addstr(buf, no_changes);
 }
 
+/* filters out prefixes which have special meaning to list_and_choose() */
+static int is_valid_prefix(const char *prefix, size_t prefix_len)
+{
+	return prefix_len && prefix &&
+		/*
+		 * We expect `prefix` to be NUL terminated, therefore this
+		 * `strcspn()` call is okay, even if it might do much more
+		 * work than strictly necessary.
+		 */
+		strcspn(prefix, " \t\r\n,") >= prefix_len &&	/* separators */
+		*prefix != '-' &&				/* deselection */
+		!isdigit(*prefix) &&				/* selection */
+		(prefix_len != 1 ||
+		 (*prefix != '*' &&				/* "all" wildcard */
+		  *prefix != '?'));				/* prompt help */
+}
+
 struct print_file_item_data {
 	const char *modified_fmt;
 	struct strbuf buf, index, worktree;
@@ -346,10 +495,23 @@ typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
 			 struct string_list *files,
 			 struct list_options *opts);
 
+struct command_item {
+	size_t prefix_length;
+	command_t command;
+};
+
 static void print_command_item(int i, struct string_list_item *item,
 			       void *print_command_item_data)
 {
-	printf(" %2d: %s", i + 1, item->string);
+	struct command_item *util = item->util;
+
+	if (!util->prefix_length ||
+	    !is_valid_prefix(item->string, util->prefix_length))
+		printf(" %2d: %s", i + 1, item->string);
+	else
+		printf(" %2d: [%.*s]%s", i + 1,
+		       (int)util->prefix_length, item->string,
+		       item->string + util->prefix_length);
 }
 
 int run_add_i(struct repository *r, const struct pathspec *ps)
@@ -365,7 +527,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	} command_list[] = {
 		{ "status", run_status },
 	};
-	struct string_list commands = STRING_LIST_INIT_NODUP;
+	struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
 
 	struct print_file_item_data print_file_item_data = {
 		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
@@ -378,9 +540,12 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	ssize_t i;
 	int res = 0;
 
-	for (i = 0; i < ARRAY_SIZE(command_list); i++)
-		string_list_append(&commands, command_list[i].string)
-			->util = command_list[i].command;
+	for (i = 0; i < ARRAY_SIZE(command_list); i++) {
+		struct command_item *util = xcalloc(sizeof(*util), 1);
+		util->command = command_list[i].command;
+		string_list_append(&commands.items, command_list[i].string)
+			->util = util;
+	}
 
 	init_add_i_state(&s, r);
 
@@ -405,8 +570,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 			break;
 		}
 		if (i != LIST_AND_CHOOSE_ERROR) {
-			command_t command = commands.items[i].util;
-			res = command(&s, ps, &files, &opts);
+			struct command_item *util =
+				commands.items.items[i].util;
+			res = util->command(&s, ps, &files, &opts);
 		}
 	}
 
@@ -415,7 +581,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	strbuf_release(&print_file_item_data.index);
 	strbuf_release(&print_file_item_data.worktree);
 	strbuf_release(&header);
-	string_list_clear(&commands, 0);
+	prefix_item_list_clear(&commands);
 
 	return res;
 }
-- 
gitgitgadget


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

* [PATCH v6 7/9] built-in add -i: support `?` (prompt help)
  2019-11-13 12:40         ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                             ` (5 preceding siblings ...)
  2019-11-13 12:41           ` [PATCH v6 6/9] built-in add -i: show unique prefixes of the commands Johannes Schindelin via GitGitGadget
@ 2019-11-13 12:41           ` Johannes Schindelin via GitGitGadget
  2019-11-13 12:41           ` [PATCH v6 8/9] built-in add -i: use color in the main loop Slavica Đukić via GitGitGadget
                             ` (3 subsequent siblings)
  10 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-11-13 12:41 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Johannes Schindelin

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

With this change, we print out the same colored help text that the
Perl-based `git add -i` prints in the main loop when question mark is
entered.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index 7b6bcf6f8a..8d3829fe68 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -11,6 +11,7 @@ struct add_i_state {
 	struct repository *r;
 	int use_color;
 	char header_color[COLOR_MAXLEN];
+	char help_color[COLOR_MAXLEN];
 };
 
 static void init_color(struct repository *r, struct add_i_state *s,
@@ -43,6 +44,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)
 	s->use_color = want_color(s->use_color);
 
 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
+	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
 }
 
 /*
@@ -210,6 +212,7 @@ struct list_and_choose_options {
 	struct list_options list_opts;
 
 	const char *prompt;
+	void (*print_help)(struct add_i_state *s);
 };
 
 #define LIST_AND_CHOOSE_ERROR (-1)
@@ -250,6 +253,11 @@ static ssize_t list_and_choose(struct add_i_state *s,
 		if (!input.len)
 			break;
 
+		if (!strcmp(input.buf, "?")) {
+			opts->print_help(s);
+			continue;
+		}
+
 		p = input.buf;
 		for (;;) {
 			size_t sep = strcspn(p, " \t\r\n,");
@@ -514,12 +522,24 @@ static void print_command_item(int i, struct string_list_item *item,
 		       item->string + util->prefix_length);
 }
 
+static void command_prompt_help(struct add_i_state *s)
+{
+	const char *help_color = s->help_color;
+	color_fprintf_ln(stdout, help_color, "%s", _("Prompt help:"));
+	color_fprintf_ln(stdout, help_color, "1          - %s",
+			 _("select a numbered item"));
+	color_fprintf_ln(stdout, help_color, "foo        - %s",
+			 _("select item based on unique prefix"));
+	color_fprintf_ln(stdout, help_color, "           - %s",
+			 _("(empty) select nothing"));
+}
+
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
 	struct list_and_choose_options main_loop_opts = {
 		{ 4, N_("*** Commands ***"), print_command_item, NULL },
-		N_("What now")
+		N_("What now"), command_prompt_help
 	};
 	struct {
 		const char *string;
-- 
gitgitgadget


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

* [PATCH v6 8/9] built-in add -i: use color in the main loop
  2019-11-13 12:40         ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                             ` (6 preceding siblings ...)
  2019-11-13 12:41           ` [PATCH v6 7/9] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
@ 2019-11-13 12:41           ` Slavica Đukić via GitGitGadget
  2019-11-13 12:41           ` [PATCH v6 9/9] built-in add -i: implement the `help` command Slavica Đukić via GitGitGadget
                             ` (2 subsequent siblings)
  10 siblings, 0 replies; 124+ messages in thread
From: Slavica Đukić via GitGitGadget @ 2019-11-13 12:41 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Slavica Đukić

From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>

The error messages as well as the unique prefixes are colored in `git
add -i` by default; We need to do the same in the built-in version.

Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 35 +++++++++++++++++++++++++++++------
 1 file changed, 29 insertions(+), 6 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 8d3829fe68..8f5531c86c 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -12,6 +12,9 @@ struct add_i_state {
 	int use_color;
 	char header_color[COLOR_MAXLEN];
 	char help_color[COLOR_MAXLEN];
+	char prompt_color[COLOR_MAXLEN];
+	char error_color[COLOR_MAXLEN];
+	char reset_color[COLOR_MAXLEN];
 };
 
 static void init_color(struct repository *r, struct add_i_state *s,
@@ -45,6 +48,9 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)
 
 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
 	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
+	init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE);
+	init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED);
+	init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET);
 }
 
 /*
@@ -240,7 +246,8 @@ static ssize_t list_and_choose(struct add_i_state *s,
 
 		list(s, &items->items, &opts->list_opts);
 
-		printf("%s%s", opts->prompt, "> ");
+		color_fprintf(stdout, s->prompt_color, "%s", opts->prompt);
+		fputs("> ", stdout);
 		fflush(stdout);
 
 		if (strbuf_getline(&input, stdin) == EOF) {
@@ -282,7 +289,8 @@ static ssize_t list_and_choose(struct add_i_state *s,
 				index = find_unique(p, items);
 
 			if (index < 0 || index >= items->items.nr)
-				printf(_("Huh (%s)?\n"), p);
+				color_fprintf_ln(stdout, s->error_color,
+						 _("Huh (%s)?"), p);
 			else {
 				res = index;
 				break;
@@ -508,18 +516,23 @@ struct command_item {
 	command_t command;
 };
 
+struct print_command_item_data {
+	const char *color, *reset;
+};
+
 static void print_command_item(int i, struct string_list_item *item,
 			       void *print_command_item_data)
 {
+	struct print_command_item_data *d = print_command_item_data;
 	struct command_item *util = item->util;
 
 	if (!util->prefix_length ||
 	    !is_valid_prefix(item->string, util->prefix_length))
 		printf(" %2d: %s", i + 1, item->string);
 	else
-		printf(" %2d: [%.*s]%s", i + 1,
-		       (int)util->prefix_length, item->string,
-		       item->string + util->prefix_length);
+		printf(" %2d: %s%.*s%s%s", i + 1,
+		       d->color, (int)util->prefix_length, item->string,
+		       d->reset, item->string + util->prefix_length);
 }
 
 static void command_prompt_help(struct add_i_state *s)
@@ -537,8 +550,9 @@ static void command_prompt_help(struct add_i_state *s)
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
+	struct print_command_item_data data = { "[", "]" };
 	struct list_and_choose_options main_loop_opts = {
-		{ 4, N_("*** Commands ***"), print_command_item, NULL },
+		{ 4, N_("*** Commands ***"), print_command_item, &data },
 		N_("What now"), command_prompt_help
 	};
 	struct {
@@ -569,6 +583,15 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 
 	init_add_i_state(&s, r);
 
+	/*
+	 * When color was asked for, use the prompt color for
+	 * highlighting, otherwise use square brackets.
+	 */
+	if (s.use_color) {
+		data.color = s.prompt_color;
+		data.reset = s.reset_color;
+	}
+
 	strbuf_addstr(&header, "      ");
 	strbuf_addf(&header, print_file_item_data.modified_fmt,
 		    _("staged"), _("unstaged"), _("path"));
-- 
gitgitgadget


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

* [PATCH v6 9/9] built-in add -i: implement the `help` command
  2019-11-13 12:40         ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                             ` (7 preceding siblings ...)
  2019-11-13 12:41           ` [PATCH v6 8/9] built-in add -i: use color in the main loop Slavica Đukić via GitGitGadget
@ 2019-11-13 12:41           ` Slavica Đukić via GitGitGadget
  2019-11-13 12:46           ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin
  2019-11-15 11:11           ` [PATCH v7 " Johannes Schindelin via GitGitGadget
  10 siblings, 0 replies; 124+ messages in thread
From: Slavica Đukić via GitGitGadget @ 2019-11-13 12:41 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler, Jeff King, Johannes Schindelin, Junio C Hamano,
	Slavica Đukić

From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>

This imitates the code to show the help text from the Perl script
`git-add--interactive.perl` in the built-in version.

To make sure that it renders exactly like the Perl version of `git add
-i`, we also add a test case for that to `t3701-add-interactive.sh`.

Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c          | 21 +++++++++++++++++++++
 t/t3701-add-interactive.sh | 25 +++++++++++++++++++++++++
 2 files changed, 46 insertions(+)

diff --git a/add-interactive.c b/add-interactive.c
index 8f5531c86c..5d89863bab 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -507,6 +507,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	return 0;
 }
 
+static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,
+		    struct string_list *unused_files,
+		    struct list_options *unused_opts)
+{
+	color_fprintf_ln(stdout, s->help_color, "status        - %s",
+			 _("show paths with changes"));
+	color_fprintf_ln(stdout, s->help_color, "update        - %s",
+			 _("add working tree state to the staged set of changes"));
+	color_fprintf_ln(stdout, s->help_color, "revert        - %s",
+			 _("revert staged set of changes back to the HEAD version"));
+	color_fprintf_ln(stdout, s->help_color, "patch         - %s",
+			 _("pick hunks and update selectively"));
+	color_fprintf_ln(stdout, s->help_color, "diff          - %s",
+			 _("view diff between HEAD and index"));
+	color_fprintf_ln(stdout, s->help_color, "add untracked - %s",
+			 _("add contents of untracked files to the staged set of changes"));
+
+	return 0;
+}
+
 typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
 			 struct string_list *files,
 			 struct list_options *opts);
@@ -560,6 +580,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 		command_t command;
 	} command_list[] = {
 		{ "status", run_status },
+		{ "help", run_help },
 	};
 	struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
 
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index d50e165ca8..d4f9386621 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' '
 	test_write_lines a b a b a a b a b a >expect &&
 	test_cmp expect a
 '
+
+test_expect_success 'show help from add--helper' '
+	git reset --hard &&
+	cat >expect <<-EOF &&
+
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>
+	<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>
+	<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>
+	<BOLD;RED>patch         - pick hunks and update selectively<RESET>
+	<BOLD;RED>diff          - view diff between HEAD and index<RESET>
+	<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>>$SP
+	Bye.
+	EOF
+	test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&
+	test_decode_color <actual.colored >actual &&
+	test_i18ncmp expect actual
+'
+
 test_done
-- 
gitgitgadget

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

* Re: [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help  so far)
  2019-11-13 12:40         ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                             ` (8 preceding siblings ...)
  2019-11-13 12:41           ` [PATCH v6 9/9] built-in add -i: implement the `help` command Slavica Đukić via GitGitGadget
@ 2019-11-13 12:46           ` Johannes Schindelin
  2019-11-15 11:11           ` [PATCH v7 " Johannes Schindelin via GitGitGadget
  10 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin @ 2019-11-13 12:46 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Jeff Hostetler, Jeff King, Junio C Hamano

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

Hi,

On Wed, 13 Nov 2019, Johannes Schindelin via GitGitGadget wrote:

> This is the first leg on the long journey to a fully built-in git add -i
> (next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3
> [https://github.com/gitgitgadget/git/pull/172], 4
> [https://github.com/gitgitgadget/git/pull/173], 5
> [https://github.com/gitgitgadget/git/pull/174], and 6
> [https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are
> not necessarily up to date, and will be re-targeted to the appropriate
> branches in https://github.com/gitster/git as soon as Junio picks them up.
>
> This here patch series reflects the part that was submitted a couple of
> times (see https://github.com/gitgitgadget/git/pull/103) during the
> Outreachy project by Slavica Ðukic that continued the journey based on an
> initial patch series by Daniel Ferreira.
>
> It only implements the status and the help part, in the interest of making
> the review remotely more reviewable.
>
> As I am a heavy user of git add -p myself and use a patched version for
> several months already (it is so nice to not suffer over one second startup
> until the MSYS2 Perl finally shows me anything, instead it feels
> instantaneous), I integrated these patch series into Git for Windows
> already, as an opt-in feature guarded by the config variable
> add.interactive.useBuiltin (and Git for Windows' installer knows to detect
> this version and offer the option in the graphical user interface).

And of course I forgot to summarize the changes since v5:

- Reworded two commit messages.
- Clarified code that does not affect `patch_mode`.
- Restricted scope of the very local variable `endp`.

Ciao,
Johannes

> Changes since v4:
>
>  * Rebased onto current master to make use of Thomas Gummerer's
>    repo_refresh_and_write_index() as well as to avoid merge conflicts with
>    Eric Wong's work on struct hashmap.
>  * Instead of rolling a dedicated data struct to simulate a Trie, we now use
>    string-list extensively (an unsorted copy and a sorted one, the latter to
>    determine unique prefixes). This had massive ramifications on the rest of
>    the patches... For example, the struct command_item structure no longer
>    contains the name field, but is intended to be a util in a string_list.
>  * Changed the commit messages and author lines to reflect Slavica's name
>    correctly.
>  * Touched up a couple commit messages.
>
> Changes since v3:
>
>  * Rebased to v2.23.0 to reduce friction.
>  * free_diffstat_info() is now made public as well, and used, to avoid a
>    memory leak.
>  * Prepared the patches for ew/hashmap (which is strict about the hashmap
>    entries' type in hashmap_entry_init() and friends).
>  * The private data types have been moved from prefix-map.h to prefix-map.c.
>  * A lot of int types were converted to more appropriate size_t in
>    prefix-map.c.
>  * A misleading parameter name list was renamed to the correct array.
>  * The code comment above find_unique_prefixes() was (hopefully) improved.
>  * The run_help() function's signature now reflects that most of the
>    parameters are actually unused.
>
> Changes since v2:
>
>  * Rebased to master to avoid merge conflicts.
>  * Renumbered the prefix-map test to avoid conflicts with two patch series
>    that are currently in-flight in pu.
>
> Changes since v1:
>
>  * The config machinery was reworked completely, to not use a callback to
>    git_config(), but instead to query the config via the repo_config_get_*()
>    functions. This also prevents a future "Huh???" moment: the internal add
>    --interactive API accepts a parameter of type struct repository *r, but
>    the previous configuration did not use that to query the config (and
>    could in the future be a repository other than the_repository).
>
>
>  * As a consequence, the color sequences are no longer stored in file-local
>    variables, but passed around via a struct.
>
>
>  * Instead of using the magical constant -2 to quit the main loop, it is now
>    defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is
>    defined as -1 and used where appropriate).
>
>
>  * Improved the add_prefix_item() function by avoiding buffer overruns, not
>    reusing the struct that is used for lookup also for adding the new item,
>    and by strengthening the bug check.
>
> Daniel Ferreira (2):
>   diff: export diffstat interface
>   built-in add -i: implement the `status` command
>
> Johannes Schindelin (4):
>   Start to implement a built-in version of `git add --interactive`
>   built-in add -i: implement the main loop
>   built-in add -i: show unique prefixes of the commands
>   built-in add -i: support `?` (prompt help)
>
> Slavica Đukić (3):
>   built-in add -i: color the header in the `status` command
>   built-in add -i: use color in the main loop
>   built-in add -i: implement the `help` command
>
>  Documentation/config/add.txt |   5 +
>  Makefile                     |   1 +
>  add-interactive.c            | 651 +++++++++++++++++++++++++++++++++++
>  add-interactive.h            |   8 +
>  builtin/add.c                |  12 +
>  diff.c                       |  39 +--
>  diff.h                       |  20 ++
>  t/README                     |   4 +
>  t/t3701-add-interactive.sh   |  25 ++
>  9 files changed, 742 insertions(+), 23 deletions(-)
>  create mode 100644 add-interactive.c
>  create mode 100644 add-interactive.h
>
>
> base-commit: 566a1439f6f56c2171b8853ddbca0ad3f5098770
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v6
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v6
> Pull-Request: https://github.com/gitgitgadget/git/pull/170
>
> Range-diff vs v5:
>
>   1:  ff59d2d0b3 !  1:  5d9962d434 Start to implement a built-in version of `git add --interactive`
>      @@ -2,31 +2,31 @@
>
>           Start to implement a built-in version of `git add --interactive`
>
>      -    This is hardly the first conversion of a Git command that is implemented
>      -    as a script to a built-in. So far, the most successful strategy for such
>      -    conversions has been to add a built-in helper and call that for more and
>      -    more functionality from the script, as more and more parts are
>      -    converted.
>      +    Unlike previous conversions to C, where we started with a built-in
>      +    helper, we start this conversion by adding an interception in the
>      +    `run_add_interactive()` function when the new opt-in
>      +    `add.interactive.useBuiltin` config knob is turned on (or the
>      +    corresponding environment variable `GIT_TEST_ADD_I_USE_BUILTIN`), and
>      +    calling the new internal API function `run_add_i()` that is implemented
>      +    directly in libgit.a.
>
>      -    With the interactive add, we choose a different strategy. The sole
>      -    reason for this is that on Windows (where such a conversion has the most
>      -    benefits in terms of speed and robustness) we face the very specific
>      -    problem that a `system()` call in Perl seems to close `stdin` in the
>      -    parent process when the spawned process consumes even one character from
>      -    `stdin`. And that just does not work for us here, as it would stop the
>      -    main loop as soon as any interactive command was performed by the
>      -    helper. Which is almost all of the commands in `git add -i`.
>      -
>      -    It is almost as if Perl told us once again that it does not want us to
>      -    use it on Windows.
>      +    At this point, the built-in version of `git add -i` only states that it
>      +    cannot do anything yet. In subsequent patches/patch series, the
>      +    `run_add_i()` function will gain more and more functionality, until it
>      +    is feature complete. The whole arc of the conversion can be found in the
>      +    PRs #170-175 at https://github.com/gitgitgadget/git.
>
>      -    Instead, we follow the opposite route where we start with a bare-bones
>      -    version of the built-in interactive add, guarded by the new
>      -    `add.interactive.useBuiltin` config variable, and then add more and more
>      -    functionality to it, until it is feature complete.
>      +    The "--helper approach" can unfortunately not be used here: on Windows
>      +    we face the very specific problem that a `system()` call in
>      +    Perl seems to close `stdin` in the parent process when the spawned
>      +    process consumes even one character from `stdin`. Which prevents us from
>      +    implementing the main loop in C and still trying to hand off to the Perl
>      +    script.
>
>      -    At this point, the built-in version of `git add -i` only states that it
>      -    cannot do anything yet ;-)
>      +    The very real downside of the approach we have to take here is that the
>      +    test suite won't pass with `GIT_TEST_ADD_I_USE_BUILTIN=true` until the
>      +    conversion is complete (the `--helper` approach would have let it pass,
>      +    even at each of the incremental conversion steps).
>
>           Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
>
>      @@ -99,12 +99,14 @@
>        	struct argv_array argv = ARGV_ARRAY_INIT;
>       +	int use_builtin_add_i =
>       +		git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
>      -+	if (use_builtin_add_i < 0)
>      -+		git_config_get_bool("add.interactive.usebuiltin",
>      -+				    &use_builtin_add_i);
>       +
>      -+	if (use_builtin_add_i == 1 && !patch_mode)
>      -+		return !!run_add_i(the_repository, pathspec);
>      ++	if (!patch_mode) {
>      ++		if (use_builtin_add_i < 0)
>      ++			git_config_get_bool("add.interactive.usebuiltin",
>      ++					    &use_builtin_add_i);
>      ++		if (use_builtin_add_i == 1)
>      ++			return !!run_add_i(the_repository, pathspec);
>      ++	}
>
>        	argv_array_push(&argv, "add--interactive");
>        	if (patch_mode)
>   2:  2fc8cc3546 =  2:  f42d7b1310 diff: export diffstat interface
>   3:  6aaa0de4f4 =  3:  4836191271 built-in add -i: implement the `status` command
>   4:  e405f07110 =  4:  d61cf9daeb built-in add -i: color the header in the `status` command
>   5:  25590fbbbe !  5:  b0c04e6ec6 built-in add -i: implement the main loop
>      @@ -7,33 +7,34 @@
>           function that we conveniently introduced for use by the `status`
>           command.
>
>      -    Apart from the "and choose" part, there are more differences between the
>      -    way the `status` command calls the `list_and_choose()` function in the
>      -    Perl version of `git add -i` compared to the other callers of said
>      -    function. The most important ones:
>      +    In contrast to the Perl version, in the built-in interactive `add`, we
>      +    will keep the `list()` function (which only displays items) and the
>      +    `list_and_choose()` function (which uses `list()` to display the items,
>      +    and only takes care of the "and choose" part) separate.
>
>      -    - The list is not only shown, but the user is also asked to make a
>      -      choice, possibly selecting multiple entries.
>      +    The `list_and_choose()` function, as implemented in
>      +    `git-add--interactive.perl` knows a few more tricks than the function we
>      +    introduce in this patch:
>
>      -    - The list of items is prefixed with a marker indicating what items have
>      -      been selected, if multi-selection is allowed.
>      +    - There is a flag to let the user select multiple items.
>
>      -    - Initially, for each item a unique prefix (if there exists any within
>      -      the given parameters) is determined, and shown in the list, and
>      +    - In multi-select mode, the list of items is prefixed with a marker
>      +      indicating what items have been selected.
>      +
>      +    - Initially, for each item a unique prefix is determined (if there
>      +      exists any within the given parameters), and shown in the list, and
>             accepted as a shortcut for the selection.
>
>      -    These features will be implemented later, except the part where the user
>      -    can choose a command. At this stage, though, the built-in `git add -i`
>      -    still only supports the `status` command, with the remaining commands to
>      -    follow over the course of the next commits.
>      +    These features will be implemented in the C version later.
>
>      -    In addition, we also modify `list()` to support displaying the commands
>      -    in columns, even if there is currently only one.
>      +    This patch does not add any new main loop command, of course, the
>      +    built-in `git add -i` still only supports the `status` command. The
>      +    remaining commands to follow over the course of the next commits.
>
>      -    The Perl script `git-add--interactive.perl` mixed the purposes of the
>      -    "list" and the "and choose" part into the same function. In the C
>      -    version, we will keep them separate instead, calling the `list()`
>      -    function from the `list_and_choose()` function.
>      +    To accommodate for listing the commands in columns, preparing for the
>      +    commands that will be implemented over the course of the next
>      +    patches/patch series, we teach the `list()` function to do precisely
>      +    that.
>
>           Note that we only have a prompt ending in a single ">" at this stage;
>           later commits will add commands that display a double ">>" to indicate
>      @@ -101,7 +102,7 @@
>       +	ssize_t res = LIST_AND_CHOOSE_ERROR;
>       +
>       +	for (;;) {
>      -+		char *p, *endp;
>      ++		char *p;
>       +
>       +		strbuf_reset(&input);
>       +
>      @@ -133,6 +134,7 @@
>       +			}
>       +
>       +			if (isdigit(*p)) {
>      ++				char *endp;
>       +				index = strtoul(p, &endp, 10) - 1;
>       +				if (endp != p + sep)
>       +					index = -1;
>   6:  57fdc01463 !  6:  b6459be5eb built-in add -i: show unique prefixes of the commands
>      @@ -177,7 +177,7 @@
>       +	find_unique_prefixes(items);
>       +
>        	for (;;) {
>      - 		char *p, *endp;
>      + 		char *p;
>
>        		strbuf_reset(&input);
>
>   7:  77ad5f333a =  7:  bdf9058d9e built-in add -i: support `?` (prompt help)
>   8:  3d0b172a7f =  8:  eafeedc49b built-in add -i: use color in the main loop
>   9:  85e508ef11 =  9:  7fda76255a built-in add -i: implement the `help` command
>
> --
> gitgitgadget
>

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

* Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
  2019-11-13 12:30                         ` Johannes Schindelin
@ 2019-11-13 14:01                           ` Junio C Hamano
  0 siblings, 0 replies; 124+ messages in thread
From: Junio C Hamano @ 2019-11-13 14:01 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

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

>> As I do not see those past '--helper' ones necessarily successes, we
>> must agree to disagree here.
>
> Right. But if I recall, you never even saw the need for the conversions
> in the first place. Maybe you still don't?

You probably are forgetting the fact that I was very supportive for
the rewrite of checkout, commit and format-patch (the last one being
my favorite) in C from scripted Porcelain.  

None of these were '--helper' style conversion and I would consider
them much more successful than the recent ones, some of which still
suffer from impedance mismatch bugs (e.g. some parts of the C
implementation work on the in-core index, while other parts working
on the on the on-disk index, letting them become out of sync and
introducing bugs).

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

* Re: [PATCH v6 1/9] Start to implement a built-in version of `git add --interactive`
  2019-11-13 12:40           ` [PATCH v6 1/9] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
@ 2019-11-14  2:15             ` Junio C Hamano
  2019-11-14 15:07               ` Johannes Schindelin
  0 siblings, 1 reply; 124+ messages in thread
From: Junio C Hamano @ 2019-11-14  2:15 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Jeff Hostetler, Jeff King, Johannes Schindelin

"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> The very real downside of the approach we have to take here is that the
> test suite won't pass with `GIT_TEST_ADD_I_USE_BUILTIN=true` until the
> conversion is complete (the `--helper` approach would have let it pass,
> even at each of the incremental conversion steps).

That actually is an issue of the quality of the tests you add for
this series.  If they are organized along the arc of run_add_i()
gaining features, the invocation of "git add -i" in the tests can
selectively use USE_BUILTIN=true to verify what has been rewritten
so far.

In any case, I think I've seen the patches in this part of the
metaseries enough and I think they are quickly stabilizing.  Let's
see if others can find and raise issues and otherwise start cooking
in 'next' sometime next week.

Thanks.

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

* Re: [PATCH v6 1/9] Start to implement a built-in version of `git add --interactive`
  2019-11-14  2:15             ` Junio C Hamano
@ 2019-11-14 15:07               ` Johannes Schindelin
  2019-11-15  4:35                 ` Junio C Hamano
  0 siblings, 1 reply; 124+ messages in thread
From: Johannes Schindelin @ 2019-11-14 15:07 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

Hi Junio,

On Thu, 14 Nov 2019, Junio C Hamano wrote:

> any case, I think I've seen the patches in this part of the metaseries
> enough and I think they are quickly stabilizing.  Let's see if others
> can find and raise issues and otherwise start cooking in 'next'
> sometime next week.

While reviewing the next patch series in this arc, I *just* noticed a
buffer overrun: in the main loop, `path + sep` might point to the
trailing `NUL`, but we assign `p += sep + 1;` at the end (which is only
correct when `path + sep` points at whitespace).

The fix is already pushed up into gitgitgadget/git#170, and the relevant
part of the range-diff reads like this:

    @@ add-interactive.c: static void list(struct add_i_state *s, struct string_list *l
     +					index = -1;
     +			}
     +
    -+			p[sep] = '\0';
    ++			if (p[sep])
    ++				p[sep++] = '\0';
     +			if (index < 0 || index >= items->nr)
     +				printf(_("Huh (%s)?\n"), p);
     +			else {
    @@ add-interactive.c: static void list(struct add_i_state *s, struct string_list *l
     +				break;
     +			}
     +
    -+			p += sep + 1;
    ++			p += sep;
     +		}
     +
     +		if (res != LIST_AND_CHOOSE_ERROR)

I plan on waiting for the PR build to finish, and maybe wait until
tomorrow just in case any further suggestion rolls in, then submit the
hopefully final iteration.

And yes, I think it is a good time to start cooking this in `next`, I,
for one, am prone to overlook anything crucial in those patches because
I have stared at them so often.

Ciao,
Dscho

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

* Re: [PATCH v6 1/9] Start to implement a built-in version of `git add --interactive`
  2019-11-14 15:07               ` Johannes Schindelin
@ 2019-11-15  4:35                 ` Junio C Hamano
  0 siblings, 0 replies; 124+ messages in thread
From: Junio C Hamano @ 2019-11-15  4:35 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Johannes Schindelin via GitGitGadget, git, Jeff Hostetler,
	Jeff King

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

> I plan on waiting for the PR build to finish, and maybe wait until
> tomorrow just in case any further suggestion rolls in, then submit the
> hopefully final iteration.

Thanks.

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

* [PATCH v7 0/9] git add -i: add a rudimentary version in C (supporting only status and help  so far)
  2019-11-13 12:40         ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
                             ` (9 preceding siblings ...)
  2019-11-13 12:46           ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin
@ 2019-11-15 11:11           ` Johannes Schindelin via GitGitGadget
  2019-11-15 11:11             ` [PATCH v7 1/9] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
                               ` (8 more replies)
  10 siblings, 9 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-11-15 11:11 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler <git@jeffhostetler.com>, Jeff King,
	Johannes Schindelin, Junio C Hamano

This is the first leg on the long journey to a fully built-in git add -i 
(next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3
[https://github.com/gitgitgadget/git/pull/172], 4
[https://github.com/gitgitgadget/git/pull/173], 5
[https://github.com/gitgitgadget/git/pull/174], and 6
[https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are
not necessarily up to date, and will be re-targeted to the appropriate
branches in https://github.com/gitster/git as soon as Junio picks them up.

This here patch series reflects the part that was submitted a couple of
times (see https://github.com/gitgitgadget/git/pull/103) during the
Outreachy project by Slavica Ðukic that continued the journey based on an
initial patch series by Daniel Ferreira.

It only implements the status and the help part, in the interest of making
the review remotely more reviewable.

As I am a heavy user of git add -p myself and use a patched version for
several months already (it is so nice to not suffer over one second startup
until the MSYS2 Perl finally shows me anything, instead it feels
instantaneous), I integrated these patch series into Git for Windows
already, as an opt-in feature guarded by the config variable 
add.interactive.useBuiltin (and Git for Windows' installer knows to detect
this version and offer the option in the graphical user interface).

Changes since v6:

 * Fixed a potential buffer overrun when parsing numbers/number ranges.

Changes since v5:

 * Reworded two commit messages.
 * Clarified code that does not affect patch_mode.
 * Restricted scope of the very local variable endp.

Changes since v4:

 * Rebased onto current master to make use of Thomas Gummerer's 
   repo_refresh_and_write_index() as well as to avoid merge conflicts with
   Eric Wong's work on struct hashmap.
 * Instead of rolling a dedicated data struct to simulate a Trie, we now use 
   string-list extensively (an unsorted copy and a sorted one, the latter to
   determine unique prefixes). This had massive ramifications on the rest of
   the patches... For example, the struct command_item structure no longer
   contains the name field, but is intended to be a util in a string_list.
 * Changed the commit messages and author lines to reflect Slavica's name
   correctly.
 * Touched up a couple commit messages.

Changes since v3:

 * Rebased to v2.23.0 to reduce friction.
 * free_diffstat_info() is now made public as well, and used, to avoid a
   memory leak.
 * Prepared the patches for ew/hashmap (which is strict about the hashmap
   entries' type in hashmap_entry_init() and friends).
 * The private data types have been moved from prefix-map.h to prefix-map.c.
 * A lot of int types were converted to more appropriate size_t in 
   prefix-map.c.
 * A misleading parameter name list was renamed to the correct array.
 * The code comment above find_unique_prefixes() was (hopefully) improved.
 * The run_help() function's signature now reflects that most of the
   parameters are actually unused.

Changes since v2:

 * Rebased to master to avoid merge conflicts.
 * Renumbered the prefix-map test to avoid conflicts with two patch series
   that are currently in-flight in pu.

Changes since v1:

 * The config machinery was reworked completely, to not use a callback to 
   git_config(), but instead to query the config via the repo_config_get_*() 
   functions. This also prevents a future "Huh???" moment: the internal add
   --interactive API accepts a parameter of type struct repository *r, but
   the previous configuration did not use that to query the config (and
   could in the future be a repository other than the_repository).
   
   
 * As a consequence, the color sequences are no longer stored in file-local
   variables, but passed around via a struct.
   
   
 * Instead of using the magical constant -2 to quit the main loop, it is now
   defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is
   defined as -1 and used where appropriate).
   
   
 * Improved the add_prefix_item() function by avoiding buffer overruns, not
   reusing the struct that is used for lookup also for adding the new item,
   and by strengthening the bug check.

Daniel Ferreira (2):
  diff: export diffstat interface
  built-in add -i: implement the `status` command

Johannes Schindelin (4):
  Start to implement a built-in version of `git add --interactive`
  built-in add -i: implement the main loop
  built-in add -i: show unique prefixes of the commands
  built-in add -i: support `?` (prompt help)

Slavica Đukić (3):
  built-in add -i: color the header in the `status` command
  built-in add -i: use color in the main loop
  built-in add -i: implement the `help` command

 Documentation/config/add.txt |   5 +
 Makefile                     |   1 +
 add-interactive.c            | 652 +++++++++++++++++++++++++++++++++++
 add-interactive.h            |   8 +
 builtin/add.c                |  12 +
 diff.c                       |  39 +--
 diff.h                       |  20 ++
 t/README                     |   4 +
 t/t3701-add-interactive.sh   |  25 ++
 9 files changed, 743 insertions(+), 23 deletions(-)
 create mode 100644 add-interactive.c
 create mode 100644 add-interactive.h


base-commit: 566a1439f6f56c2171b8853ddbca0ad3f5098770
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v7
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v7
Pull-Request: https://github.com/gitgitgadget/git/pull/170

Range-diff vs v6:

  1:  5d9962d434 =  1:  5d9962d434 Start to implement a built-in version of `git add --interactive`
  2:  f42d7b1310 =  2:  f42d7b1310 diff: export diffstat interface
  3:  4836191271 =  3:  4836191271 built-in add -i: implement the `status` command
  4:  d61cf9daeb =  4:  d61cf9daeb built-in add -i: color the header in the `status` command
  5:  b0c04e6ec6 !  5:  601c371089 built-in add -i: implement the main loop
     @@ -140,7 +140,8 @@
      +					index = -1;
      +			}
      +
     -+			p[sep] = '\0';
     ++			if (p[sep])
     ++				p[sep++] = '\0';
      +			if (index < 0 || index >= items->nr)
      +				printf(_("Huh (%s)?\n"), p);
      +			else {
     @@ -148,7 +149,7 @@
      +				break;
      +			}
      +
     -+			p += sep + 1;
     ++			p += sep;
      +		}
      +
      +		if (res != LIST_AND_CHOOSE_ERROR)
  6:  b6459be5eb !  6:  978bb25b81 built-in add -i: show unique prefixes of the commands
     @@ -187,9 +187,9 @@
       		printf("%s%s", opts->prompt, "> ");
       		fflush(stdout);
      @@
     - 			}
       
     - 			p[sep] = '\0';
     + 			if (p[sep])
     + 				p[sep++] = '\0';
      -			if (index < 0 || index >= items->nr)
      +			if (index < 0)
      +				index = find_unique(p, items);
  7:  bdf9058d9e =  7:  d7d447026c built-in add -i: support `?` (prompt help)
  8:  eafeedc49b =  8:  bcf4aa7c5e built-in add -i: use color in the main loop
  9:  7fda76255a =  9:  ccc6fb4558 built-in add -i: implement the `help` command

-- 
gitgitgadget

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

* [PATCH v7 1/9] Start to implement a built-in version of `git add --interactive`
  2019-11-15 11:11           ` [PATCH v7 " Johannes Schindelin via GitGitGadget
@ 2019-11-15 11:11             ` Johannes Schindelin via GitGitGadget
  2019-11-15 11:11             ` [PATCH v7 2/9] diff: export diffstat interface Daniel Ferreira via GitGitGadget
                               ` (7 subsequent siblings)
  8 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-11-15 11:11 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler <git@jeffhostetler.com>, Jeff King,
	Johannes Schindelin, Junio C Hamano, Johannes Schindelin

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

Unlike previous conversions to C, where we started with a built-in
helper, we start this conversion by adding an interception in the
`run_add_interactive()` function when the new opt-in
`add.interactive.useBuiltin` config knob is turned on (or the
corresponding environment variable `GIT_TEST_ADD_I_USE_BUILTIN`), and
calling the new internal API function `run_add_i()` that is implemented
directly in libgit.a.

At this point, the built-in version of `git add -i` only states that it
cannot do anything yet. In subsequent patches/patch series, the
`run_add_i()` function will gain more and more functionality, until it
is feature complete. The whole arc of the conversion can be found in the
PRs #170-175 at https://github.com/gitgitgadget/git.

The "--helper approach" can unfortunately not be used here: on Windows
we face the very specific problem that a `system()` call in
Perl seems to close `stdin` in the parent process when the spawned
process consumes even one character from `stdin`. Which prevents us from
implementing the main loop in C and still trying to hand off to the Perl
script.

The very real downside of the approach we have to take here is that the
test suite won't pass with `GIT_TEST_ADD_I_USE_BUILTIN=true` until the
conversion is complete (the `--helper` approach would have let it pass,
even at each of the incremental conversion steps).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/config/add.txt |  5 +++++
 Makefile                     |  1 +
 add-interactive.c            |  7 +++++++
 add-interactive.h            |  8 ++++++++
 builtin/add.c                | 12 ++++++++++++
 t/README                     |  4 ++++
 6 files changed, 37 insertions(+)
 create mode 100644 add-interactive.c
 create mode 100644 add-interactive.h

diff --git a/Documentation/config/add.txt b/Documentation/config/add.txt
index 4d753f006e..c9f748f81c 100644
--- a/Documentation/config/add.txt
+++ b/Documentation/config/add.txt
@@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::
 	option of linkgit:git-add[1].  `add.ignore-errors` is deprecated,
 	as it does not follow the usual naming convention for configuration
 	variables.
+
+add.interactive.useBuiltin::
+	[EXPERIMENTAL] Set to `true` to use the experimental built-in
+	implementation of the interactive version of linkgit:git-add[1]
+	instead of the Perl script version. Is `false` by default.
diff --git a/Makefile b/Makefile
index 58b92af54b..6c4a1e0ee5 100644
--- a/Makefile
+++ b/Makefile
@@ -823,6 +823,7 @@ LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentat
 	-name '*.h' -print)))
 
 LIB_OBJS += abspath.o
+LIB_OBJS += add-interactive.o
 LIB_OBJS += advice.o
 LIB_OBJS += alias.o
 LIB_OBJS += alloc.o
diff --git a/add-interactive.c b/add-interactive.c
new file mode 100644
index 0000000000..482e458dc6
--- /dev/null
+++ b/add-interactive.c
@@ -0,0 +1,7 @@
+#include "cache.h"
+#include "add-interactive.h"
+
+int run_add_i(struct repository *r, const struct pathspec *ps)
+{
+	die(_("No commands are available in the built-in `git add -i` yet!"));
+}
diff --git a/add-interactive.h b/add-interactive.h
new file mode 100644
index 0000000000..7043b8741d
--- /dev/null
+++ b/add-interactive.h
@@ -0,0 +1,8 @@
+#ifndef ADD_INTERACTIVE_H
+#define ADD_INTERACTIVE_H
+
+struct repository;
+struct pathspec;
+int run_add_i(struct repository *r, const struct pathspec *ps);
+
+#endif
diff --git a/builtin/add.c b/builtin/add.c
index dd18e5c9b6..d4686d5218 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -20,6 +20,7 @@
 #include "bulk-checkin.h"
 #include "argv-array.h"
 #include "submodule.h"
+#include "add-interactive.h"
 
 static const char * const builtin_add_usage[] = {
 	N_("git add [<options>] [--] <pathspec>..."),
@@ -185,6 +186,16 @@ int run_add_interactive(const char *revision, const char *patch_mode,
 {
 	int status, i;
 	struct argv_array argv = ARGV_ARRAY_INIT;
+	int use_builtin_add_i =
+		git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
+
+	if (!patch_mode) {
+		if (use_builtin_add_i < 0)
+			git_config_get_bool("add.interactive.usebuiltin",
+					    &use_builtin_add_i);
+		if (use_builtin_add_i == 1)
+			return !!run_add_i(the_repository, pathspec);
+	}
 
 	argv_array_push(&argv, "add--interactive");
 	if (patch_mode)
@@ -319,6 +330,7 @@ static int add_config(const char *var, const char *value, void *cb)
 		ignore_add_errors = git_config_bool(var, value);
 		return 0;
 	}
+
 	return git_default_config(var, value, cb);
 }
 
diff --git a/t/README b/t/README
index 60d5b77bcc..5132ec83f8 100644
--- a/t/README
+++ b/t/README
@@ -397,6 +397,10 @@ 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_ADD_I_USE_BUILTIN=<boolean>, when true, enables the
+built-in version of git add -i. See 'add.interactive.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
-- 
gitgitgadget


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

* [PATCH v7 2/9] diff: export diffstat interface
  2019-11-15 11:11           ` [PATCH v7 " Johannes Schindelin via GitGitGadget
  2019-11-15 11:11             ` [PATCH v7 1/9] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
@ 2019-11-15 11:11             ` Daniel Ferreira via GitGitGadget
  2019-11-15 11:11             ` [PATCH v7 3/9] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
                               ` (6 subsequent siblings)
  8 siblings, 0 replies; 124+ messages in thread
From: Daniel Ferreira via GitGitGadget @ 2019-11-15 11:11 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler <git@jeffhostetler.com>, Jeff King,
	Johannes Schindelin, Junio C Hamano, Daniel Ferreira

From: Daniel Ferreira <bnmvco@gmail.com>

Make the diffstat interface (namely, the diffstat_t struct and
compute_diffstat) no longer be internal to diff.c and allow it to be used
by other parts of git.

This is helpful for code that may want to easily extract information
from files using the diff machinery, while flushing it differently from
how the show_* functions used by diff_flush() do it. One example is the
builtin implementation of git-add--interactive's status.

Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 diff.c | 39 ++++++++++++++++-----------------------
 diff.h | 20 ++++++++++++++++++++
 2 files changed, 36 insertions(+), 23 deletions(-)

diff --git a/diff.c b/diff.c
index afe4400a60..5703a9b78f 100644
--- a/diff.c
+++ b/diff.c
@@ -2495,22 +2495,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)
 	}
 }
 
-struct diffstat_t {
-	int nr;
-	int alloc;
-	struct diffstat_file {
-		char *from_name;
-		char *name;
-		char *print_name;
-		const char *comments;
-		unsigned is_unmerged:1;
-		unsigned is_binary:1;
-		unsigned is_renamed:1;
-		unsigned is_interesting:1;
-		uintmax_t added, deleted;
-	} **files;
-};
-
 static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
 					  const char *name_a,
 					  const char *name_b)
@@ -3157,7 +3141,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o
 	gather_dirstat(options, &dir, changed, "", 0);
 }
 
-static void free_diffstat_info(struct diffstat_t *diffstat)
+void free_diffstat_info(struct diffstat_t *diffstat)
 {
 	int i;
 	for (i = 0; i < diffstat->nr; i++) {
@@ -6283,12 +6267,7 @@ void diff_flush(struct diff_options *options)
 	    dirstat_by_line) {
 		struct diffstat_t diffstat;
 
-		memset(&diffstat, 0, sizeof(struct diffstat_t));
-		for (i = 0; i < q->nr; i++) {
-			struct diff_filepair *p = q->queue[i];
-			if (check_pair_status(p))
-				diff_flush_stat(p, options, &diffstat);
-		}
+		compute_diffstat(options, &diffstat, q);
 		if (output_format & DIFF_FORMAT_NUMSTAT)
 			show_numstat(&diffstat, options);
 		if (output_format & DIFF_FORMAT_DIFFSTAT)
@@ -6621,6 +6600,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)
 	return ignored;
 }
 
+void compute_diffstat(struct diff_options *options,
+		      struct diffstat_t *diffstat,
+		      struct diff_queue_struct *q)
+{
+	int i;
+
+	memset(diffstat, 0, sizeof(struct diffstat_t));
+	for (i = 0; i < q->nr; i++) {
+		struct diff_filepair *p = q->queue[i];
+		if (check_pair_status(p))
+			diff_flush_stat(p, options, diffstat);
+	}
+}
+
 void diff_addremove(struct diff_options *options,
 		    int addremove, unsigned mode,
 		    const struct object_id *oid,
diff --git a/diff.h b/diff.h
index 7f8f024feb..d986ddc3b5 100644
--- a/diff.h
+++ b/diff.h
@@ -245,6 +245,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);
 void diff_emit_submodule_pipethrough(struct diff_options *o,
 				     const char *line, int len);
 
+struct diffstat_t {
+	int nr;
+	int alloc;
+	struct diffstat_file {
+		char *from_name;
+		char *name;
+		char *print_name;
+		const char *comments;
+		unsigned is_unmerged:1;
+		unsigned is_binary:1;
+		unsigned is_renamed:1;
+		unsigned is_interesting:1;
+		uintmax_t added, deleted;
+	} **files;
+};
+
 enum color_diff {
 	DIFF_RESET = 0,
 	DIFF_CONTEXT = 1,
@@ -334,6 +350,10 @@ void diff_change(struct diff_options *,
 
 struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);
 
+void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
+		      struct diff_queue_struct *q);
+void free_diffstat_info(struct diffstat_t *diffstat);
+
 #define DIFF_SETUP_REVERSE      	1
 #define DIFF_SETUP_USE_SIZE_CACHE	4
 
-- 
gitgitgadget


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

* [PATCH v7 3/9] built-in add -i: implement the `status` command
  2019-11-15 11:11           ` [PATCH v7 " Johannes Schindelin via GitGitGadget
  2019-11-15 11:11             ` [PATCH v7 1/9] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
  2019-11-15 11:11             ` [PATCH v7 2/9] diff: export diffstat interface Daniel Ferreira via GitGitGadget
@ 2019-11-15 11:11             ` Daniel Ferreira via GitGitGadget
  2019-11-15 11:11             ` [PATCH v7 4/9] built-in add -i: color the header in " Slavica Đukić via GitGitGadget
                               ` (5 subsequent siblings)
  8 siblings, 0 replies; 124+ messages in thread
From: Daniel Ferreira via GitGitGadget @ 2019-11-15 11:11 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler <git@jeffhostetler.com>, Jeff King,
	Johannes Schindelin, Junio C Hamano, Daniel Ferreira

From: Daniel Ferreira <bnmvco@gmail.com>

This implements the `status` command of `git add -i`. The data
structures introduced in this commit will be extended later, as needed.

At this point, we re-implement only part of the `list_and_choose()`
function of the Perl script `git-add--interactive.perl` and call it
`list()`. It does not yet color anything, or do columns, or allow user
input.

Over the course of the next commits, we will introduce a
`list_and_choose()` function that uses `list()` to display the list of
options and let the user choose one or more of the displayed items. This
will be used to implement the main loop of the built-in `git add -i`, at
which point the new `status` command can actually be used.

Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 251 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 250 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index 482e458dc6..aa35184d87 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -1,7 +1,256 @@
 #include "cache.h"
 #include "add-interactive.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "refs.h"
+#include "string-list.h"
+
+struct add_i_state {
+	struct repository *r;
+};
+
+static void init_add_i_state(struct add_i_state *s, struct repository *r)
+{
+       s->r = r;
+}
+
+struct list_options {
+	const char *header;
+	void (*print_item)(int i, struct string_list_item *item, void *print_item_data);
+	void *print_item_data;
+};
+
+static void list(struct string_list *list, struct list_options *opts)
+{
+	int i;
+
+	if (!list->nr)
+		return;
+
+	if (opts->header)
+		printf("%s\n", opts->header);
+
+	for (i = 0; i < list->nr; i++) {
+		opts->print_item(i, list->items + i, opts->print_item_data);
+		putchar('\n');
+	}
+}
+
+struct adddel {
+	uintmax_t add, del;
+	unsigned seen:1, binary:1;
+};
+
+struct file_item {
+	struct adddel index, worktree;
+};
+
+static void add_file_item(struct string_list *files, const char *name)
+{
+	struct file_item *item = xcalloc(sizeof(*item), 1);
+
+	string_list_append(files, name)->util = item;
+}
+
+struct pathname_entry {
+	struct hashmap_entry ent;
+	const char *name;
+	struct file_item *item;
+};
+
+static int pathname_entry_cmp(const void *unused_cmp_data,
+			      const struct hashmap_entry *he1,
+			      const struct hashmap_entry *he2,
+			      const void *name)
+{
+	const struct pathname_entry *e1 =
+		container_of(he1, const struct pathname_entry, ent);
+	const struct pathname_entry *e2 =
+		container_of(he2, const struct pathname_entry, ent);
+
+	return strcmp(e1->name, name ? (const char *)name : e2->name);
+}
+
+struct collection_status {
+	enum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase;
+
+	const char *reference;
+
+	struct string_list *files;
+	struct hashmap file_map;
+};
+
+static void collect_changes_cb(struct diff_queue_struct *q,
+			       struct diff_options *options,
+			       void *data)
+{
+	struct collection_status *s = data;
+	struct diffstat_t stat = { 0 };
+	int i;
+
+	if (!q->nr)
+		return;
+
+	compute_diffstat(options, &stat, q);
+
+	for (i = 0; i < stat.nr; i++) {
+		const char *name = stat.files[i]->name;
+		int hash = strhash(name);
+		struct pathname_entry *entry;
+		struct file_item *file_item;
+		struct adddel *adddel;
+
+		entry = hashmap_get_entry_from_hash(&s->file_map, hash, name,
+						    struct pathname_entry, ent);
+		if (!entry) {
+			add_file_item(s->files, name);
+
+			entry = xcalloc(sizeof(*entry), 1);
+			hashmap_entry_init(&entry->ent, hash);
+			entry->name = s->files->items[s->files->nr - 1].string;
+			entry->item = s->files->items[s->files->nr - 1].util;
+			hashmap_add(&s->file_map, &entry->ent);
+		}
+
+		file_item = entry->item;
+		adddel = s->phase == FROM_INDEX ?
+			&file_item->index : &file_item->worktree;
+		adddel->seen = 1;
+		adddel->add = stat.files[i]->added;
+		adddel->del = stat.files[i]->deleted;
+		if (stat.files[i]->is_binary)
+			adddel->binary = 1;
+	}
+	free_diffstat_info(&stat);
+}
+
+static int get_modified_files(struct repository *r, struct string_list *files,
+			      const struct pathspec *ps)
+{
+	struct object_id head_oid;
+	int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+					     &head_oid, NULL);
+	struct collection_status s = { FROM_WORKTREE };
+
+	if (discard_index(r->index) < 0 ||
+	    repo_read_index_preload(r, ps, 0) < 0)
+		return error(_("could not read index"));
+
+	string_list_clear(files, 1);
+	s.files = files;
+	hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);
+
+	for (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {
+		struct rev_info rev;
+		struct setup_revision_opt opt = { 0 };
+
+		opt.def = is_initial ?
+			empty_tree_oid_hex() : oid_to_hex(&head_oid);
+
+		init_revisions(&rev, NULL);
+		setup_revisions(0, NULL, &rev, &opt);
+
+		rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+		rev.diffopt.format_callback = collect_changes_cb;
+		rev.diffopt.format_callback_data = &s;
+
+		if (ps)
+			copy_pathspec(&rev.prune_data, ps);
+
+		if (s.phase == FROM_INDEX)
+			run_diff_index(&rev, 1);
+		else {
+			rev.diffopt.flags.ignore_dirty_submodules = 1;
+			run_diff_files(&rev, 0);
+		}
+	}
+	hashmap_free_entries(&s.file_map, struct pathname_entry, ent);
+
+	/* While the diffs are ordered already, we ran *two* diffs... */
+	string_list_sort(files);
+
+	return 0;
+}
+
+static void render_adddel(struct strbuf *buf,
+				struct adddel *ad, const char *no_changes)
+{
+	if (ad->binary)
+		strbuf_addstr(buf, _("binary"));
+	else if (ad->seen)
+		strbuf_addf(buf, "+%"PRIuMAX"/-%"PRIuMAX,
+			    (uintmax_t)ad->add, (uintmax_t)ad->del);
+	else
+		strbuf_addstr(buf, no_changes);
+}
+
+struct print_file_item_data {
+	const char *modified_fmt;
+	struct strbuf buf, index, worktree;
+};
+
+static void print_file_item(int i, struct string_list_item *item,
+			    void *print_file_item_data)
+{
+	struct file_item *c = item->util;
+	struct print_file_item_data *d = print_file_item_data;
+
+	strbuf_reset(&d->index);
+	strbuf_reset(&d->worktree);
+	strbuf_reset(&d->buf);
+
+	render_adddel(&d->worktree, &c->worktree, _("nothing"));
+	render_adddel(&d->index, &c->index, _("unchanged"));
+	strbuf_addf(&d->buf, d->modified_fmt,
+		    d->index.buf, d->worktree.buf, item->string);
+
+	printf(" %2d: %s", i + 1, d->buf.buf);
+}
+
+static int run_status(struct add_i_state *s, const struct pathspec *ps,
+		      struct string_list *files, struct list_options *opts)
+{
+	if (get_modified_files(s->r, files, ps) < 0)
+		return -1;
+
+	list(files, opts);
+	putchar('\n');
+
+	return 0;
+}
 
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
-	die(_("No commands are available in the built-in `git add -i` yet!"));
+	struct add_i_state s = { NULL };
+	struct print_file_item_data print_file_item_data = {
+		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+	};
+	struct list_options opts = {
+		NULL, print_file_item, &print_file_item_data
+	};
+	struct strbuf header = STRBUF_INIT;
+	struct string_list files = STRING_LIST_INIT_DUP;
+	int res = 0;
+
+	init_add_i_state(&s, r);
+	strbuf_addstr(&header, "      ");
+	strbuf_addf(&header, print_file_item_data.modified_fmt,
+		    _("staged"), _("unstaged"), _("path"));
+	opts.header = header.buf;
+
+	if (discard_index(r->index) < 0 ||
+	    repo_read_index(r) < 0 ||
+	    repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
+					 NULL, NULL, NULL) < 0)
+		warning(_("could not refresh index"));
+
+	res = run_status(&s, ps, &files, &opts);
+
+	string_list_clear(&files, 1);
+	strbuf_release(&print_file_item_data.buf);
+	strbuf_release(&print_file_item_data.index);
+	strbuf_release(&print_file_item_data.worktree);
+	strbuf_release(&header);
+
+	return res;
 }
-- 
gitgitgadget


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

* [PATCH v7 4/9] built-in add -i: color the header in the `status` command
  2019-11-15 11:11           ` [PATCH v7 " Johannes Schindelin via GitGitGadget
                               ` (2 preceding siblings ...)
  2019-11-15 11:11             ` [PATCH v7 3/9] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
@ 2019-11-15 11:11             ` Slavica Đukić via GitGitGadget
  2019-11-15 11:11             ` [PATCH v7 5/9] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
                               ` (4 subsequent siblings)
  8 siblings, 0 replies; 124+ messages in thread
From: Slavica Đukić via GitGitGadget @ 2019-11-15 11:11 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler <git@jeffhostetler.com>, Jeff King,
	Johannes Schindelin, Junio C Hamano, Slavica Đukić

From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>

For simplicity, we only implemented the `status` command without colors.
This patch starts adding color, matching what the Perl script
`git-add--interactive.perl` does.

Original-Patch-By: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 41 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 37 insertions(+), 4 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index aa35184d87..174e07ce83 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -1,5 +1,7 @@
 #include "cache.h"
 #include "add-interactive.h"
+#include "color.h"
+#include "config.h"
 #include "diffcore.h"
 #include "revision.h"
 #include "refs.h"
@@ -7,11 +9,40 @@
 
 struct add_i_state {
 	struct repository *r;
+	int use_color;
+	char header_color[COLOR_MAXLEN];
 };
 
+static void init_color(struct repository *r, struct add_i_state *s,
+		       const char *slot_name, char *dst,
+		       const char *default_color)
+{
+	char *key = xstrfmt("color.interactive.%s", slot_name);
+	const char *value;
+
+	if (!s->use_color)
+		dst[0] = '\0';
+	else if (repo_config_get_value(r, key, &value) ||
+		 color_parse(value, dst))
+		strlcpy(dst, default_color, COLOR_MAXLEN);
+
+	free(key);
+}
+
 static void init_add_i_state(struct add_i_state *s, struct repository *r)
 {
-       s->r = r;
+	const char *value;
+
+	s->r = r;
+
+	if (repo_config_get_value(r, "color.interactive", &value))
+		s->use_color = -1;
+	else
+		s->use_color =
+			git_config_colorbool("color.interactive", value);
+	s->use_color = want_color(s->use_color);
+
+	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
 }
 
 struct list_options {
@@ -20,7 +51,8 @@ struct list_options {
 	void *print_item_data;
 };
 
-static void list(struct string_list *list, struct list_options *opts)
+static void list(struct add_i_state *s, struct string_list *list,
+		 struct list_options *opts)
 {
 	int i;
 
@@ -28,7 +60,8 @@ static void list(struct string_list *list, struct list_options *opts)
 		return;
 
 	if (opts->header)
-		printf("%s\n", opts->header);
+		color_fprintf_ln(stdout, s->header_color,
+				 "%s", opts->header);
 
 	for (i = 0; i < list->nr; i++) {
 		opts->print_item(i, list->items + i, opts->print_item_data);
@@ -213,7 +246,7 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	if (get_modified_files(s->r, files, ps) < 0)
 		return -1;
 
-	list(files, opts);
+	list(s, files, opts);
 	putchar('\n');
 
 	return 0;
-- 
gitgitgadget


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

* [PATCH v7 5/9] built-in add -i: implement the main loop
  2019-11-15 11:11           ` [PATCH v7 " Johannes Schindelin via GitGitGadget
                               ` (3 preceding siblings ...)
  2019-11-15 11:11             ` [PATCH v7 4/9] built-in add -i: color the header in " Slavica Đukić via GitGitGadget
@ 2019-11-15 11:11             ` Johannes Schindelin via GitGitGadget
  2019-11-15 11:11             ` [PATCH v7 6/9] built-in add -i: show unique prefixes of the commands Johannes Schindelin via GitGitGadget
                               ` (3 subsequent siblings)
  8 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-11-15 11:11 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler <git@jeffhostetler.com>, Jeff King,
	Johannes Schindelin, Junio C Hamano, Johannes Schindelin

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

The reason why we did not start with the main loop to begin with is that
it is the first user of `list_and_choose()`, which uses the `list()`
function that we conveniently introduced for use by the `status`
command.

In contrast to the Perl version, in the built-in interactive `add`, we
will keep the `list()` function (which only displays items) and the
`list_and_choose()` function (which uses `list()` to display the items,
and only takes care of the "and choose" part) separate.

The `list_and_choose()` function, as implemented in
`git-add--interactive.perl` knows a few more tricks than the function we
introduce in this patch:

- There is a flag to let the user select multiple items.

- In multi-select mode, the list of items is prefixed with a marker
  indicating what items have been selected.

- Initially, for each item a unique prefix is determined (if there
  exists any within the given parameters), and shown in the list, and
  accepted as a shortcut for the selection.

These features will be implemented in the C version later.

This patch does not add any new main loop command, of course, the
built-in `git add -i` still only supports the `status` command. The
remaining commands to follow over the course of the next commits.

To accommodate for listing the commands in columns, preparing for the
commands that will be implemented over the course of the next
patches/patch series, we teach the `list()` function to do precisely
that.

Note that we only have a prompt ending in a single ">" at this stage;
later commits will add commands that display a double ">>" to indicate
that the user is in a different loop than the main one.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 137 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 135 insertions(+), 2 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 174e07ce83..76d9824de0 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -46,6 +46,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)
 }
 
 struct list_options {
+	int columns;
 	const char *header;
 	void (*print_item)(int i, struct string_list_item *item, void *print_item_data);
 	void *print_item_data;
@@ -54,7 +55,7 @@ struct list_options {
 static void list(struct add_i_state *s, struct string_list *list,
 		 struct list_options *opts)
 {
-	int i;
+	int i, last_lf = 0;
 
 	if (!list->nr)
 		return;
@@ -65,8 +66,98 @@ static void list(struct add_i_state *s, struct string_list *list,
 
 	for (i = 0; i < list->nr; i++) {
 		opts->print_item(i, list->items + i, opts->print_item_data);
+
+		if ((opts->columns) && ((i + 1) % (opts->columns))) {
+			putchar('\t');
+			last_lf = 0;
+		}
+		else {
+			putchar('\n');
+			last_lf = 1;
+		}
+	}
+
+	if (!last_lf)
 		putchar('\n');
+}
+struct list_and_choose_options {
+	struct list_options list_opts;
+
+	const char *prompt;
+};
+
+#define LIST_AND_CHOOSE_ERROR (-1)
+#define LIST_AND_CHOOSE_QUIT  (-2)
+
+/*
+ * Returns the selected index.
+ *
+ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
+ * `LIST_AND_CHOOSE_QUIT` is returned.
+ */
+static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
+			       struct list_and_choose_options *opts)
+{
+	struct strbuf input = STRBUF_INIT;
+	ssize_t res = LIST_AND_CHOOSE_ERROR;
+
+	for (;;) {
+		char *p;
+
+		strbuf_reset(&input);
+
+		list(s, items, &opts->list_opts);
+
+		printf("%s%s", opts->prompt, "> ");
+		fflush(stdout);
+
+		if (strbuf_getline(&input, stdin) == EOF) {
+			putchar('\n');
+			res = LIST_AND_CHOOSE_QUIT;
+			break;
+		}
+		strbuf_trim(&input);
+
+		if (!input.len)
+			break;
+
+		p = input.buf;
+		for (;;) {
+			size_t sep = strcspn(p, " \t\r\n,");
+			ssize_t index = -1;
+
+			if (!sep) {
+				if (!*p)
+					break;
+				p++;
+				continue;
+			}
+
+			if (isdigit(*p)) {
+				char *endp;
+				index = strtoul(p, &endp, 10) - 1;
+				if (endp != p + sep)
+					index = -1;
+			}
+
+			if (p[sep])
+				p[sep++] = '\0';
+			if (index < 0 || index >= items->nr)
+				printf(_("Huh (%s)?\n"), p);
+			else {
+				res = index;
+				break;
+			}
+
+			p += sep;
+		}
+
+		if (res != LIST_AND_CHOOSE_ERROR)
+			break;
 	}
+
+	strbuf_release(&input);
+	return res;
 }
 
 struct adddel {
@@ -252,20 +343,48 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	return 0;
 }
 
+typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
+			 struct string_list *files,
+			 struct list_options *opts);
+
+static void print_command_item(int i, struct string_list_item *item,
+			       void *print_command_item_data)
+{
+	printf(" %2d: %s", i + 1, item->string);
+}
+
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
+	struct list_and_choose_options main_loop_opts = {
+		{ 4, N_("*** Commands ***"), print_command_item, NULL },
+		N_("What now")
+	};
+	struct {
+		const char *string;
+		command_t command;
+	} command_list[] = {
+		{ "status", run_status },
+	};
+	struct string_list commands = STRING_LIST_INIT_NODUP;
+
 	struct print_file_item_data print_file_item_data = {
 		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 	};
 	struct list_options opts = {
-		NULL, print_file_item, &print_file_item_data
+		0, NULL, print_file_item, &print_file_item_data
 	};
 	struct strbuf header = STRBUF_INIT;
 	struct string_list files = STRING_LIST_INIT_DUP;
+	ssize_t i;
 	int res = 0;
 
+	for (i = 0; i < ARRAY_SIZE(command_list); i++)
+		string_list_append(&commands, command_list[i].string)
+			->util = command_list[i].command;
+
 	init_add_i_state(&s, r);
+
 	strbuf_addstr(&header, "      ");
 	strbuf_addf(&header, print_file_item_data.modified_fmt,
 		    _("staged"), _("unstaged"), _("path"));
@@ -279,11 +398,25 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 
 	res = run_status(&s, ps, &files, &opts);
 
+	for (;;) {
+		i = list_and_choose(&s, &commands, &main_loop_opts);
+		if (i == LIST_AND_CHOOSE_QUIT) {
+			printf(_("Bye.\n"));
+			res = 0;
+			break;
+		}
+		if (i != LIST_AND_CHOOSE_ERROR) {
+			command_t command = commands.items[i].util;
+			res = command(&s, ps, &files, &opts);
+		}
+	}
+
 	string_list_clear(&files, 1);
 	strbuf_release(&print_file_item_data.buf);
 	strbuf_release(&print_file_item_data.index);
 	strbuf_release(&print_file_item_data.worktree);
 	strbuf_release(&header);
+	string_list_clear(&commands, 0);
 
 	return res;
 }
-- 
gitgitgadget


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

* [PATCH v7 6/9] built-in add -i: show unique prefixes of the commands
  2019-11-15 11:11           ` [PATCH v7 " Johannes Schindelin via GitGitGadget
                               ` (4 preceding siblings ...)
  2019-11-15 11:11             ` [PATCH v7 5/9] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
@ 2019-11-15 11:11             ` Johannes Schindelin via GitGitGadget
  2019-11-15 11:11             ` [PATCH v7 7/9] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
                               ` (2 subsequent siblings)
  8 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-11-15 11:11 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler <git@jeffhostetler.com>, Jeff King,
	Johannes Schindelin, Junio C Hamano, Johannes Schindelin

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

Just like in the Perl script `git-add--interactive.perl`, for each
command a unique prefix is determined (if there exists any within the
given parameters), and shown in the list, and accepted as a shortcut for
the command.

To determine the unique prefixes, as well as to look up the command in
question, we use a copy of the list and sort it.

While this might seem like overkill for a single command, it will make
much more sense when all the commands are implemented, and when we reuse
the same logic to present a list of files to edit, with convenient
unique prefixes.

At the start of the development of this patch series, a dedicated data
structure was introduced that imitated the Trie that the Perl version
implements. However, this was deemed overkill, and we now simply sort
the list before determining the length of the unique prefixes by looking
at each item's neighbor. As a bonus, we now use the same sorted list to
perform a binary search using the user-provided prefix as search key.

Original-patch-by: Slavica Đukić <slawica92@hotmail.com>
Helped-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
---
 add-interactive.c | 188 +++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 177 insertions(+), 11 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 76d9824de0..c5d95d4796 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -45,6 +45,132 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)
 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
 }
 
+/*
+ * A "prefix item list" is a list of items that are identified by a string, and
+ * a unique prefix (if any) is determined for each item.
+ *
+ * It is implemented in the form of a pair of `string_list`s, the first one
+ * duplicating the strings, with the `util` field pointing at a structure whose
+ * first field must be `size_t prefix_length`.
+ *
+ * That `prefix_length` field will be computed by `find_unique_prefixes()`; It
+ * will be set to zero if no valid, unique prefix could be found.
+ *
+ * The second `string_list` is called `sorted` and does _not_ duplicate the
+ * strings but simply reuses the first one's, with the `util` field pointing at
+ * the `string_item_list` of the first `string_list`. It  will be populated and
+ * sorted by `find_unique_prefixes()`.
+ */
+struct prefix_item_list {
+	struct string_list items;
+	struct string_list sorted;
+	size_t min_length, max_length;
+};
+#define PREFIX_ITEM_LIST_INIT \
+	{ STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, 1, 4 }
+
+static void prefix_item_list_clear(struct prefix_item_list *list)
+{
+	string_list_clear(&list->items, 1);
+	string_list_clear(&list->sorted, 0);
+}
+
+static void extend_prefix_length(struct string_list_item *p,
+				 const char *other_string, size_t max_length)
+{
+	size_t *len = p->util;
+
+	if (!*len || memcmp(p->string, other_string, *len))
+		return;
+
+	for (;;) {
+		char c = p->string[*len];
+
+		/*
+		 * Is `p` a strict prefix of `other`? Or have we exhausted the
+		 * maximal length of the prefix? Or is the current character a
+		 * multi-byte UTF-8 one? If so, there is no valid, unique
+		 * prefix.
+		 */
+		if (!c || ++*len > max_length || !isascii(c)) {
+			*len = 0;
+			break;
+		}
+
+		if (c != other_string[*len - 1])
+			break;
+	}
+}
+
+static void find_unique_prefixes(struct prefix_item_list *list)
+{
+	size_t i;
+
+	if (list->sorted.nr == list->items.nr)
+		return;
+
+	string_list_clear(&list->sorted, 0);
+	/* Avoid reallocating incrementally */
+	list->sorted.items = xmalloc(st_mult(sizeof(*list->sorted.items),
+					     list->items.nr));
+	list->sorted.nr = list->sorted.alloc = list->items.nr;
+
+	for (i = 0; i < list->items.nr; i++) {
+		list->sorted.items[i].string = list->items.items[i].string;
+		list->sorted.items[i].util = list->items.items + i;
+	}
+
+	string_list_sort(&list->sorted);
+
+	for (i = 0; i < list->sorted.nr; i++) {
+		struct string_list_item *sorted_item = list->sorted.items + i;
+		struct string_list_item *item = sorted_item->util;
+		size_t *len = item->util;
+
+		*len = 0;
+		while (*len < list->min_length) {
+			char c = item->string[(*len)++];
+
+			if (!c || !isascii(c)) {
+				*len = 0;
+				break;
+			}
+		}
+
+		if (i > 0)
+			extend_prefix_length(item, sorted_item[-1].string,
+					     list->max_length);
+		if (i + 1 < list->sorted.nr)
+			extend_prefix_length(item, sorted_item[1].string,
+					     list->max_length);
+	}
+}
+
+static ssize_t find_unique(const char *string, struct prefix_item_list *list)
+{
+	int index = string_list_find_insert_index(&list->sorted, string, 1);
+	struct string_list_item *item;
+
+	if (list->items.nr != list->sorted.nr)
+		BUG("prefix_item_list in inconsistent state (%"PRIuMAX
+		    " vs %"PRIuMAX")",
+		    (uintmax_t)list->items.nr, (uintmax_t)list->sorted.nr);
+
+	if (index < 0)
+		item = list->sorted.items[-1 - index].util;
+	else if (index > 0 &&
+		 starts_with(list->sorted.items[index - 1].string, string))
+		return -1;
+	else if (index + 1 < list->sorted.nr &&
+		 starts_with(list->sorted.items[index + 1].string, string))
+		return -1;
+	else if (index < list->sorted.nr)
+		item = list->sorted.items[index].util;
+	else
+		return -1;
+	return item - list->items.items;
+}
+
 struct list_options {
 	int columns;
 	const char *header;
@@ -95,18 +221,21 @@ struct list_and_choose_options {
  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
  * `LIST_AND_CHOOSE_QUIT` is returned.
  */
-static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
+static ssize_t list_and_choose(struct add_i_state *s,
+			       struct prefix_item_list *items,
 			       struct list_and_choose_options *opts)
 {
 	struct strbuf input = STRBUF_INIT;
 	ssize_t res = LIST_AND_CHOOSE_ERROR;
 
+	find_unique_prefixes(items);
+
 	for (;;) {
 		char *p;
 
 		strbuf_reset(&input);
 
-		list(s, items, &opts->list_opts);
+		list(s, &items->items, &opts->list_opts);
 
 		printf("%s%s", opts->prompt, "> ");
 		fflush(stdout);
@@ -142,7 +271,10 @@ static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
 
 			if (p[sep])
 				p[sep++] = '\0';
-			if (index < 0 || index >= items->nr)
+			if (index < 0)
+				index = find_unique(p, items);
+
+			if (index < 0 || index >= items->items.nr)
 				printf(_("Huh (%s)?\n"), p);
 			else {
 				res = index;
@@ -308,6 +440,23 @@ static void render_adddel(struct strbuf *buf,
 		strbuf_addstr(buf, no_changes);
 }
 
+/* filters out prefixes which have special meaning to list_and_choose() */
+static int is_valid_prefix(const char *prefix, size_t prefix_len)
+{
+	return prefix_len && prefix &&
+		/*
+		 * We expect `prefix` to be NUL terminated, therefore this
+		 * `strcspn()` call is okay, even if it might do much more
+		 * work than strictly necessary.
+		 */
+		strcspn(prefix, " \t\r\n,") >= prefix_len &&	/* separators */
+		*prefix != '-' &&				/* deselection */
+		!isdigit(*prefix) &&				/* selection */
+		(prefix_len != 1 ||
+		 (*prefix != '*' &&				/* "all" wildcard */
+		  *prefix != '?'));				/* prompt help */
+}
+
 struct print_file_item_data {
 	const char *modified_fmt;
 	struct strbuf buf, index, worktree;
@@ -347,10 +496,23 @@ typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
 			 struct string_list *files,
 			 struct list_options *opts);
 
+struct command_item {
+	size_t prefix_length;
+	command_t command;
+};
+
 static void print_command_item(int i, struct string_list_item *item,
 			       void *print_command_item_data)
 {
-	printf(" %2d: %s", i + 1, item->string);
+	struct command_item *util = item->util;
+
+	if (!util->prefix_length ||
+	    !is_valid_prefix(item->string, util->prefix_length))
+		printf(" %2d: %s", i + 1, item->string);
+	else
+		printf(" %2d: [%.*s]%s", i + 1,
+		       (int)util->prefix_length, item->string,
+		       item->string + util->prefix_length);
 }
 
 int run_add_i(struct repository *r, const struct pathspec *ps)
@@ -366,7 +528,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	} command_list[] = {
 		{ "status", run_status },
 	};
-	struct string_list commands = STRING_LIST_INIT_NODUP;
+	struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
 
 	struct print_file_item_data print_file_item_data = {
 		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
@@ -379,9 +541,12 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	ssize_t i;
 	int res = 0;
 
-	for (i = 0; i < ARRAY_SIZE(command_list); i++)
-		string_list_append(&commands, command_list[i].string)
-			->util = command_list[i].command;
+	for (i = 0; i < ARRAY_SIZE(command_list); i++) {
+		struct command_item *util = xcalloc(sizeof(*util), 1);
+		util->command = command_list[i].command;
+		string_list_append(&commands.items, command_list[i].string)
+			->util = util;
+	}
 
 	init_add_i_state(&s, r);
 
@@ -406,8 +571,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 			break;
 		}
 		if (i != LIST_AND_CHOOSE_ERROR) {
-			command_t command = commands.items[i].util;
-			res = command(&s, ps, &files, &opts);
+			struct command_item *util =
+				commands.items.items[i].util;
+			res = util->command(&s, ps, &files, &opts);
 		}
 	}
 
@@ -416,7 +582,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 	strbuf_release(&print_file_item_data.index);
 	strbuf_release(&print_file_item_data.worktree);
 	strbuf_release(&header);
-	string_list_clear(&commands, 0);
+	prefix_item_list_clear(&commands);
 
 	return res;
 }
-- 
gitgitgadget


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

* [PATCH v7 7/9] built-in add -i: support `?` (prompt help)
  2019-11-15 11:11           ` [PATCH v7 " Johannes Schindelin via GitGitGadget
                               ` (5 preceding siblings ...)
  2019-11-15 11:11             ` [PATCH v7 6/9] built-in add -i: show unique prefixes of the commands Johannes Schindelin via GitGitGadget
@ 2019-11-15 11:11             ` Johannes Schindelin via GitGitGadget
  2019-11-15 11:11             ` [PATCH v7 8/9] built-in add -i: use color in the main loop Slavica Đukić via GitGitGadget
  2019-11-15 11:11             ` [PATCH v7 9/9] built-in add -i: implement the `help` command Slavica Đukić via GitGitGadget
  8 siblings, 0 replies; 124+ messages in thread
From: Johannes Schindelin via GitGitGadget @ 2019-11-15 11:11 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler <git@jeffhostetler.com>, Jeff King,
	Johannes Schindelin, Junio C Hamano, Johannes Schindelin

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

With this change, we print out the same colored help text that the
Perl-based `git add -i` prints in the main loop when question mark is
entered.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/add-interactive.c b/add-interactive.c
index c5d95d4796..0a03b9017d 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -11,6 +11,7 @@ struct add_i_state {
 	struct repository *r;
 	int use_color;
 	char header_color[COLOR_MAXLEN];
+	char help_color[COLOR_MAXLEN];
 };
 
 static void init_color(struct repository *r, struct add_i_state *s,
@@ -43,6 +44,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)
 	s->use_color = want_color(s->use_color);
 
 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
+	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
 }
 
 /*
@@ -210,6 +212,7 @@ struct list_and_choose_options {
 	struct list_options list_opts;
 
 	const char *prompt;
+	void (*print_help)(struct add_i_state *s);
 };
 
 #define LIST_AND_CHOOSE_ERROR (-1)
@@ -250,6 +253,11 @@ static ssize_t list_and_choose(struct add_i_state *s,
 		if (!input.len)
 			break;
 
+		if (!strcmp(input.buf, "?")) {
+			opts->print_help(s);
+			continue;
+		}
+
 		p = input.buf;
 		for (;;) {
 			size_t sep = strcspn(p, " \t\r\n,");
@@ -515,12 +523,24 @@ static void print_command_item(int i, struct string_list_item *item,
 		       item->string + util->prefix_length);
 }
 
+static void command_prompt_help(struct add_i_state *s)
+{
+	const char *help_color = s->help_color;
+	color_fprintf_ln(stdout, help_color, "%s", _("Prompt help:"));
+	color_fprintf_ln(stdout, help_color, "1          - %s",
+			 _("select a numbered item"));
+	color_fprintf_ln(stdout, help_color, "foo        - %s",
+			 _("select item based on unique prefix"));
+	color_fprintf_ln(stdout, help_color, "           - %s",
+			 _("(empty) select nothing"));
+}
+
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
 	struct list_and_choose_options main_loop_opts = {
 		{ 4, N_("*** Commands ***"), print_command_item, NULL },
-		N_("What now")
+		N_("What now"), command_prompt_help
 	};
 	struct {
 		const char *string;
-- 
gitgitgadget


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

* [PATCH v7 8/9] built-in add -i: use color in the main loop
  2019-11-15 11:11           ` [PATCH v7 " Johannes Schindelin via GitGitGadget
                               ` (6 preceding siblings ...)
  2019-11-15 11:11             ` [PATCH v7 7/9] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
@ 2019-11-15 11:11             ` Slavica Đukić via GitGitGadget
  2019-11-15 11:11             ` [PATCH v7 9/9] built-in add -i: implement the `help` command Slavica Đukić via GitGitGadget
  8 siblings, 0 replies; 124+ messages in thread
From: Slavica Đukić via GitGitGadget @ 2019-11-15 11:11 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler <git@jeffhostetler.com>, Jeff King,
	Johannes Schindelin, Junio C Hamano, Slavica Đukić

From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>

The error messages as well as the unique prefixes are colored in `git
add -i` by default; We need to do the same in the built-in version.

Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c | 35 +++++++++++++++++++++++++++++------
 1 file changed, 29 insertions(+), 6 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 0a03b9017d..170a5800e3 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -12,6 +12,9 @@ struct add_i_state {
 	int use_color;
 	char header_color[COLOR_MAXLEN];
 	char help_color[COLOR_MAXLEN];
+	char prompt_color[COLOR_MAXLEN];
+	char error_color[COLOR_MAXLEN];
+	char reset_color[COLOR_MAXLEN];
 };
 
 static void init_color(struct repository *r, struct add_i_state *s,
@@ -45,6 +48,9 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)
 
 	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
 	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
+	init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE);
+	init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED);
+	init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET);
 }
 
 /*
@@ -240,7 +246,8 @@ static ssize_t list_and_choose(struct add_i_state *s,
 
 		list(s, &items->items, &opts->list_opts);
 
-		printf("%s%s", opts->prompt, "> ");
+		color_fprintf(stdout, s->prompt_color, "%s", opts->prompt);
+		fputs("> ", stdout);
 		fflush(stdout);
 
 		if (strbuf_getline(&input, stdin) == EOF) {
@@ -283,7 +290,8 @@ static ssize_t list_and_choose(struct add_i_state *s,
 				index = find_unique(p, items);
 
 			if (index < 0 || index >= items->items.nr)
-				printf(_("Huh (%s)?\n"), p);
+				color_fprintf_ln(stdout, s->error_color,
+						 _("Huh (%s)?"), p);
 			else {
 				res = index;
 				break;
@@ -509,18 +517,23 @@ struct command_item {
 	command_t command;
 };
 
+struct print_command_item_data {
+	const char *color, *reset;
+};
+
 static void print_command_item(int i, struct string_list_item *item,
 			       void *print_command_item_data)
 {
+	struct print_command_item_data *d = print_command_item_data;
 	struct command_item *util = item->util;
 
 	if (!util->prefix_length ||
 	    !is_valid_prefix(item->string, util->prefix_length))
 		printf(" %2d: %s", i + 1, item->string);
 	else
-		printf(" %2d: [%.*s]%s", i + 1,
-		       (int)util->prefix_length, item->string,
-		       item->string + util->prefix_length);
+		printf(" %2d: %s%.*s%s%s", i + 1,
+		       d->color, (int)util->prefix_length, item->string,
+		       d->reset, item->string + util->prefix_length);
 }
 
 static void command_prompt_help(struct add_i_state *s)
@@ -538,8 +551,9 @@ static void command_prompt_help(struct add_i_state *s)
 int run_add_i(struct repository *r, const struct pathspec *ps)
 {
 	struct add_i_state s = { NULL };
+	struct print_command_item_data data = { "[", "]" };
 	struct list_and_choose_options main_loop_opts = {
-		{ 4, N_("*** Commands ***"), print_command_item, NULL },
+		{ 4, N_("*** Commands ***"), print_command_item, &data },
 		N_("What now"), command_prompt_help
 	};
 	struct {
@@ -570,6 +584,15 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 
 	init_add_i_state(&s, r);
 
+	/*
+	 * When color was asked for, use the prompt color for
+	 * highlighting, otherwise use square brackets.
+	 */
+	if (s.use_color) {
+		data.color = s.prompt_color;
+		data.reset = s.reset_color;
+	}
+
 	strbuf_addstr(&header, "      ");
 	strbuf_addf(&header, print_file_item_data.modified_fmt,
 		    _("staged"), _("unstaged"), _("path"));
-- 
gitgitgadget


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

* [PATCH v7 9/9] built-in add -i: implement the `help` command
  2019-11-15 11:11           ` [PATCH v7 " Johannes Schindelin via GitGitGadget
                               ` (7 preceding siblings ...)
  2019-11-15 11:11             ` [PATCH v7 8/9] built-in add -i: use color in the main loop Slavica Đukić via GitGitGadget
@ 2019-11-15 11:11             ` Slavica Đukić via GitGitGadget
  8 siblings, 0 replies; 124+ messages in thread
From: Slavica Đukić via GitGitGadget @ 2019-11-15 11:11 UTC (permalink / raw)
  To: git
  Cc: Jeff Hostetler <git@jeffhostetler.com>, Jeff King,
	Johannes Schindelin, Junio C Hamano, Slavica Đukić

From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>

This imitates the code to show the help text from the Perl script
`git-add--interactive.perl` in the built-in version.

To make sure that it renders exactly like the Perl version of `git add
-i`, we also add a test case for that to `t3701-add-interactive.sh`.

Signed-off-by: Slavica Đukić <slawica92@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-interactive.c          | 21 +++++++++++++++++++++
 t/t3701-add-interactive.sh | 25 +++++++++++++++++++++++++
 2 files changed, 46 insertions(+)

diff --git a/add-interactive.c b/add-interactive.c
index 170a5800e3..d6cb98cd40 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -508,6 +508,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,
 	return 0;
 }
 
+static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,
+		    struct string_list *unused_files,
+		    struct list_options *unused_opts)
+{
+	color_fprintf_ln(stdout, s->help_color, "status        - %s",
+			 _("show paths with changes"));
+	color_fprintf_ln(stdout, s->help_color, "update        - %s",
+			 _("add working tree state to the staged set of changes"));
+	color_fprintf_ln(stdout, s->help_color, "revert        - %s",
+			 _("revert staged set of changes back to the HEAD version"));
+	color_fprintf_ln(stdout, s->help_color, "patch         - %s",
+			 _("pick hunks and update selectively"));
+	color_fprintf_ln(stdout, s->help_color, "diff          - %s",
+			 _("view diff between HEAD and index"));
+	color_fprintf_ln(stdout, s->help_color, "add untracked - %s",
+			 _("add contents of untracked files to the staged set of changes"));
+
+	return 0;
+}
+
 typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
 			 struct string_list *files,
 			 struct list_options *opts);
@@ -561,6 +581,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 		command_t command;
 	} command_list[] = {
 		{ "status", run_status },
+		{ "help", run_help },
 	};
 	struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
 
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index d50e165ca8..d4f9386621 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' '
 	test_write_lines a b a b a a b a b a >expect &&
 	test_cmp expect a
 '
+
+test_expect_success 'show help from add--helper' '
+	git reset --hard &&
+	cat >expect <<-EOF &&
+
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>
+	<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>
+	<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>
+	<BOLD;RED>patch         - pick hunks and update selectively<RESET>
+	<BOLD;RED>diff          - view diff between HEAD and index<RESET>
+	<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>
+	<BOLD>*** Commands ***<RESET>
+	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	<BOLD;BLUE>What now<RESET>>$SP
+	Bye.
+	EOF
+	test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&
+	test_decode_color <actual.colored >actual &&
+	test_i18ncmp expect actual
+'
+
 test_done
-- 
gitgitgadget

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

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

Thread overview: 124+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-04-10 17:37 [PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
2019-04-10 17:37 ` [PATCH 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
2019-04-18 14:31   ` Jeff Hostetler
2019-04-18 16:06     ` Jeff King
2019-04-30 23:40       ` Johannes Schindelin
2019-05-01  2:21         ` Jeff King
2019-05-13 11:14           ` Johannes Schindelin
2019-04-10 17:37 ` [PATCH 02/11] diff: export diffstat interface Daniel Ferreira via GitGitGadget
2019-04-10 17:37 ` [PATCH 03/11] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
2019-04-10 17:37 ` [PATCH 04/11] built-in add -i: refresh the index before running `status` Johannes Schindelin via GitGitGadget
2019-04-10 17:37 ` [PATCH 05/11] built-in add -i: color the header in the `status` command Johannes Schindelin via GitGitGadget
2019-04-10 17:37 ` [PATCH 06/11] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
2019-04-18 16:49   ` Jeff Hostetler
2019-05-13 12:04     ` Johannes Schindelin
2019-04-10 17:37 ` [PATCH 07/11] Add a function to determine unique prefixes for a list of strings Slavica Djukic via GitGitGadget
2019-04-18 17:57   ` Jeff Hostetler
2019-05-13 12:48     ` Johannes Schindelin
2019-04-10 17:37 ` [PATCH 08/11] built-in add -i: show unique prefixes of the commands Slavica Djukic via GitGitGadget
2019-04-10 17:37 ` [PATCH 09/11] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
2019-04-10 17:37 ` [PATCH 10/11] built-in add -i: use color in the main loop Slavica Djukic via GitGitGadget
2019-04-10 17:37 ` [PATCH 11/11] built-in add -i: implement the `help` command Johannes Schindelin via GitGitGadget
2019-05-13 17:27 ` [PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
2019-05-13 17:27   ` [PATCH v2 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
2019-05-13 17:27   ` [PATCH v2 02/11] diff: export diffstat interface Daniel Ferreira via GitGitGadget
2019-05-13 17:27   ` [PATCH v2 03/11] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
2019-05-13 17:27   ` [PATCH v2 04/11] built-in add -i: refresh the index before running `status` Johannes Schindelin via GitGitGadget
2019-05-13 17:27   ` [PATCH v2 05/11] built-in add -i: color the header in the `status` command Johannes Schindelin via GitGitGadget
2019-05-13 17:27   ` [PATCH v2 06/11] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
2019-05-13 17:27   ` [PATCH v2 07/11] Add a function to determine unique prefixes for a list of strings Slavica Djukic via GitGitGadget
2019-05-13 17:27   ` [PATCH v2 08/11] built-in add -i: show unique prefixes of the commands Slavica Djukic via GitGitGadget
2019-05-13 17:28   ` [PATCH v2 09/11] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
2019-05-13 17:28   ` [PATCH v2 10/11] built-in add -i: use color in the main loop Slavica Djukic via GitGitGadget
2019-05-13 17:28   ` [PATCH v2 11/11] built-in add -i: implement the `help` command Johannes Schindelin via GitGitGadget
2019-07-16 14:58   ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
2019-07-16 14:58     ` [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
2019-07-31 17:52       ` Junio C Hamano
2019-08-26 21:26         ` Johannes Schindelin
2019-08-27 22:25           ` Junio C Hamano
2019-08-28 15:06             ` Johannes Schindelin
2019-08-28 15:37               ` Junio C Hamano
2019-07-16 14:58     ` [PATCH v3 02/11] diff: export diffstat interface Daniel Ferreira via GitGitGadget
2019-07-31 17:59       ` Junio C Hamano
2019-08-27  9:22         ` Johannes Schindelin
2019-07-16 14:58     ` [PATCH v3 03/11] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
2019-07-31 18:12       ` Junio C Hamano
2019-08-27 10:04         ` Johannes Schindelin
2019-07-16 14:58     ` [PATCH v3 04/11] built-in add -i: refresh the index before running `status` Johannes Schindelin via GitGitGadget
2019-07-16 14:58     ` [PATCH v3 05/11] built-in add -i: color the header in the `status` command Johannes Schindelin via GitGitGadget
2019-07-16 14:58     ` [PATCH v3 06/11] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
2019-07-31 18:14       ` Junio C Hamano
2019-07-16 14:58     ` [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings Slavica Djukic via GitGitGadget
2019-07-31 18:39       ` Junio C Hamano
2019-08-24 12:38       ` SZEDER Gábor
2019-08-27 12:14         ` Johannes Schindelin
2019-08-28 16:30           ` SZEDER Gábor
2019-08-28 16:34             ` [PATCH] [PoC] A simpler find_unique_prefixes() implementation SZEDER Gábor
2019-08-30 20:12               ` Johannes Schindelin
2019-07-16 14:58     ` [PATCH v3 08/11] built-in add -i: show unique prefixes of the commands Slavica Djukic via GitGitGadget
2019-07-16 14:58     ` [PATCH v3 09/11] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
2019-07-16 14:58     ` [PATCH v3 10/11] built-in add -i: use color in the main loop Slavica Djukic via GitGitGadget
2019-07-16 14:58     ` [PATCH v3 11/11] built-in add -i: implement the `help` command Johannes Schindelin via GitGitGadget
2019-08-02 21:04       ` Junio C Hamano
2019-08-02 22:26         ` Jeff King
2019-07-16 18:38     ` [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin
2019-08-02 21:06       ` Junio C Hamano
2019-08-27 12:57     ` [PATCH v4 " Johannes Schindelin via GitGitGadget
2019-08-27 12:57       ` [PATCH v4 01/11] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
2019-08-27 12:57       ` [PATCH v4 02/11] diff: export diffstat interface Daniel Ferreira via GitGitGadget
2019-08-27 12:57       ` [PATCH v4 03/11] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
2019-08-27 12:57       ` [PATCH v4 04/11] built-in add -i: refresh the index before running `status` Johannes Schindelin via GitGitGadget
2019-08-27 12:57       ` [PATCH v4 05/11] built-in add -i: color the header in the `status` command Johannes Schindelin via GitGitGadget
2019-08-27 12:57       ` [PATCH v4 07/11] Add a function to determine unique prefixes for a list of strings Slavica Djukic via GitGitGadget
2019-08-27 12:57       ` [PATCH v4 06/11] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
2019-08-27 12:57       ` [PATCH v4 08/11] built-in add -i: show unique prefixes of the commands Slavica Djukic via GitGitGadget
2019-08-27 12:58       ` [PATCH v4 09/11] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
2019-08-27 12:58       ` [PATCH v4 10/11] built-in add -i: use color in the main loop Slavica Djukic via GitGitGadget
2019-08-27 12:58       ` [PATCH v4 11/11] built-in add -i: implement the `help` command Johannes Schindelin via GitGitGadget
2019-11-04 12:15       ` [PATCH v5 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
2019-11-04 12:15         ` [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
2019-11-08  4:49           ` Junio C Hamano
2019-11-09 11:06             ` Johannes Schindelin
2019-11-10  7:18               ` Junio C Hamano
2019-11-11  9:15                 ` Johannes Schindelin
2019-11-11 12:09                   ` Junio C Hamano
2019-11-12 15:03                     ` Johannes Schindelin
2019-11-13  3:54                       ` Junio C Hamano
2019-11-13 12:30                         ` Johannes Schindelin
2019-11-13 14:01                           ` Junio C Hamano
2019-11-04 12:15         ` [PATCH v5 2/9] diff: export diffstat interface Daniel Ferreira via GitGitGadget
2019-11-08  4:56           ` Junio C Hamano
2019-11-04 12:15         ` [PATCH v5 3/9] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
2019-11-08  5:01           ` Junio C Hamano
2019-11-04 12:15         ` [PATCH v5 4/9] built-in add -i: color the header in " Slavica Đukić via GitGitGadget
2019-11-04 12:15         ` [PATCH v5 5/9] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
2019-11-08  5:17           ` Junio C Hamano
2019-11-09 11:21             ` Johannes Schindelin
2019-11-04 12:15         ` [PATCH v5 6/9] built-in add -i: show unique prefixes of the commands Johannes Schindelin via GitGitGadget
2019-11-04 12:15         ` [PATCH v5 7/9] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
2019-11-04 12:15         ` [PATCH v5 8/9] built-in add -i: use color in the main loop Slavica Đukić via GitGitGadget
2019-11-04 12:15         ` [PATCH v5 9/9] built-in add -i: implement the `help` command Slavica Đukić via GitGitGadget
2019-11-13 12:40         ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin via GitGitGadget
2019-11-13 12:40           ` [PATCH v6 1/9] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
2019-11-14  2:15             ` Junio C Hamano
2019-11-14 15:07               ` Johannes Schindelin
2019-11-15  4:35                 ` Junio C Hamano
2019-11-13 12:40           ` [PATCH v6 2/9] diff: export diffstat interface Daniel Ferreira via GitGitGadget
2019-11-13 12:40           ` [PATCH v6 3/9] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
2019-11-13 12:41           ` [PATCH v6 4/9] built-in add -i: color the header in " Slavica Đukić via GitGitGadget
2019-11-13 12:41           ` [PATCH v6 5/9] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
2019-11-13 12:41           ` [PATCH v6 6/9] built-in add -i: show unique prefixes of the commands Johannes Schindelin via GitGitGadget
2019-11-13 12:41           ` [PATCH v6 7/9] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
2019-11-13 12:41           ` [PATCH v6 8/9] built-in add -i: use color in the main loop Slavica Đukić via GitGitGadget
2019-11-13 12:41           ` [PATCH v6 9/9] built-in add -i: implement the `help` command Slavica Đukić via GitGitGadget
2019-11-13 12:46           ` [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far) Johannes Schindelin
2019-11-15 11:11           ` [PATCH v7 " Johannes Schindelin via GitGitGadget
2019-11-15 11:11             ` [PATCH v7 1/9] Start to implement a built-in version of `git add --interactive` Johannes Schindelin via GitGitGadget
2019-11-15 11:11             ` [PATCH v7 2/9] diff: export diffstat interface Daniel Ferreira via GitGitGadget
2019-11-15 11:11             ` [PATCH v7 3/9] built-in add -i: implement the `status` command Daniel Ferreira via GitGitGadget
2019-11-15 11:11             ` [PATCH v7 4/9] built-in add -i: color the header in " Slavica Đukić via GitGitGadget
2019-11-15 11:11             ` [PATCH v7 5/9] built-in add -i: implement the main loop Johannes Schindelin via GitGitGadget
2019-11-15 11:11             ` [PATCH v7 6/9] built-in add -i: show unique prefixes of the commands Johannes Schindelin via GitGitGadget
2019-11-15 11:11             ` [PATCH v7 7/9] built-in add -i: support `?` (prompt help) Johannes Schindelin via GitGitGadget
2019-11-15 11:11             ` [PATCH v7 8/9] built-in add -i: use color in the main loop Slavica Đukić via GitGitGadget
2019-11-15 11:11             ` [PATCH v7 9/9] built-in add -i: implement the `help` command Slavica Đukić via GitGitGadget

Code repositories for project(s) associated with this public inbox

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

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