{"thread":{"id":"50913","subject":"[PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far)","startedAt":"2019-04-10T17:37:54Z","lastAt":"2019-11-15T11:11:38Z","messageCount":124,"participants":["Johannes Schindelin via GitGitGadget","Daniel Ferreira via GitGitGadget","Slavica Djukic via GitGitGadget","Jeff Hostetler","Jeff King","Johannes Schindelin","Junio C Hamano","SZEDER Gábor","Slavica Đukić via GitGitGadget"],"isPatch":true,"patchVersion":1,"patchTotal":11},"messages":[{"id":"373594","messageId":"pull.170.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":null,"subject":"[PATCH 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far)","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-04-10T17:37:49Z","receivedAt":"2019-04-10T17:37:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is the first leg on the long journey to a fully built-in git add -i \n(next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3\n[https://github.com/gitgitgadget/git/pull/172], 4\n[https://github.com/gitgitgadget/git/pull/173], 5\n[https://github.com/gitgitgadget/git/pull/174], and 6\n[https://github.com/gitgitgadget/git/pull/175]).\n\nIt reflects the part that was submitted a couple of times (see \nhttps://github.com/gitgitgadget/git/pull/103) during the Outreachy project\nby Slavica Ðukic that continued the journey based on an initial patch series\nby Daniel Ferreira.\n\nThis part only implements the status and the help part, like Slavica's last\niteration did, in the interest of making the review remotely more\nreviewable. I fear that this attempt of making it a bit more reviewable is\npretty futile, as so many things changed. So I will ask the reviewers for\nforgiveness: please be kind, and give this sort of a fresh review.\n\nI threw in a couple of major changes on top of that iteration, though:\n\n * The original plan was to add a helper (git add--helper) that takes over\n   more and more responsibility from the Perl script over the course of the\n   conversion.\n   \n   This plan is no longer in effect, as I encountered a serious problem with\n   that: the MSYS2 runtime used by the Perl interpreter which Git for\n   Windows employs to run git add -i has a curious bug (that is safely\n   outside the purview of this here patch series) where it fails to read\n   from standard input after it spawned a non-MSYS2 program (such as \n   add--helper) that reads from standard input. So that wouldn't work, but\n   quit the interactive add as soon as even one interactive command had been\n   handled by the helper. To keep my git add -i in a working state, I\n   therefore adopted a different strategy:\n   \n   Just like git difftool was converted by starting with a built-in that did\n   nothing but handing off to the scripted version, guarded by the (opt-in) \n   difftool.useBuiltin config setting, I start this patch series by a\n   built-in add -i that does nothing else but state that it is not\n   implemented yet, guarded by the (opt-in) add.interactive.useBuiltin \n   config setting.\n   \n   In contrast to the git difftool situation, it is quite a bit easier here,\n   as we do not even have to rename the script to \n   git-legacy-add--interactive.perl: the add--interactive command is an\n   implementation detail that users are not even supposed to know about.\n   Therefore, we can implement that road fork between the built-in and the\n   scripted version in builtin/add.c, i.e. in the user-facing git add \n   command.\n   \n   This will also naturally help with the transition to a fully built-in git\n   add -i/git add -p, as we saw with the built-in git rebase how important\n   it is for end users to have an escape hatch (and for that reason, tried\n   our best to provide the same with the built-in git stash).\n   \n   \n * The help command was actually not hooked up in git add -i in Slavica's\n   last iteration, but was only available as a special option of the git\n   add--helper command. As that command no longer exists, I kind of had to\n   implement some way to let the built-in git add -i show the help text.\n   \n   \n * The main loop of git add -i (i.e. the thing that lets you choose status \n   or help) is now implemented (but only lists status and help, of course),\n   as it makes use of that feature that took the main chunk of the Outreachy\n   project: the function to determine unique prefixes of a list of strings.\n   \n   \n * Speaking of the unique prefixes: the functionality to determine those is\n   now encapsulated in the prefix-map.c file, and I also added a regression\n   test.\n   \n   \n * Speaking of the tests: I also implemented support for the environment\n   variable GIT_TEST_ADD_I_USE_BUILTIN: by setting it, the test suite can be\n   forced to use the built-in, or the Perl script, version of git add -i.\n   Needless to say: by the end of this patch series, running the test suite\n   with GIT_TEST_ADD_I_USE_BUILTIN=true will still result in a ton of test\n   failures due to not-yet-implemented commands, but it will also\n   demonstrate what already works.\n   \n   \n * Since the main loop starts not only by showing the status, but refreshes\n   the index before that, I added that, and I actually refactored that code\n   into a new function (repo_refresh_and_write_index()), as it will be used\n   a couple of times by the end of the complete conversion of git add -i \n   into a built-in command.\n\nDaniel Ferreira (2):\n  diff: export diffstat interface\n  built-in add -i: implement the `status` command\n\nJohannes Schindelin (6):\n  Start to implement a built-in version of `git add --interactive`\n  built-in add -i: refresh the index before running `status`\n  built-in add -i: color the header in the `status` command\n  built-in add -i: implement the main loop\n  built-in add -i: support `?` (prompt help)\n  built-in add -i: implement the `help` command\n\nSlavica Djukic (3):\n  Add a function to determine unique prefixes for a list of strings\n  built-in add -i: show unique prefixes of the commands\n  built-in add -i: use color in the main loop\n\n Documentation/config/add.txt |   5 +\n Makefile                     |   3 +\n add-interactive.c            | 560 +++++++++++++++++++++++++++++++++++\n add-interactive.h            |  10 +\n builtin/add.c                |  16 +-\n diff.c                       |  37 +--\n diff.h                       |  19 ++\n prefix-map.c                 | 111 +++++++\n prefix-map.h                 |  40 +++\n repository.c                 |  19 ++\n repository.h                 |   7 +\n t/README                     |   4 +\n t/helper/test-prefix-map.c   |  58 ++++\n t/helper/test-tool.c         |   1 +\n t/helper/test-tool.h         |   1 +\n t/t0016-prefix-map.sh        |  10 +\n t/t3701-add-interactive.sh   |  24 ++\n 17 files changed, 902 insertions(+), 23 deletions(-)\n create mode 100644 add-interactive.c\n create mode 100644 add-interactive.h\n create mode 100644 prefix-map.c\n create mode 100644 prefix-map.h\n create mode 100644 t/helper/test-prefix-map.c\n create mode 100755 t/t0016-prefix-map.sh\n\n\nbase-commit: 8104ec994ea3849a968b4667d072fedd1e688642\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/170\n-- \ngitgitgadget\n"},{"id":"373595","messageId":"12978dc248a2cd07c90559691b8a2add84f45394.1554917868.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.git.gitgitgadget@gmail.com","subject":"[PATCH 01/11] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-04-10T17:37:50Z","receivedAt":"2019-04-10T17:37:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis is hardly the first conversion of a Git command that is implemented\nas a script to a built-in. So far, the most successful strategy for such\nconversions has been to add a built-in helper and call that for more and\nmore functionality from the script, as more and more parts are\nconverted.\n\nWith the interactive add, we choose a different strategy. The sole\nreason for this is that on Windows (where such a conversion has the most\nbenefits in terms of speed and robustness) we face the very specific\nproblem that a `system()` call in Perl seems to close `stdin` in the\nparent process when the spawned process consumes even one character from\n`stdin`. And that just does not work for us here, as it would stop the\nmain loop as soon as any interactive command was performed by the\nhelper. Which is almost all of the commands in `git add -i`.\n\nIt is almost as if Perl told us once again that it does not want us to\nuse it on Windows.\n\nInstead, we follow the opposite route where we start with a bare-bones\nversion of the built-in interactive add, guarded by the new\n`add.interactive.useBuiltin` config variable, and then add more and more\nfunctionality to it, until it is feature complete.\n\nAt this point, the built-in version of `git add -i` only states that it\ncannot do anything yet ;-)\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config/add.txt |  5 +++++\n Makefile                     |  1 +\n add-interactive.c            | 13 +++++++++++++\n add-interactive.h            | 10 ++++++++++\n builtin/add.c                | 16 +++++++++++++++-\n t/README                     |  4 ++++\n 6 files changed, 48 insertions(+), 1 deletion(-)\n create mode 100644 add-interactive.c\n create mode 100644 add-interactive.h\n\ndiff --git a/Documentation/config/add.txt b/Documentation/config/add.txt\nindex 4d753f006e..c9f748f81c 100644\n--- a/Documentation/config/add.txt\n+++ b/Documentation/config/add.txt\n@@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::\n \toption of linkgit:git-add[1].  `add.ignore-errors` is deprecated,\n \tas it does not follow the usual naming convention for configuration\n \tvariables.\n+\n+add.interactive.useBuiltin::\n+\t[EXPERIMENTAL] Set to `true` to use the experimental built-in\n+\timplementation of the interactive version of linkgit:git-add[1]\n+\tinstead of the Perl script version. Is `false` by default.\ndiff --git a/Makefile b/Makefile\nindex c5240942f2..18e656a32f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -848,6 +848,7 @@ LIB_H = $(shell $(FIND) . \\\n \t-name '*.h' -print)\n \n LIB_OBJS += abspath.o\n+LIB_OBJS += add-interactive.o\n LIB_OBJS += advice.o\n LIB_OBJS += alias.o\n LIB_OBJS += alloc.o\ndiff --git a/add-interactive.c b/add-interactive.c\nnew file mode 100644\nindex 0000000000..540bf185d8\n--- /dev/null\n+++ b/add-interactive.c\n@@ -0,0 +1,13 @@\n+#include \"cache.h\"\n+#include \"add-interactive.h\"\n+#include \"config.h\"\n+\n+int add_i_config(const char *var, const char *value, void *cb)\n+{\n+\treturn git_default_config(var, value, cb);\n+}\n+\n+int run_add_i(struct repository *r, const struct pathspec *ps)\n+{\n+\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n+}\ndiff --git a/add-interactive.h b/add-interactive.h\nnew file mode 100644\nindex 0000000000..e6e6e051eb\n--- /dev/null\n+++ b/add-interactive.h\n@@ -0,0 +1,10 @@\n+#ifndef ADD_INTERACTIVE_H\n+#define ADD_INTERACTIVE_H\n+\n+int add_i_config(const char *var, const char *value, void *cb);\n+\n+struct repository;\n+struct pathspec;\n+int run_add_i(struct repository *r, const struct pathspec *ps);\n+\n+#endif\ndiff --git a/builtin/add.c b/builtin/add.c\nindex db2dfa4350..5a32a755c8 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -20,6 +20,7 @@\n #include \"bulk-checkin.h\"\n #include \"argv-array.h\"\n #include \"submodule.h\"\n+#include \"add-interactive.h\"\n \n static const char * const builtin_add_usage[] = {\n \tN_(\"git add [<options>] [--] <pathspec>...\"),\n@@ -28,6 +29,7 @@ static const char * const builtin_add_usage[] = {\n static int patch_interactive, add_interactive, edit_interactive;\n static int take_worktree_changes;\n static int add_renormalize;\n+static int use_builtin_add_i;\n \n struct update_callback_data {\n \tint flags;\n@@ -186,6 +188,9 @@ int run_add_interactive(const char *revision, const char *patch_mode,\n \tint status, i;\n \tstruct argv_array argv = ARGV_ARRAY_INIT;\n \n+\tif (use_builtin_add_i && !patch_mode)\n+\t\treturn !!run_add_i(the_repository, pathspec);\n+\n \targv_array_push(&argv, \"add--interactive\");\n \tif (patch_mode)\n \t\targv_array_push(&argv, patch_mode);\n@@ -319,7 +324,12 @@ static int add_config(const char *var, const char *value, void *cb)\n \t\tignore_add_errors = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n-\treturn git_default_config(var, value, cb);\n+\tif (!strcmp(var, \"add.interactive.usebuiltin\")) {\n+\t\tuse_builtin_add_i = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n+\treturn add_i_config(var, value, cb);\n }\n \n static const char embedded_advice[] = N_(\n@@ -394,8 +404,12 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \tint require_pathspec;\n \tchar *seen = NULL;\n \tstruct lock_file lock_file = LOCK_INIT;\n+\tint use_builtin_add_i_env =\n+\t\tgit_env_bool(\"GIT_TEST_ADD_I_USE_BUILTIN\", -1);\n \n \tgit_config(add_config, NULL);\n+\tif (use_builtin_add_i_env >= 0)\n+\t\tuse_builtin_add_i = use_builtin_add_i_env;\n \n \targc = parse_options(argc, argv, prefix, builtin_add_options,\n \t\t\t  builtin_add_usage, PARSE_OPT_KEEP_ARGV0);\ndiff --git a/t/README b/t/README\nindex 886bbec5bc..6408a1847e 100644\n--- a/t/README\n+++ b/t/README\n@@ -383,6 +383,10 @@ GIT_TEST_REBASE_USE_BUILTIN=<boolean>, when false, disables the\n builtin version of git-rebase. See 'rebase.useBuiltin' in\n git-config(1).\n \n+GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the\n+builtin version of git add -i. See 'add.interactive.useBuiltin' in\n+git-config(1).\n+\n GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading\n of the index for the whole test suite by bypassing the default number of\n cache entries and thread minimums. Setting this to 1 will make the\n-- \ngitgitgadget\n\n"},{"id":"373596","messageId":"06ba1ae34462c201b19b617ee23e76886928b387.1554917868.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.git.gitgitgadget@gmail.com","subject":"[PATCH 02/11] diff: export diffstat interface","fromName":"Daniel Ferreira via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-04-10T17:37:51Z","receivedAt":"2019-04-10T17:37:56Z","isPatch":true,"sender":{"key":"bnmvco@gmail.com","avatar":"https://avatars.githubusercontent.com/u/735397?v=4"},"body":"From: Daniel Ferreira <bnmvco@gmail.com>\n\nMake the diffstat interface (namely, the diffstat_t struct and\ncompute_diffstat) no longer be internal to diff.c and allow it to be used\nby other parts of git.\n\nThis is helpful for code that may want to easily extract information\nfrom files using the diff machinery, while flushing it differently from\nhow the show_* functions used by diff_flush() do it. One example is the\nbuiltin implementation of git-add--interactive's status.\n\nSigned-off-by: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 37 +++++++++++++++----------------------\n diff.h | 19 +++++++++++++++++++\n 2 files changed, 34 insertions(+), 22 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 5306c48652..daa5f3a736 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -2489,22 +2489,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)\n \t}\n }\n \n-struct diffstat_t {\n-\tint nr;\n-\tint alloc;\n-\tstruct diffstat_file {\n-\t\tchar *from_name;\n-\t\tchar *name;\n-\t\tchar *print_name;\n-\t\tconst char *comments;\n-\t\tunsigned is_unmerged:1;\n-\t\tunsigned is_binary:1;\n-\t\tunsigned is_renamed:1;\n-\t\tunsigned is_interesting:1;\n-\t\tuintmax_t added, deleted;\n-\t} **files;\n-};\n-\n static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,\n \t\t\t\t\t  const char *name_a,\n \t\t\t\t\t  const char *name_b)\n@@ -6001,12 +5985,7 @@ void diff_flush(struct diff_options *options)\n \t    dirstat_by_line) {\n \t\tstruct diffstat_t diffstat;\n \n-\t\tmemset(&diffstat, 0, sizeof(struct diffstat_t));\n-\t\tfor (i = 0; i < q->nr; i++) {\n-\t\t\tstruct diff_filepair *p = q->queue[i];\n-\t\t\tif (check_pair_status(p))\n-\t\t\t\tdiff_flush_stat(p, options, &diffstat);\n-\t\t}\n+\t\tcompute_diffstat(options, &diffstat, q);\n \t\tif (output_format & DIFF_FORMAT_NUMSTAT)\n \t\t\tshow_numstat(&diffstat, options);\n \t\tif (output_format & DIFF_FORMAT_DIFFSTAT)\n@@ -6306,6 +6285,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)\n \treturn ignored;\n }\n \n+void compute_diffstat(struct diff_options *options,\n+\t\t      struct diffstat_t *diffstat,\n+\t\t      struct diff_queue_struct *q)\n+{\n+\tint i;\n+\n+\tmemset(diffstat, 0, sizeof(struct diffstat_t));\n+\tfor (i = 0; i < q->nr; i++) {\n+\t\tstruct diff_filepair *p = q->queue[i];\n+\t\tif (check_pair_status(p))\n+\t\t\tdiff_flush_stat(p, options, diffstat);\n+\t}\n+}\n+\n void diff_addremove(struct diff_options *options,\n \t\t    int addremove, unsigned mode,\n \t\t    const struct object_id *oid,\ndiff --git a/diff.h b/diff.h\nindex b512d0477a..ae9bedfab8 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -240,6 +240,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);\n void diff_emit_submodule_pipethrough(struct diff_options *o,\n \t\t\t\t     const char *line, int len);\n \n+struct diffstat_t {\n+\tint nr;\n+\tint alloc;\n+\tstruct diffstat_file {\n+\t\tchar *from_name;\n+\t\tchar *name;\n+\t\tchar *print_name;\n+\t\tconst char *comments;\n+\t\tunsigned is_unmerged:1;\n+\t\tunsigned is_binary:1;\n+\t\tunsigned is_renamed:1;\n+\t\tunsigned is_interesting:1;\n+\t\tuintmax_t added, deleted;\n+\t} **files;\n+};\n+\n enum color_diff {\n \tDIFF_RESET = 0,\n \tDIFF_CONTEXT = 1,\n@@ -328,6 +344,9 @@ void diff_change(struct diff_options *,\n \n struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);\n \n+void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,\n+\t\t      struct diff_queue_struct *q);\n+\n #define DIFF_SETUP_REVERSE      \t1\n #define DIFF_SETUP_USE_SIZE_CACHE\t4\n \n-- \ngitgitgadget\n\n"},{"id":"373597","messageId":"a512e14609e27e483f79c9e6cda007f88ec7a6dc.1554917868.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.git.gitgitgadget@gmail.com","subject":"[PATCH 04/11] built-in add -i: refresh the index before running `status`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-04-10T17:37:53Z","receivedAt":"2019-04-10T17:37:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis is what the Perl version does, and therefore it is what the\nbuilt-in version should do, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c |  4 +++-\n repository.c      | 19 +++++++++++++++++++\n repository.h      |  7 +++++++\n 3 files changed, 29 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex f627a56eeb..d971b58552 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -264,7 +264,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n \topts.header = header.buf;\n \n-\tres = run_status(r, ps, &files, &opts);\n+\trepo_refresh_and_write_index(r, REFRESH_QUIET, 1);\n+\tif (run_status(r, ps, &files, &opts) < 0)\n+\t\tres = -1;\n \n \trelease_file_list(&files);\n \tstrbuf_release(&print_file_item_data.buf);\ndiff --git a/repository.c b/repository.c\nindex 65e6f8b8fd..c90f310093 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -272,3 +272,22 @@ int repo_hold_locked_index(struct repository *repo,\n \t\tBUG(\"the repo hasn't been setup\");\n \treturn hold_lock_file_for_update(lf, repo->index_file, flags);\n }\n+\n+int repo_refresh_and_write_index(struct repository *r,\n+\t\t\t\t unsigned int flags, int gentle)\n+{\n+\tstruct lock_file lock_file = LOCK_INIT;\n+\tint fd;\n+\n+\tif (repo_read_index_preload(r, NULL, 0) < 0)\n+\t\treturn error(_(\"could not read index\"));\n+\tfd = repo_hold_locked_index(r, &lock_file, 0);\n+\tif (!gentle && fd < 0)\n+\t\treturn error(_(\"could not lock index for writing\"));\n+\trefresh_index(r->index, flags, NULL, NULL, NULL);\n+\tif (0 <= fd)\n+\t\trepo_update_index_if_able(r, &lock_file);\n+\trollback_lock_file(&lock_file);\n+\n+\treturn 0;\n+}\ndiff --git a/repository.h b/repository.h\nindex 8981649d43..fb49e0e328 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -154,5 +154,12 @@ int repo_read_index_unmerged(struct repository *);\n  */\n void repo_update_index_if_able(struct repository *, struct lock_file *);\n \n+/*\n+ * Refresh the index and write it out. If the index file could not be\n+ * locked, error out, except in gentle mode. The flags will be passed\n+ * through to refresh_index().\n+ */\n+int repo_refresh_and_write_index(struct repository *r,\n+\t\t\t\t unsigned int flags, int gentle);\n \n #endif /* REPOSITORY_H */\n-- \ngitgitgadget\n\n"},{"id":"373598","messageId":"be9ab904d111b27083e66d6dd696d4e0c5e479c3.1554917868.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.git.gitgitgadget@gmail.com","subject":"[PATCH 03/11] built-in add -i: implement the `status` command","fromName":"Daniel Ferreira via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-04-10T17:37:52Z","receivedAt":"2019-04-10T17:37:58Z","isPatch":true,"sender":{"key":"bnmvco@gmail.com","avatar":"https://avatars.githubusercontent.com/u/735397?v=4"},"body":"From: Daniel Ferreira <bnmvco@gmail.com>\n\nThis implements the `status` command of `git add -i`. The data\nstructures introduced in this commit will be extended as needed later.\n\nAt this point, we re-implement only part of the `list_and_choose()`\nfunction of the Perl script `git-add--interactive.perl` and call it\n`list()`. It does not yet color anything, or do columns, or allow user\ninput.\n\nOver the course of the next commits, we will introduce a\n`list_and_choose()` function that uses `list()` to display the list of\noptions and let the user choose one or more of the displayed items. This\nwill be used to implement the main loop of the built-in `git add -i`, at\nwhich point the new `status` command can actually be used.\n\nNote that we pass the list of items as a `struct item **` as opposed to\na `struct item *`, to allow for the actual items to contain much more\ninformation than merely the name.\n\nSigned-off-by: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 265 +++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 264 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 540bf185d8..f627a56eeb 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -1,13 +1,276 @@\n #include \"cache.h\"\n #include \"add-interactive.h\"\n #include \"config.h\"\n+#include \"diffcore.h\"\n+#include \"revision.h\"\n+#include \"refs.h\"\n \n int add_i_config(const char *var, const char *value, void *cb)\n {\n \treturn git_default_config(var, value, cb);\n }\n \n+struct item {\n+\tconst char *name;\n+};\n+\n+struct list_options {\n+\tconst char *header;\n+\tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n+\tvoid *print_item_data;\n+};\n+\n+static void list(struct item **list, size_t nr, struct list_options *opts)\n+{\n+\tint i;\n+\n+\tif (!nr)\n+\t\treturn;\n+\n+\tif (opts->header)\n+\t\tprintf(\"%s\\n\", opts->header);\n+\n+\tfor (i = 0; i < nr; i++) {\n+\t\topts->print_item(i, list[i], opts->print_item_data);\n+\t\tputchar('\\n');\n+\t}\n+}\n+\n+struct adddel {\n+\tuintmax_t add, del;\n+\tunsigned seen:1, binary:1;\n+};\n+\n+struct file_list {\n+\tstruct file_item {\n+\t\tstruct item item;\n+\t\tstruct adddel index, worktree;\n+\t} **file;\n+\tsize_t nr, alloc;\n+};\n+\n+static void add_file_item(struct file_list *list, const char *name)\n+{\n+\tstruct file_item *item;\n+\n+\tFLEXPTR_ALLOC_STR(item, item.name, name);\n+\n+\tALLOC_GROW(list->file, list->nr + 1, list->alloc);\n+\tlist->file[list->nr++] = item;\n+}\n+\n+static void reset_file_list(struct file_list *list)\n+{\n+\tsize_t i;\n+\n+\tfor (i = 0; i < list->nr; i++)\n+\t\tfree(list->file[i]);\n+\tlist->nr = 0;\n+}\n+\n+static void release_file_list(struct file_list *list)\n+{\n+\treset_file_list(list);\n+\tFREE_AND_NULL(list->file);\n+\tlist->alloc = 0;\n+}\n+\n+static int file_item_cmp(const void *a, const void *b)\n+{\n+\tconst struct file_item * const *f1 = a;\n+\tconst struct file_item * const *f2 = b;\n+\n+\treturn strcmp((*f1)->item.name, (*f2)->item.name);\n+}\n+\n+struct pathname_entry {\n+\tstruct hashmap_entry ent;\n+\tsize_t index;\n+\tchar pathname[FLEX_ARRAY];\n+};\n+\n+static int pathname_entry_cmp(const void *unused_cmp_data,\n+\t\t\t      const void *entry, const void *entry_or_key,\n+\t\t\t      const void *pathname)\n+{\n+\tconst struct pathname_entry *e1 = entry, *e2 = entry_or_key;\n+\n+\treturn strcmp(e1->pathname,\n+\t\t      pathname ? (const char *)pathname : e2->pathname);\n+}\n+\n+struct collection_status {\n+\tenum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase;\n+\n+\tconst char *reference;\n+\n+\tstruct file_list *list;\n+\tstruct hashmap file_map;\n+};\n+\n+static void collect_changes_cb(struct diff_queue_struct *q,\n+\t\t\t       struct diff_options *options,\n+\t\t\t       void *data)\n+{\n+\tstruct collection_status *s = data;\n+\tstruct diffstat_t stat = { 0 };\n+\tint i;\n+\n+\tif (!q->nr)\n+\t\treturn;\n+\n+\tcompute_diffstat(options, &stat, q);\n+\n+\tfor (i = 0; i < stat.nr; i++) {\n+\t\tconst char *name = stat.files[i]->name;\n+\t\tint hash = strhash(name);\n+\t\tstruct pathname_entry *entry;\n+\t\tsize_t file_index;\n+\t\tstruct file_item *file;\n+\t\tstruct adddel *adddel;\n+\n+\t\tentry = hashmap_get_from_hash(&s->file_map, hash, name);\n+\t\tif (entry)\n+\t\t\tfile_index = entry->index;\n+\t\telse {\n+\t\t\tFLEX_ALLOC_STR(entry, pathname, name);\n+\t\t\thashmap_entry_init(entry, hash);\n+\t\t\tentry->index = file_index = s->list->nr;\n+\t\t\thashmap_add(&s->file_map, entry);\n+\n+\t\t\tadd_file_item(s->list, name);\n+\t\t}\n+\t\tfile = s->list->file[file_index];\n+\n+\t\tadddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;\n+\t\tadddel->seen = 1;\n+\t\tadddel->add = stat.files[i]->added;\n+\t\tadddel->del = stat.files[i]->deleted;\n+\t\tif (stat.files[i]->is_binary)\n+\t\t\tadddel->binary = 1;\n+\t}\n+}\n+\n+static int get_modified_files(struct repository *r, struct file_list *list,\n+\t\t\t      const struct pathspec *ps)\n+{\n+\tstruct object_id head_oid;\n+\tint is_initial = !resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING,\n+\t\t\t\t\t     &head_oid, NULL);\n+\tstruct collection_status s = { FROM_WORKTREE };\n+\n+\tif (repo_read_index_preload(r, ps, 0) < 0)\n+\t\treturn error(_(\"could not read index\"));\n+\n+\ts.list = list;\n+\thashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);\n+\n+\tfor (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {\n+\t\tstruct rev_info rev;\n+\t\tstruct setup_revision_opt opt = { 0 };\n+\n+\t\topt.def = is_initial ?\n+\t\t\tempty_tree_oid_hex() : oid_to_hex(&head_oid);\n+\n+\t\tinit_revisions(&rev, NULL);\n+\t\tsetup_revisions(0, NULL, &rev, &opt);\n+\n+\t\trev.diffopt.output_format = DIFF_FORMAT_CALLBACK;\n+\t\trev.diffopt.format_callback = collect_changes_cb;\n+\t\trev.diffopt.format_callback_data = &s;\n+\n+\t\tif (ps)\n+\t\t\tcopy_pathspec(&rev.prune_data, ps);\n+\n+\t\tif (s.phase == FROM_INDEX)\n+\t\t\trun_diff_index(&rev, 1);\n+\t\telse {\n+\t\t\trev.diffopt.flags.ignore_dirty_submodules = 1;\n+\t\t\trun_diff_files(&rev, 0);\n+\t\t}\n+\t}\n+\thashmap_free(&s.file_map, 1);\n+\n+\t/* While the diffs are ordered already, we ran *two* diffs... */\n+\tQSORT(list->file, list->nr, file_item_cmp);\n+\n+\treturn 0;\n+}\n+\n+static void populate_wi_changes(struct strbuf *buf,\n+\t\t\t\tstruct adddel *ad, const char *no_changes)\n+{\n+\tif (ad->binary)\n+\t\tstrbuf_addstr(buf, _(\"binary\"));\n+\telse if (ad->seen)\n+\t\tstrbuf_addf(buf, \"+%\"PRIuMAX\"/-%\"PRIuMAX,\n+\t\t\t    (uintmax_t)ad->add, (uintmax_t)ad->del);\n+\telse\n+\t\tstrbuf_addstr(buf, no_changes);\n+}\n+\n+struct print_file_item_data {\n+\tconst char *modified_fmt;\n+\tstruct strbuf buf, index, worktree;\n+};\n+\n+static void print_file_item(int i, struct item *item,\n+\t\t\t    void *print_file_item_data)\n+{\n+\tstruct file_item *c = (struct file_item *)item;\n+\tstruct print_file_item_data *d = print_file_item_data;\n+\n+\tstrbuf_reset(&d->index);\n+\tstrbuf_reset(&d->worktree);\n+\tstrbuf_reset(&d->buf);\n+\n+\tpopulate_wi_changes(&d->worktree, &c->worktree, _(\"nothing\"));\n+\tpopulate_wi_changes(&d->index, &c->index, _(\"unchanged\"));\n+\tstrbuf_addf(&d->buf, d->modified_fmt,\n+\t\t    d->index.buf, d->worktree.buf, item->name);\n+\n+\tprintf(\" %2d: %s\", i + 1, d->buf.buf);\n+}\n+\n+static int run_status(struct repository *r, const struct pathspec *ps,\n+\t\t      struct file_list *files, struct list_options *opts)\n+{\n+\treset_file_list(files);\n+\n+\tif (get_modified_files(r, files, ps) < 0)\n+\t\treturn -1;\n+\n+\tif (files->nr)\n+\t\tlist((struct item **)files->file, files->nr, opts);\n+\tputchar('\\n');\n+\n+\treturn 0;\n+}\n+\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n-\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n+\tstruct print_file_item_data print_file_item_data = {\n+\t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n+\t};\n+\tstruct list_options opts = {\n+\t\tNULL, print_file_item, &print_file_item_data\n+\t};\n+\tstruct strbuf header = STRBUF_INIT;\n+\tstruct file_list files = { NULL };\n+\tint res = 0;\n+\n+\tstrbuf_addstr(&header, \"      \");\n+\tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n+\t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n+\topts.header = header.buf;\n+\n+\tres = run_status(r, ps, &files, &opts);\n+\n+\trelease_file_list(&files);\n+\tstrbuf_release(&print_file_item_data.buf);\n+\tstrbuf_release(&print_file_item_data.index);\n+\tstrbuf_release(&print_file_item_data.worktree);\n+\tstrbuf_release(&header);\n+\n+\treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"373599","messageId":"f7c8df058c3c84d7c98d64b08fc43b8325b994e6.1554917868.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.git.gitgitgadget@gmail.com","subject":"[PATCH 05/11] built-in add -i: color the header in the `status` command","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-04-10T17:37:54Z","receivedAt":"2019-04-10T17:38:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nFor simplicity, we only implemented the `status` command without colors.\nThis patch starts adding color, matching what the Perl script\n`git-add--interactive.perl` does.\n\nOriginal-Patch-By: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 51 ++++++++++++++++++++++++++++++++++++++++++++---\n 1 file changed, 48 insertions(+), 3 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex d971b58552..79adc58321 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -1,13 +1,56 @@\n #include \"cache.h\"\n #include \"add-interactive.h\"\n #include \"config.h\"\n+#include \"color.h\"\n+#include \"config.h\"\n #include \"diffcore.h\"\n #include \"revision.h\"\n #include \"refs.h\"\n \n+static int use_color = -1;\n+\n+enum color_add_i {\n+\tCOLOR_HEADER = 0,\n+};\n+\n+static char list_colors[][COLOR_MAXLEN] = {\n+\tGIT_COLOR_BOLD,      /* Header */\n+};\n+\n+static const char *get_add_i_color(enum color_add_i ix)\n+{\n+\tif (want_color(use_color))\n+\t\treturn list_colors[ix];\n+\treturn \"\";\n+}\n+\n+static int parse_color_slot(const char *slot)\n+{\n+\tif (!strcasecmp(slot, \"header\"))\n+\t\treturn COLOR_HEADER;\n+\n+\treturn -1;\n+}\n+\n int add_i_config(const char *var, const char *value, void *cb)\n {\n-\treturn git_default_config(var, value, cb);\n+\tconst char *name;\n+\n+\tif (!strcmp(var, \"color.interactive\")) {\n+\t\tuse_color = git_config_colorbool(var, value);\n+\t\treturn 0;\n+\t}\n+\n+\tif (skip_prefix(var, \"color.interactive.\", &name)) {\n+\t\tint slot = parse_color_slot(name);\n+\t\tif (slot < 0)\n+\t\t\treturn 0;\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\treturn color_parse(value, list_colors[slot]);\n+\t}\n+\n+\treturn git_color_default_config(var, value, cb);\n }\n \n struct item {\n@@ -27,8 +70,10 @@ static void list(struct item **list, size_t nr, struct list_options *opts)\n \tif (!nr)\n \t\treturn;\n \n-\tif (opts->header)\n-\t\tprintf(\"%s\\n\", opts->header);\n+\tif (opts->header) {\n+\t\tconst char *header_color = get_add_i_color(COLOR_HEADER);\n+\t\tcolor_fprintf_ln(stdout, header_color, \"%s\", opts->header);\n+\t}\n \n \tfor (i = 0; i < nr; i++) {\n \t\topts->print_item(i, list[i], opts->print_item_data);\n-- \ngitgitgadget\n\n"},{"id":"373600","messageId":"93b3151b6c8abeeab0674919badae72e39eea68d.1554917868.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.git.gitgitgadget@gmail.com","subject":"[PATCH 06/11] built-in add -i: implement the main loop","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-04-10T17:37:55Z","receivedAt":"2019-04-10T17:38:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe reason why we did not start with the main loop to begin with is that\nit is the first user of `list_and_choose()`, which uses the `list()`\nfunction that we conveniently introduced for use by the `status`\ncommand.\n\nApart from the \"and choose\" part, there are more differences between the\nway the `status` command calls the `list_and_choose()` function in the\nPerl version of `git add -i` compared to the other callers of said\nfunction. The most important ones:\n\n- The list is not only shown, but the user is also asked to make a\n  choice, possibly selecting multiple entries.\n\n- The list of items is prefixed with a marker indicating what items have\n  been selected, if multi-selection is allowed.\n\n- Initially, for each item a unique prefix (if there exists any within\n  the given parameters) is determined, and shown in the list, and\n  accepted as a shortcut for the selection.\n\nThese features will be implemented later, except the part where the user\ncan choose a command. At this stage, though, the built-in `git add -i`\nstill only supports the `status` command, with the remaining commands to\nfollow over the course of the next commits.\n\nIn addition, we also modify `list()` to support displaying the commands\nin columns, even if there is currently only one.\n\nThe Perl script `git-add--interactive.perl` mixed the purposes of the\n\"list\" and the \"and choose\" part into the same function. In the C\nversion, we will keep them separate instead, calling the `list()`\nfunction from the `list_and_choose()` function.\n\nNote that we only have a prompt ending in a single \">\" at this stage;\nlater commits will add commands that display a double \">>\" to indicate\nthat the user is in a different loop than the main one.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 122 +++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 120 insertions(+), 2 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 79adc58321..c8bd62369e 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -58,6 +58,7 @@ struct item {\n };\n \n struct list_options {\n+\tint columns;\n \tconst char *header;\n \tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n \tvoid *print_item_data;\n@@ -65,7 +66,7 @@ struct list_options {\n \n static void list(struct item **list, size_t nr, struct list_options *opts)\n {\n-\tint i;\n+\tint i, last_lf = 0;\n \n \tif (!nr)\n \t\treturn;\n@@ -77,8 +78,90 @@ static void list(struct item **list, size_t nr, struct list_options *opts)\n \n \tfor (i = 0; i < nr; i++) {\n \t\topts->print_item(i, list[i], opts->print_item_data);\n+\n+\t\tif ((opts->columns) && ((i + 1) % (opts->columns))) {\n+\t\t\tputchar('\\t');\n+\t\t\tlast_lf = 0;\n+\t\t}\n+\t\telse {\n+\t\t\tputchar('\\n');\n+\t\t\tlast_lf = 1;\n+\t\t}\n+\t}\n+\n+\tif (!last_lf)\n \t\tputchar('\\n');\n+}\n+struct list_and_choose_options {\n+\tstruct list_options list_opts;\n+\n+\tconst char *prompt;\n+};\n+\n+/*\n+ * Returns the selected index.\n+ */\n+static ssize_t list_and_choose(struct item **items, size_t nr,\n+\t\t\t       struct list_and_choose_options *opts)\n+{\n+\tstruct strbuf input = STRBUF_INIT;\n+\tssize_t res = -1;\n+\n+\tfor (;;) {\n+\t\tchar *p, *endp;\n+\n+\t\tstrbuf_reset(&input);\n+\n+\t\tlist(items, nr, &opts->list_opts);\n+\n+\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n+\t\tfflush(stdout);\n+\n+\t\tif (strbuf_getline(&input, stdin) == EOF) {\n+\t\t\tputchar('\\n');\n+\t\t\tres = -2;\n+\t\t\tbreak;\n+\t\t}\n+\t\tstrbuf_trim(&input);\n+\n+\t\tif (!input.len)\n+\t\t\tbreak;\n+\n+\t\tp = input.buf;\n+\t\tfor (;;) {\n+\t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n+\t\t\tssize_t index = -1;\n+\n+\t\t\tif (!sep) {\n+\t\t\t\tif (!*p)\n+\t\t\t\t\tbreak;\n+\t\t\t\tp++;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (isdigit(*p)) {\n+\t\t\t\tindex = strtoul(p, &endp, 10) - 1;\n+\t\t\t\tif (endp != p + sep)\n+\t\t\t\t\tindex = -1;\n+\t\t\t}\n+\n+\t\t\tp[sep] = '\\0';\n+\t\t\tif (index < 0 || index >= nr)\n+\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n+\t\t\telse {\n+\t\t\t\tres = index;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tp += sep + 1;\n+\t\t}\n+\n+\t\tif (res >= 0)\n+\t\t\tbreak;\n \t}\n+\n+\tstrbuf_release(&input);\n+\treturn res;\n }\n \n struct adddel {\n@@ -292,16 +375,39 @@ static int run_status(struct repository *r, const struct pathspec *ps,\n \treturn 0;\n }\n \n+static void print_command_item(int i, struct item *item,\n+\t\t\t       void *print_command_item_data)\n+{\n+\tprintf(\" %2d: %s\", i + 1, item->name);\n+}\n+\n+struct command_item {\n+\tstruct item item;\n+\tint (*command)(struct repository *r, const struct pathspec *ps,\n+\t\t       struct file_list *files, struct list_options *opts);\n+};\n+\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n+\tstruct list_and_choose_options main_loop_opts = {\n+\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n+\t\tN_(\"What now\")\n+\t};\n+\tstruct command_item\n+\t\tstatus = { { \"status\" }, run_status };\n+\tstruct command_item *commands[] = {\n+\t\t&status\n+\t};\n+\n \tstruct print_file_item_data print_file_item_data = {\n \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n \t};\n \tstruct list_options opts = {\n-\t\tNULL, print_file_item, &print_file_item_data\n+\t\t0, NULL, print_file_item, &print_file_item_data\n \t};\n \tstruct strbuf header = STRBUF_INIT;\n \tstruct file_list files = { NULL };\n+\tssize_t i;\n \tint res = 0;\n \n \tstrbuf_addstr(&header, \"      \");\n@@ -313,6 +419,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tif (run_status(r, ps, &files, &opts) < 0)\n \t\tres = -1;\n \n+\tfor (;;) {\n+\t\ti = list_and_choose((struct item **)commands,\n+\t\t\t\t    ARRAY_SIZE(commands), &main_loop_opts);\n+\t\tif (i < -1) {\n+\t\t\tprintf(_(\"Bye.\\n\"));\n+\t\t\tres = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (i >= 0)\n+\t\t\tres = commands[i]->command(r, ps, &files, &opts);\n+\t}\n+\n \trelease_file_list(&files);\n \tstrbuf_release(&print_file_item_data.buf);\n \tstrbuf_release(&print_file_item_data.index);\n-- \ngitgitgadget\n\n"},{"id":"373601","messageId":"ddd0ee51f68ef98f4d6a57353d28d6bfaff445f2.1554917868.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.git.gitgitgadget@gmail.com","subject":"[PATCH 09/11] built-in add -i: support `?` (prompt help)","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-04-10T17:37:57Z","receivedAt":"2019-04-10T17:38:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWith this change, we print out the same colored help text that the\nPerl-based `git add -i` prints in the main loop when question mark is\nentered.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 24 +++++++++++++++++++++++-\n 1 file changed, 23 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 62ce446dd9..03d0770013 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -12,10 +12,12 @@ static int use_color = -1;\n \n enum color_add_i {\n \tCOLOR_HEADER = 0,\n+\tCOLOR_HELP,\n };\n \n static char list_colors[][COLOR_MAXLEN] = {\n \tGIT_COLOR_BOLD,      /* Header */\n+\tGIT_COLOR_BOLD_RED,  /* Help */\n };\n \n static const char *get_add_i_color(enum color_add_i ix)\n@@ -29,6 +31,8 @@ static int parse_color_slot(const char *slot)\n {\n \tif (!strcasecmp(slot, \"header\"))\n \t\treturn COLOR_HEADER;\n+\tif (!strcasecmp(slot, \"help\"))\n+\t\treturn COLOR_HELP;\n \n \treturn -1;\n }\n@@ -112,6 +116,7 @@ struct list_and_choose_options {\n \tstruct list_options list_opts;\n \n \tconst char *prompt;\n+\tvoid (*print_help)(void);\n };\n \n /*\n@@ -145,6 +150,11 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \t\tif (!input.len)\n \t\t\tbreak;\n \n+\t\tif (!strcmp(input.buf, \"?\")) {\n+\t\t\topts->print_help();\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tp = input.buf;\n \t\tfor (;;) {\n \t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n@@ -431,11 +441,23 @@ struct command_item {\n \t\t       struct file_list *files, struct list_options *opts);\n };\n \n+static void command_prompt_help(void)\n+{\n+\tconst char *help_color = get_add_i_color(COLOR_HELP);\n+\tcolor_fprintf_ln(stdout, help_color, \"%s\", _(\"Prompt help:\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"1          - %s\",\n+\t\t\t _(\"select a numbered item\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"foo        - %s\",\n+\t\t\t _(\"select item based on unique prefix\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"           - %s\",\n+\t\t\t _(\"(empty) select nothing\"));\n+}\n+\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct list_and_choose_options main_loop_opts = {\n \t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n-\t\tN_(\"What now\")\n+\t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct command_item\n \t\tstatus = { { \"status\" }, run_status };\n-- \ngitgitgadget\n\n"},{"id":"373602","messageId":"56acc31e962e5985c7ad2119b7e6d86f7647e786.1554917868.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.git.gitgitgadget@gmail.com","subject":"[PATCH 08/11] built-in add -i: show unique prefixes of the commands","fromName":"Slavica Djukic via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-04-10T17:37:56Z","receivedAt":"2019-04-10T17:38:05Z","isPatch":true,"sender":{"key":"slavicadj.ip2018@gmail.com","avatar":null},"body":"From: Slavica Djukic <slawica92@hotmail.com>\n\nJust like in the Perl script `git-add--interactive.perl`, for each\ncommand a unique prefix is determined (if there exists any within the\ngiven parameters), and shown in the list, and accepted as a shortcut for\nthe command.\n\nWe use the prefix map implementation that we just added in the previous\ncommit for that purpose.\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n---\n add-interactive.c | 70 ++++++++++++++++++++++++++++++++++++++---------\n 1 file changed, 57 insertions(+), 13 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex c8bd62369e..62ce446dd9 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -6,6 +6,7 @@\n #include \"diffcore.h\"\n #include \"revision.h\"\n #include \"refs.h\"\n+#include \"prefix-map.h\"\n \n static int use_color = -1;\n \n@@ -53,18 +54,33 @@ int add_i_config(const char *var, const char *value, void *cb)\n \treturn git_color_default_config(var, value, cb);\n }\n \n-struct item {\n-\tconst char *name;\n-};\n+static ssize_t find_unique(const char *string,\n+\t\t\t   struct prefix_item **list, size_t nr)\n+{\n+\tssize_t found = -1, i;\n+\n+\tfor (i = 0; i < nr; i++) {\n+\t\tstruct prefix_item *item = list[i];\n+\t\tif (!starts_with(item->name, string))\n+\t\t\tcontinue;\n+\t\tif (found >= 0)\n+\t\t\treturn -1;\n+\t\tfound = i;\n+\t}\n+\n+\treturn found;\n+}\n \n struct list_options {\n \tint columns;\n \tconst char *header;\n-\tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n+\tvoid (*print_item)(int i, struct prefix_item *item,\n+\t\t\t   void *print_item_data);\n \tvoid *print_item_data;\n };\n \n-static void list(struct item **list, size_t nr, struct list_options *opts)\n+static void list(struct prefix_item **list, size_t nr,\n+\t\t struct list_options *opts)\n {\n \tint i, last_lf = 0;\n \n@@ -101,12 +117,14 @@ struct list_and_choose_options {\n /*\n  * Returns the selected index.\n  */\n-static ssize_t list_and_choose(struct item **items, size_t nr,\n+static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \t\t\t       struct list_and_choose_options *opts)\n {\n \tstruct strbuf input = STRBUF_INIT;\n \tssize_t res = -1;\n \n+\tfind_unique_prefixes(items, nr, 1, 4);\n+\n \tfor (;;) {\n \t\tchar *p, *endp;\n \n@@ -146,6 +164,9 @@ static ssize_t list_and_choose(struct item **items, size_t nr,\n \t\t\t}\n \n \t\t\tp[sep] = '\\0';\n+\t\t\tif (index < 0)\n+\t\t\t\tindex = find_unique(p, items, nr);\n+\n \t\t\tif (index < 0 || index >= nr)\n \t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n \t\t\telse {\n@@ -171,7 +192,7 @@ struct adddel {\n \n struct file_list {\n \tstruct file_item {\n-\t\tstruct item item;\n+\t\tstruct prefix_item item;\n \t\tstruct adddel index, worktree;\n \t} **file;\n \tsize_t nr, alloc;\n@@ -337,12 +358,29 @@ static void populate_wi_changes(struct strbuf *buf,\n \t\tstrbuf_addstr(buf, no_changes);\n }\n \n+/* filters out prefixes which have special meaning to list_and_choose() */\n+static int is_valid_prefix(const char *prefix, size_t prefix_len)\n+{\n+\treturn prefix_len && prefix &&\n+\t\t/*\n+\t\t * We expect `prefix` to be NUL terminated, therefore this\n+\t\t * `strcspn()` call is okay, even if it might do much more\n+\t\t * work than strictly necessary.\n+\t\t */\n+\t\tstrcspn(prefix, \" \\t\\r\\n,\") >= prefix_len &&\t/* separators */\n+\t\t*prefix != '-' &&\t\t\t\t/* deselection */\n+\t\t!isdigit(*prefix) &&\t\t\t\t/* selection */\n+\t\t(prefix_len != 1 ||\n+\t\t (*prefix != '*' &&\t\t\t\t/* \"all\" wildcard */\n+\t\t  *prefix != '?'));\t\t\t\t/* prompt help */\n+}\n+\n struct print_file_item_data {\n \tconst char *modified_fmt;\n \tstruct strbuf buf, index, worktree;\n };\n \n-static void print_file_item(int i, struct item *item,\n+static void print_file_item(int i, struct prefix_item *item,\n \t\t\t    void *print_file_item_data)\n {\n \tstruct file_item *c = (struct file_item *)item;\n@@ -369,20 +407,26 @@ static int run_status(struct repository *r, const struct pathspec *ps,\n \t\treturn -1;\n \n \tif (files->nr)\n-\t\tlist((struct item **)files->file, files->nr, opts);\n+\t\tlist((struct prefix_item **)files->file, files->nr, opts);\n \tputchar('\\n');\n \n \treturn 0;\n }\n \n-static void print_command_item(int i, struct item *item,\n+static void print_command_item(int i, struct prefix_item *item,\n \t\t\t       void *print_command_item_data)\n {\n-\tprintf(\" %2d: %s\", i + 1, item->name);\n+\tif (!item->prefix_length ||\n+\t    !is_valid_prefix(item->name, item->prefix_length))\n+\t\tprintf(\" %2d: %s\", i + 1, item->name);\n+\telse\n+\t\tprintf(\" %3d: [%.*s]%s\", i + 1,\n+\t\t       (int)item->prefix_length, item->name,\n+\t\t       item->name + item->prefix_length);\n }\n \n struct command_item {\n-\tstruct item item;\n+\tstruct prefix_item item;\n \tint (*command)(struct repository *r, const struct pathspec *ps,\n \t\t       struct file_list *files, struct list_options *opts);\n };\n@@ -420,7 +464,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\tres = -1;\n \n \tfor (;;) {\n-\t\ti = list_and_choose((struct item **)commands,\n+\t\ti = list_and_choose((struct prefix_item **)commands,\n \t\t\t\t    ARRAY_SIZE(commands), &main_loop_opts);\n \t\tif (i < -1) {\n \t\t\tprintf(_(\"Bye.\\n\"));\n-- \ngitgitgadget\n\n"},{"id":"373605","messageId":"db1ede363645b0620d4924639efe5ec708441aa7.1554917868.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.git.gitgitgadget@gmail.com","subject":"[PATCH 07/11] Add a function to determine unique prefixes for a list of strings","fromName":"Slavica Djukic via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-04-10T17:37:55Z","receivedAt":"2019-04-10T17:38:06Z","isPatch":true,"sender":{"key":"slavicadj.ip2018@gmail.com","avatar":null},"body":"From: Slavica Djukic <slawica92@hotmail.com>\n\nIn the `git add -i` command, we show unique prefixes of the commands and\nfiles, to give an indication what prefix would select them.\n\nNaturally, the C implementation looks a lot different than the Perl\nimplementation: in Perl, a trie is much easier implemented, while we\nalready have a pretty neat hashmap implementation in C that we use for\nthe purpose of storing (not necessarily unique) prefixes.\n\nThe idea: for each item that we add, we generate prefixes starting with\nthe first letter, then the first two letters, then three, etc, until we\nfind a prefix that is unique (or until the prefix length would be\nlonger than we want). If we encounter a previously-unique prefix on the\nway, we adjust that item's prefix to make it unique again (or we mark it\nas having no unique prefix if we failed to find one). These partial\nprefixes are stored in a hash map (for quick lookup times).\n\nTo make sure that this function works as expected, we add a test using a\nspecial-purpose test helper that was added for that purpose.\n\nNote: We expect the list of prefix items to be passed in as a list of\npointers rather than as regular list to avoid having to copy information\n(the actual items will most likely contain more information than just\nthe name and the length of the unique prefix, but passing in `struct\nprefix_item *` would not allow for that).\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile                   |   2 +\n prefix-map.c               | 111 +++++++++++++++++++++++++++++++++++++\n prefix-map.h               |  40 +++++++++++++\n t/helper/test-prefix-map.c |  58 +++++++++++++++++++\n t/helper/test-tool.c       |   1 +\n t/helper/test-tool.h       |   1 +\n t/t0016-prefix-map.sh      |  10 ++++\n 7 files changed, 223 insertions(+)\n create mode 100644 prefix-map.c\n create mode 100644 prefix-map.h\n create mode 100644 t/helper/test-prefix-map.c\n create mode 100755 t/t0016-prefix-map.sh\n\ndiff --git a/Makefile b/Makefile\nindex 18e656a32f..8299b3f17d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -754,6 +754,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o\n TEST_BUILTINS_OBJS += test-parse-options.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n+TEST_BUILTINS_OBJS += test-prefix-map.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\n@@ -967,6 +968,7 @@ LIB_OBJS += patch-ids.o\n LIB_OBJS += path.o\n LIB_OBJS += pathspec.o\n LIB_OBJS += pkt-line.o\n+LIB_OBJS += prefix-map.o\n LIB_OBJS += preload-index.o\n LIB_OBJS += pretty.o\n LIB_OBJS += prio-queue.o\ndiff --git a/prefix-map.c b/prefix-map.c\nnew file mode 100644\nindex 0000000000..3c5ae4ae0a\n--- /dev/null\n+++ b/prefix-map.c\n@@ -0,0 +1,111 @@\n+#include \"cache.h\"\n+#include \"prefix-map.h\"\n+\n+static int map_cmp(const void *unused_cmp_data,\n+\t\t   const void *entry,\n+\t\t   const void *entry_or_key,\n+\t\t   const void *unused_keydata)\n+{\n+\tconst struct prefix_map_entry *a = entry;\n+\tconst struct prefix_map_entry *b = entry_or_key;\n+\n+\treturn a->prefix_length != b->prefix_length ||\n+\t\tstrncmp(a->name, b->name, a->prefix_length);\n+}\n+\n+static void add_prefix_entry(struct hashmap *map, const char *name,\n+\t\t\t     size_t prefix_length, struct prefix_item *item)\n+{\n+\tstruct prefix_map_entry *result = xmalloc(sizeof(*result));\n+\tresult->name = name;\n+\tresult->prefix_length = prefix_length;\n+\tresult->item = item;\n+\thashmap_entry_init(result, memhash(name, prefix_length));\n+\thashmap_add(map, result);\n+}\n+\n+static void init_prefix_map(struct prefix_map *prefix_map,\n+\t\t\t    int min_prefix_length, int max_prefix_length)\n+{\n+\thashmap_init(&prefix_map->map, map_cmp, NULL, 0);\n+\tprefix_map->min_length = min_prefix_length;\n+\tprefix_map->max_length = max_prefix_length;\n+}\n+\n+static void add_prefix_item(struct prefix_map *prefix_map,\n+\t\t\t    struct prefix_item *item)\n+{\n+\tstruct prefix_map_entry *e = xmalloc(sizeof(*e)), *e2;\n+\tint j;\n+\n+\te->item = item;\n+\te->name = e->item->name;\n+\n+\tfor (j = prefix_map->min_length; j <= prefix_map->max_length; j++) {\n+\t\tif (!isascii(e->name[j])) {\n+\t\t\tfree(e);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\te->prefix_length = j;\n+\t\thashmap_entry_init(e, memhash(e->name, j));\n+\t\te2 = hashmap_get(&prefix_map->map, e, NULL);\n+\t\tif (!e2) {\n+\t\t\t/* prefix is unique so far */\n+\t\t\te->item->prefix_length = j;\n+\t\t\thashmap_add(&prefix_map->map, e);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (!e2->item)\n+\t\t\tcontinue; /* non-unique prefix */\n+\n+\t\tif (j != e2->item->prefix_length)\n+\t\t\tBUG(\"unexpected prefix length: %d != %d\",\n+\t\t\t    (int)j, (int)e2->item->prefix_length);\n+\n+\t\t/* skip common prefix */\n+\t\tfor (; j < prefix_map->max_length && e->name[j]; j++) {\n+\t\t\tif (e->item->name[j] != e2->item->name[j])\n+\t\t\t\tbreak;\n+\t\t\tadd_prefix_entry(&prefix_map->map, e->name, j + 1,\n+\t\t\t\t\t NULL);\n+\t\t}\n+\n+\t\t/* e2 no longer refers to a unique prefix */\n+\t\tif (j < prefix_map->max_length && e2->name[j]) {\n+\t\t\t/* found a new unique prefix for e2's item */\n+\t\t\te2->item->prefix_length = j + 1;\n+\t\t\tadd_prefix_entry(&prefix_map->map, e2->name, j + 1,\n+\t\t\t\t\t e2->item);\n+\t\t}\n+\t\telse\n+\t\t\te2->item->prefix_length = 0;\n+\t\te2->item = NULL;\n+\n+\t\tif (j < prefix_map->max_length && e->name[j]) {\n+\t\t\t/* found a unique prefix for the item */\n+\t\t\te->item->prefix_length = j + 1;\n+\t\t\tadd_prefix_entry(&prefix_map->map, e->name, j + 1,\n+\t\t\t\t\t e->item);\n+\t\t} else {\n+\t\t\t/* item has no (short enough) unique prefix */\n+\t\t\te->item->prefix_length = 0;\n+\t\t\tfree(e);\n+\t\t}\n+\n+\t\tbreak;\n+\t}\n+}\n+\n+void find_unique_prefixes(struct prefix_item **list, size_t nr,\n+\t\t\t  int min_length, int max_length)\n+{\n+\tint i;\n+\tstruct prefix_map prefix_map;\n+\n+\tinit_prefix_map(&prefix_map, min_length, max_length);\n+\tfor (i = 0; i < nr; i++)\n+\t\tadd_prefix_item(&prefix_map, list[i]);\n+\thashmap_free(&prefix_map.map, 1);\n+}\ndiff --git a/prefix-map.h b/prefix-map.h\nnew file mode 100644\nindex 0000000000..ce3b8a4a32\n--- /dev/null\n+++ b/prefix-map.h\n@@ -0,0 +1,40 @@\n+#ifndef PREFIX_MAP_H\n+#define PREFIX_MAP_H\n+\n+#include \"hashmap.h\"\n+\n+struct prefix_item {\n+\tconst char *name;\n+\tsize_t prefix_length;\n+};\n+\n+struct prefix_map_entry {\n+\tstruct hashmap_entry e;\n+\tconst char *name;\n+\tsize_t prefix_length;\n+\t/* if item is NULL, the prefix is not unique */\n+\tstruct prefix_item *item;\n+};\n+\n+struct prefix_map {\n+\tstruct hashmap map;\n+\tint min_length, max_length;\n+};\n+\n+/*\n+ * Find unique prefixes in a given list of strings.\n+ *\n+ * Typically, the `struct prefix_item` information will be but a field in the\n+ * actual item struct; For this reason, the `list` parameter is specified as a\n+ * list of pointers to the items.\n+ *\n+ * The `min_length`/`max_length` parameters define what length the unique\n+ * prefixes should have.\n+ *\n+ * If no unique prefix could be found for a given item, its `prefix_length`\n+ * will be set to 0.\n+ */\n+void find_unique_prefixes(struct prefix_item **list, size_t nr,\n+\t\t\t  int min_length, int max_length);\n+\n+#endif\ndiff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c\nnew file mode 100644\nindex 0000000000..3f1c90eaf0\n--- /dev/null\n+++ b/t/helper/test-prefix-map.c\n@@ -0,0 +1,58 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"prefix-map.h\"\n+\n+static size_t test_count, failed_count;\n+\n+static void check(int succeeded, const char *file, size_t line_no,\n+\t\t  const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\ttest_count++;\n+\tif (succeeded)\n+\t\treturn;\n+\n+\tva_start(ap, fmt);\n+\tfprintf(stderr, \"%s:%d: \", file, (int)line_no);\n+\tvfprintf(stderr, fmt, ap);\n+\tfputc('\\n', stderr);\n+\tva_end(ap);\n+\n+\tfailed_count++;\n+}\n+\n+#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \\\n+\tcheck(expect == actual, __FILE__, __LINE__, \\\n+\t      \"size_t's do not match: %\" \\\n+\t      PRIdMAX \" != %\" PRIdMAX \" (%s) (%s)\", \\\n+\t      (intmax_t)expect, (intmax_t)actual, #actual, hint)\n+\n+int cmd__prefix_map(int argc, const char **argv)\n+{\n+#define NR 5\n+\tstruct prefix_item items[NR] = {\n+\t\t{ \"unique\" },\n+\t\t{ \"hell\" },\n+\t\t{ \"hello\" },\n+\t\t{ \"wok\" },\n+\t\t{ \"world\" },\n+\t};\n+\tstruct prefix_item *list[NR] = {\n+\t\titems, items + 1, items + 2, items + 3, items + 4\n+\t};\n+\n+\tfind_unique_prefixes(list, NR, 1, 3);\n+\n+#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \\\n+\tEXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \\\n+\t\t\t     list[index]->name)\n+\n+\tEXPECT_PREFIX_LENGTH_EQUALS(1, 0);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(0, 1);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(0, 2);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(3, 3);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(3, 4);\n+\n+\treturn !!failed_count;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 99db7409b8..d6a92a8699 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -32,6 +32,7 @@ static struct test_cmd cmds[] = {\n \t{ \"parse-options\", cmd__parse_options },\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n+\t{ \"prefix-map\", cmd__prefix_map },\n \t{ \"prio-queue\", cmd__prio_queue },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 25abed1cf2..33a089ee4e 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -29,6 +29,7 @@ int cmd__online_cpus(int argc, const char **argv);\n int cmd__parse_options(int argc, const char **argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n+int cmd__prefix_map(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t0016-prefix-map.sh b/t/t0016-prefix-map.sh\nnew file mode 100755\nindex 0000000000..187fa92aec\n--- /dev/null\n+++ b/t/t0016-prefix-map.sh\n@@ -0,0 +1,10 @@\n+#!/bin/sh\n+\n+test_description='basic tests for prefix map'\n+. ./test-lib.sh\n+\n+test_expect_success 'prefix map' '\n+\ttest-tool prefix-map\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"373604","messageId":"3e1a88d9c038818fb0d53483e6d9c569d687b38e.1554917868.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.git.gitgitgadget@gmail.com","subject":"[PATCH 10/11] built-in add -i: use color in the main loop","fromName":"Slavica Djukic via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-04-10T17:37:58Z","receivedAt":"2019-04-10T17:38:07Z","isPatch":true,"sender":{"key":"slavicadj.ip2018@gmail.com","avatar":null},"body":"From: Slavica Djukic <slawica92@hotmail.com>\n\nThe error messages as well as the unique prefixes are colored in `git\nadd -i` by default; We need to do the same in the built-in version.\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 40 +++++++++++++++++++++++++++++++++++-----\n 1 file changed, 35 insertions(+), 5 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 03d0770013..a1550d9b9f 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -13,11 +13,17 @@ static int use_color = -1;\n enum color_add_i {\n \tCOLOR_HEADER = 0,\n \tCOLOR_HELP,\n+\tCOLOR_PROMPT,\n+\tCOLOR_ERROR,\n+\tCOLOR_RESET,\n };\n \n static char list_colors[][COLOR_MAXLEN] = {\n \tGIT_COLOR_BOLD,      /* Header */\n \tGIT_COLOR_BOLD_RED,  /* Help */\n+\tGIT_COLOR_BOLD_BLUE, /* Prompt */\n+\tGIT_COLOR_BOLD_RED,  /* Error */\n+\tGIT_COLOR_RESET,     /* Reset */\n };\n \n static const char *get_add_i_color(enum color_add_i ix)\n@@ -33,6 +39,12 @@ static int parse_color_slot(const char *slot)\n \t\treturn COLOR_HEADER;\n \tif (!strcasecmp(slot, \"help\"))\n \t\treturn COLOR_HELP;\n+\tif (!strcasecmp(slot, \"prompt\"))\n+\t\treturn COLOR_PROMPT;\n+\tif (!strcasecmp(slot, \"error\"))\n+\t\treturn COLOR_ERROR;\n+\tif (!strcasecmp(slot, \"reset\"))\n+\t\treturn COLOR_RESET;\n \n \treturn -1;\n }\n@@ -125,6 +137,8 @@ struct list_and_choose_options {\n static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \t\t\t       struct list_and_choose_options *opts)\n {\n+\tconst char *prompt_color = get_add_i_color(COLOR_PROMPT);\n+\tconst char *error_color = get_add_i_color(COLOR_ERROR);\n \tstruct strbuf input = STRBUF_INIT;\n \tssize_t res = -1;\n \n@@ -137,7 +151,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \n \t\tlist(items, nr, &opts->list_opts);\n \n-\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n+\t\tcolor_fprintf(stdout, prompt_color, \"%s\", opts->prompt);\n+\t\tfputs(\"> \", stdout);\n \t\tfflush(stdout);\n \n \t\tif (strbuf_getline(&input, stdin) == EOF) {\n@@ -178,7 +193,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \t\t\t\tindex = find_unique(p, items, nr);\n \n \t\t\tif (index < 0 || index >= nr)\n-\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n+\t\t\t\tcolor_fprintf_ln(stdout, error_color,\n+\t\t\t\t\t\t _(\"Huh (%s)?\"), p);\n \t\t\telse {\n \t\t\t\tres = index;\n \t\t\t\tbreak;\n@@ -423,15 +439,21 @@ static int run_status(struct repository *r, const struct pathspec *ps,\n \treturn 0;\n }\n \n+struct print_command_item_data {\n+\tconst char *color, *reset;\n+};\n+\n static void print_command_item(int i, struct prefix_item *item,\n \t\t\t       void *print_command_item_data)\n {\n+\tstruct print_command_item_data *d = print_command_item_data;\n+\n \tif (!item->prefix_length ||\n \t    !is_valid_prefix(item->name, item->prefix_length))\n \t\tprintf(\" %2d: %s\", i + 1, item->name);\n \telse\n-\t\tprintf(\" %3d: [%.*s]%s\", i + 1,\n-\t\t       (int)item->prefix_length, item->name,\n+\t\tprintf(\" %2d: %s%.*s%s%s\", i + 1,\n+\t\t       d->color, (int)item->prefix_length, item->name, d->reset,\n \t\t       item->name + item->prefix_length);\n }\n \n@@ -455,8 +477,16 @@ static void command_prompt_help(void)\n \n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n+\tstruct print_command_item_data data = {\n+\t\t/*\n+\t\t * When color was asked for, use the prompt color for\n+\t\t * highlighting, otherwise use square brackets.\n+\t\t */\n+\t\twant_color(use_color) ? get_add_i_color(COLOR_PROMPT) : \"[\",\n+\t\twant_color(use_color) ? get_add_i_color(COLOR_RESET) : \"]\"\n+\t};\n \tstruct list_and_choose_options main_loop_opts = {\n-\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n+\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, &data },\n \t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct command_item\n-- \ngitgitgadget\n\n"},{"id":"373603","messageId":"481e3316d5a2486dbfef32fca9e096a8eb9a64ee.1554917868.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.git.gitgitgadget@gmail.com","subject":"[PATCH 11/11] built-in add -i: implement the `help` command","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-04-10T17:37:59Z","receivedAt":"2019-04-10T17:38:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis imitates the code to show the help text from the Perl script\n`git-add--interactive.perl` in the built-in version.\n\nTo make sure that it renders exactly like the Perl version of `git add\n-i`, we also add a test case for that to `t3701-add-interactive.sh`.\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c          | 27 +++++++++++++++++++++++++--\n t/t3701-add-interactive.sh | 24 ++++++++++++++++++++++++\n 2 files changed, 49 insertions(+), 2 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex a1550d9b9f..a6baf57e20 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -439,6 +439,27 @@ static int run_status(struct repository *r, const struct pathspec *ps,\n \treturn 0;\n }\n \n+static int run_help(struct repository *r, const struct pathspec *ps,\n+\t\t    struct file_list *files, struct list_options *opts)\n+{\n+\tconst char *help_color = get_add_i_color(COLOR_HELP);\n+\n+\tcolor_fprintf_ln(stdout, help_color, \"status        - %s\",\n+\t\t\t _(\"show paths with changes\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"update        - %s\",\n+\t\t\t _(\"add working tree state to the staged set of changes\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"revert        - %s\",\n+\t\t\t _(\"revert staged set of changes back to the HEAD version\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"patch         - %s\",\n+\t\t\t _(\"pick hunks and update selectively\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"diff          - %s\",\n+\t\t\t _(\"view diff between HEAD and index\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"add untracked - %s\",\n+\t\t\t _(\"add contents of untracked files to the staged set of changes\"));\n+\n+\treturn 0;\n+}\n+\n struct print_command_item_data {\n \tconst char *color, *reset;\n };\n@@ -490,9 +511,11 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct command_item\n-\t\tstatus = { { \"status\" }, run_status };\n+\t\tstatus = { { \"status\" }, run_status },\n+\t\thelp = { { \"help\" }, run_help };\n \tstruct command_item *commands[] = {\n-\t\t&status\n+\t\t&status,\n+\t\t&help\n \t};\n \n \tstruct print_file_item_data print_file_item_data = {\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex 65dfbc033a..91aaef2932 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -639,4 +639,28 @@ test_expect_success 'add -p patch editing works with pathological context lines'\n \ttest_cmp expected-2 actual\n '\n \n+test_expect_success 'show help from add--helper' '\n+\tgit reset --hard &&\n+\tcat >expect <<-EOF &&\n+\n+\t<BOLD>*** Commands ***<RESET>\n+\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>\n+\t<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>\n+\t<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>\n+\t<BOLD;RED>patch         - pick hunks and update selectively<RESET>\n+\t<BOLD;RED>diff          - view diff between HEAD and index<RESET>\n+\t<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>\n+\t<BOLD>*** Commands ***<RESET>\n+\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t<BOLD;BLUE>What now<RESET>>$SP\n+\tBye.\n+\tEOF\n+\ttest_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&\n+\ttest_decode_color <actual.colored >actual &&\n+\ttest_i18ncmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"374117","messageId":"3149b9ba-2f5a-46ce-ad89-4105ec217795@jeffhostetler.com","threadId":"50913","inReplyTo":"12978dc248a2cd07c90559691b8a2add84f45394.1554917868.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 01/11] Start to implement a built-in version of `git add --interactive`","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-04-18T14:31:30Z","receivedAt":"2019-04-18T14:31:35Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 4/10/2019 1:37 PM, Johannes Schindelin via GitGitGadget wrote:\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> \n> This is hardly the first conversion of a Git command that is implemented\n> as a script to a built-in. So far, the most successful strategy for such\n> conversions has been to add a built-in helper and call that for more and\n> more functionality from the script, as more and more parts are\n> converted.\n> \n> With the interactive add, we choose a different strategy. The sole\n> reason for this is that on Windows (where such a conversion has the most\n> benefits in terms of speed and robustness) we face the very specific\n> problem that a `system()` call in Perl seems to close `stdin` in the\n> parent process when the spawned process consumes even one character from\n> `stdin`. And that just does not work for us here, as it would stop the\n> main loop as soon as any interactive command was performed by the\n> helper. Which is almost all of the commands in `git add -i`.\n> \n> It is almost as if Perl told us once again that it does not want us to\n> use it on Windows.\n> \n> Instead, we follow the opposite route where we start with a bare-bones\n> version of the built-in interactive add, guarded by the new\n> `add.interactive.useBuiltin` config variable, and then add more and more\n> functionality to it, until it is feature complete.\n> \n> At this point, the built-in version of `git add -i` only states that it\n> cannot do anything yet ;-)\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>   Documentation/config/add.txt |  5 +++++\n>   Makefile                     |  1 +\n>   add-interactive.c            | 13 +++++++++++++\n>   add-interactive.h            | 10 ++++++++++\n>   builtin/add.c                | 16 +++++++++++++++-\n>   t/README                     |  4 ++++\n>   6 files changed, 48 insertions(+), 1 deletion(-)\n>   create mode 100644 add-interactive.c\n>   create mode 100644 add-interactive.h\n> \n> diff --git a/Documentation/config/add.txt b/Documentation/config/add.txt\n> index 4d753f006e..c9f748f81c 100644\n> --- a/Documentation/config/add.txt\n> +++ b/Documentation/config/add.txt\n> @@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::\n>   \toption of linkgit:git-add[1].  `add.ignore-errors` is deprecated,\n>   \tas it does not follow the usual naming convention for configuration\n>   \tvariables.\n> +\n> +add.interactive.useBuiltin::\n> +\t[EXPERIMENTAL] Set to `true` to use the experimental built-in\n> +\timplementation of the interactive version of linkgit:git-add[1]\n> +\tinstead of the Perl script version. Is `false` by default.\n> diff --git a/Makefile b/Makefile\n> index c5240942f2..18e656a32f 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -848,6 +848,7 @@ LIB_H = $(shell $(FIND) . \\\n>   \t-name '*.h' -print)\n>   \n>   LIB_OBJS += abspath.o\n> +LIB_OBJS += add-interactive.o\n>   LIB_OBJS += advice.o\n>   LIB_OBJS += alias.o\n>   LIB_OBJS += alloc.o\n> diff --git a/add-interactive.c b/add-interactive.c\n> new file mode 100644\n> index 0000000000..540bf185d8\n> --- /dev/null\n> +++ b/add-interactive.c\n> @@ -0,0 +1,13 @@\n> +#include \"cache.h\"\n> +#include \"add-interactive.h\"\n> +#include \"config.h\"\n> +\n> +int add_i_config(const char *var, const char *value, void *cb)\n> +{\n> +\treturn git_default_config(var, value, cb);\n> +}\n[...]\n> diff --git a/builtin/add.c b/builtin/add.c\n> index db2dfa4350..5a32a755c8 100644\n> --- a/builtin/add.c\n> +++ b/builtin/add.c\n[...]\n> +static int use_builtin_add_i;\n[...]\n> @@ -319,7 +324,12 @@ static int add_config(const char *var, const char *value, void *cb)\n>   \t\tignore_add_errors = git_config_bool(var, value);\n>   \t\treturn 0;\n>   \t}\n> -\treturn git_default_config(var, value, cb);\n> +\tif (!strcmp(var, \"add.interactive.usebuiltin\")) {\n> +\t\tuse_builtin_add_i = git_config_bool(var, value);\n> +\t\treturn 0;\n> +\t}\n> +\n> +\treturn add_i_config(var, value, cb);\n >   }\n\nSomething about this split between add_config() in this file\nand add_i_config() in add-interactive.c bothers me.  I'm not\nsaying it is wrong, but it bothers me.\n\nPerhaps it is that we moved the call to git_default_config()\nto add-interactive.c -- so correct behavior of the non-interactive\ncase depends on add-interactive.c to do the right thing.\n\nAlso, since we can't control the order of observed the k/v pairs,\nwe can't do \"if (use_builtin_add_i) return add_i_config(...);\"\nAnd that wouldn't allow both versions to pickup a common config\nsetting (in their own static variables).\n\nCurrently, neither function looks at any other k/v pairs, so\nthis is a bit of a moot point, but I'm wondering if this should\nlook like this:\n\n     int add_config(...)\n     {\n         // give add-interactive.c a chance to look at k/v pair, but\n         // do not short-cut because we don't know yet whether we\n         // will be interactive or not yet.\n         (void)add_i_config(...);\n\n         ...ignore_add_errors...\n         ...use_builtin_add_i...\n\n         return git_default_config(...);\n     }\n\n     int add_i_config(...)\n     {\n         return 0;\n     }\n\nor just inline everything here in add_config() and be done with it.\n\n\nJeff\n\n"},{"id":"374119","messageId":"20190418160633.GB8054@sigill.intra.peff.net","threadId":"50913","inReplyTo":"3149b9ba-2f5a-46ce-ad89-4105ec217795@jeffhostetler.com","subject":"Re: [PATCH 01/11] Start to implement a built-in version of `git add --interactive`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2019-04-18T16:06:33Z","receivedAt":"2019-04-18T16:06:37Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Apr 18, 2019 at 10:31:30AM -0400, Jeff Hostetler wrote:\n\n> Currently, neither function looks at any other k/v pairs, so\n> this is a bit of a moot point, but I'm wondering if this should\n> look like this:\n> \n>     int add_config(...)\n>     {\n>         // give add-interactive.c a chance to look at k/v pair, but\n>         // do not short-cut because we don't know yet whether we\n>         // will be interactive or not yet.\n>         (void)add_i_config(...);\n> \n>         ...ignore_add_errors...\n>         ...use_builtin_add_i...\n> \n>         return git_default_config(...);\n>     }\n\nYeah, I agree this split seems a bit more natural. It is worth\npropagating errors from add_i_config(), though, like:\n\n  if (add_i_config(var, value, data))\n\treturn -1;\n\nso that any key-specific errors (e.g., config_error_nonbool) stop the\nparsing in the usual way.\n\n-Peff\n"},{"id":"374120","messageId":"27ebbb44-1208-563c-2419-edfc6570fefb@jeffhostetler.com","threadId":"50913","inReplyTo":"93b3151b6c8abeeab0674919badae72e39eea68d.1554917868.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 06/11] built-in add -i: implement the main loop","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-04-18T16:49:58Z","receivedAt":"2019-04-18T16:50:03Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 4/10/2019 1:37 PM, Johannes Schindelin via GitGitGadget wrote:\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> \n> The reason why we did not start with the main loop to begin with is that\n> it is the first user of `list_and_choose()`, which uses the `list()`\n> function that we conveniently introduced for use by the `status`\n> command.\n> \n> Apart from the \"and choose\" part, there are more differences between the\n> way the `status` command calls the `list_and_choose()` function in the\n> Perl version of `git add -i` compared to the other callers of said\n> function. The most important ones:\n> \n> - The list is not only shown, but the user is also asked to make a\n>    choice, possibly selecting multiple entries.\n> \n> - The list of items is prefixed with a marker indicating what items have\n>    been selected, if multi-selection is allowed.\n> \n> - Initially, for each item a unique prefix (if there exists any within\n>    the given parameters) is determined, and shown in the list, and\n>    accepted as a shortcut for the selection.\n> \n> These features will be implemented later, except the part where the user\n> can choose a command. At this stage, though, the built-in `git add -i`\n> still only supports the `status` command, with the remaining commands to\n> follow over the course of the next commits.\n> \n> In addition, we also modify `list()` to support displaying the commands\n> in columns, even if there is currently only one.\n> \n> The Perl script `git-add--interactive.perl` mixed the purposes of the\n> \"list\" and the \"and choose\" part into the same function. In the C\n> version, we will keep them separate instead, calling the `list()`\n> function from the `list_and_choose()` function.\n> \n> Note that we only have a prompt ending in a single \">\" at this stage;\n> later commits will add commands that display a double \">>\" to indicate\n> that the user is in a different loop than the main one.\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>   add-interactive.c | 122 +++++++++++++++++++++++++++++++++++++++++++++-\n>   1 file changed, 120 insertions(+), 2 deletions(-)\n> \n> diff --git a/add-interactive.c b/add-interactive.c\n> index 79adc58321..c8bd62369e 100644\n> --- a/add-interactive.c\n> +++ b/add-interactive.c\n> @@ -58,6 +58,7 @@ struct item {\n>   };\n>   \n>   struct list_options {\n> +\tint columns;\n>   \tconst char *header;\n>   \tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n>   \tvoid *print_item_data;\n> @@ -65,7 +66,7 @@ struct list_options {\n>   \n>   static void list(struct item **list, size_t nr, struct list_options *opts)\n>   {\n> -\tint i;\n> +\tint i, last_lf = 0;\n>   \n>   \tif (!nr)\n>   \t\treturn;\n> @@ -77,8 +78,90 @@ static void list(struct item **list, size_t nr, struct list_options *opts)\n>   \n>   \tfor (i = 0; i < nr; i++) {\n>   \t\topts->print_item(i, list[i], opts->print_item_data);\n> +\n> +\t\tif ((opts->columns) && ((i + 1) % (opts->columns))) {\n> +\t\t\tputchar('\\t');\n> +\t\t\tlast_lf = 0;\n> +\t\t}\n> +\t\telse {\n> +\t\t\tputchar('\\n');\n> +\t\t\tlast_lf = 1;\n> +\t\t}\n> +\t}\n> +\n> +\tif (!last_lf)\n>   \t\tputchar('\\n');\n> +}\n> +struct list_and_choose_options {\n> +\tstruct list_options list_opts;\n> +\n> +\tconst char *prompt;\n> +};\n> +\n> +/*\n> + * Returns the selected index.\n> + */\n> +static ssize_t list_and_choose(struct item **items, size_t nr,\n> +\t\t\t       struct list_and_choose_options *opts)\n> +{\n> +\tstruct strbuf input = STRBUF_INIT;\n> +\tssize_t res = -1;\n> +\n> +\tfor (;;) {\n> +\t\tchar *p, *endp;\n> +\n> +\t\tstrbuf_reset(&input);\n> +\n> +\t\tlist(items, nr, &opts->list_opts);\n> +\n> +\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n> +\t\tfflush(stdout);\n> +\n> +\t\tif (strbuf_getline(&input, stdin) == EOF) {\n> +\t\t\tputchar('\\n');\n> +\t\t\tres = -2;\n\nIt would be nice to know what -1 and -2 mean if\nthey get returned to our caller.  Maybe a #define\nfor these??\n\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tstrbuf_trim(&input);\n> +\n> +\t\tif (!input.len)\n> +\t\t\tbreak;\n> +\n> +\t\tp = input.buf;\n> +\t\tfor (;;) {\n> +\t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n> +\t\t\tssize_t index = -1;\n> +\n> +\t\t\tif (!sep) {\n> +\t\t\t\tif (!*p)\n> +\t\t\t\t\tbreak;\n> +\t\t\t\tp++;\n> +\t\t\t\tcontinue;\n> +\t\t\t}\n> +\n> +\t\t\tif (isdigit(*p)) {\n> +\t\t\t\tindex = strtoul(p, &endp, 10) - 1;\n> +\t\t\t\tif (endp != p + sep)\n> +\t\t\t\t\tindex = -1;\n> +\t\t\t}\n> +\n> +\t\t\tp[sep] = '\\0';\n> +\t\t\tif (index < 0 || index >= nr)\n> +\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n> +\t\t\telse {\n> +\t\t\t\tres = index;\n> +\t\t\t\tbreak;\n> +\t\t\t}\n> +\n> +\t\t\tp += sep + 1;\n> +\t\t}\n> +\n> +\t\tif (res >= 0)\n> +\t\t\tbreak;\n>   \t}\n> +\n> +\tstrbuf_release(&input);\n> +\treturn res;\n>   }\n>   \n>   struct adddel {\n> @@ -292,16 +375,39 @@ static int run_status(struct repository *r, const struct pathspec *ps,\n>   \treturn 0;\n>   }\n>   \n> +static void print_command_item(int i, struct item *item,\n> +\t\t\t       void *print_command_item_data)\n> +{\n> +\tprintf(\" %2d: %s\", i + 1, item->name);\n> +}\n> +\n> +struct command_item {\n> +\tstruct item item;\n> +\tint (*command)(struct repository *r, const struct pathspec *ps,\n> +\t\t       struct file_list *files, struct list_options *opts);\n> +};\n> +\n>   int run_add_i(struct repository *r, const struct pathspec *ps)\n>   {\n> +\tstruct list_and_choose_options main_loop_opts = {\n> +\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n> +\t\tN_(\"What now\")\n> +\t};\n> +\tstruct command_item\n> +\t\tstatus = { { \"status\" }, run_status };\n> +\tstruct command_item *commands[] = {\n> +\t\t&status\n> +\t};\n> +\n>   \tstruct print_file_item_data print_file_item_data = {\n>   \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n>   \t};\n>   \tstruct list_options opts = {\n> -\t\tNULL, print_file_item, &print_file_item_data\n> +\t\t0, NULL, print_file_item, &print_file_item_data\n>   \t};\n>   \tstruct strbuf header = STRBUF_INIT;\n>   \tstruct file_list files = { NULL };\n> +\tssize_t i;\n>   \tint res = 0;\n>   \n>   \tstrbuf_addstr(&header, \"      \");\n> @@ -313,6 +419,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n>   \tif (run_status(r, ps, &files, &opts) < 0)\n>   \t\tres = -1;\n>   \n> +\tfor (;;) {\n> +\t\ti = list_and_choose((struct item **)commands,\n> +\t\t\t\t    ARRAY_SIZE(commands), &main_loop_opts);\n> +\t\tif (i < -1) {\n> +\t\t\tprintf(_(\"Bye.\\n\"));\n> +\t\t\tres = 0;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tif (i >= 0)\n> +\t\t\tres = commands[i]->command(r, ps, &files, &opts);\n> +\t}\n> +\n>   \trelease_file_list(&files);\n>   \tstrbuf_release(&print_file_item_data.buf);\n>   \tstrbuf_release(&print_file_item_data.index);\n> \n"},{"id":"374123","messageId":"1be9b470-6daa-a50e-ba3a-432520721b0f@jeffhostetler.com","threadId":"50913","inReplyTo":"db1ede363645b0620d4924639efe5ec708441aa7.1554917868.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 07/11] Add a function to determine unique prefixes for a list of strings","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-04-18T17:57:52Z","receivedAt":"2019-04-18T17:57:57Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 4/10/2019 1:37 PM, Slavica Djukic via GitGitGadget wrote:\n> From: Slavica Djukic <slawica92@hotmail.com>\n> \n> In the `git add -i` command, we show unique prefixes of the commands and\n> files, to give an indication what prefix would select them.\n> \n> Naturally, the C implementation looks a lot different than the Perl\n> implementation: in Perl, a trie is much easier implemented, while we\n> already have a pretty neat hashmap implementation in C that we use for\n> the purpose of storing (not necessarily unique) prefixes.\n> \n> The idea: for each item that we add, we generate prefixes starting with\n> the first letter, then the first two letters, then three, etc, until we\n> find a prefix that is unique (or until the prefix length would be\n> longer than we want). If we encounter a previously-unique prefix on the\n> way, we adjust that item's prefix to make it unique again (or we mark it\n> as having no unique prefix if we failed to find one). These partial\n> prefixes are stored in a hash map (for quick lookup times).\n> \n> To make sure that this function works as expected, we add a test using a\n> special-purpose test helper that was added for that purpose.\n> \n> Note: We expect the list of prefix items to be passed in as a list of\n> pointers rather than as regular list to avoid having to copy information\n> (the actual items will most likely contain more information than just\n> the name and the length of the unique prefix, but passing in `struct\n> prefix_item *` would not allow for that).\n> \n> Signed-off-by: Slavica Djukic <slawica92@hotmail.com>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>   Makefile                   |   2 +\n>   prefix-map.c               | 111 +++++++++++++++++++++++++++++++++++++\n>   prefix-map.h               |  40 +++++++++++++\n>   t/helper/test-prefix-map.c |  58 +++++++++++++++++++\n>   t/helper/test-tool.c       |   1 +\n>   t/helper/test-tool.h       |   1 +\n>   t/t0016-prefix-map.sh      |  10 ++++\n>   7 files changed, 223 insertions(+)\n>   create mode 100644 prefix-map.c\n>   create mode 100644 prefix-map.h\n>   create mode 100644 t/helper/test-prefix-map.c\n>   create mode 100755 t/t0016-prefix-map.sh\n> \n> diff --git a/Makefile b/Makefile\n> index 18e656a32f..8299b3f17d 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -754,6 +754,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o\n>   TEST_BUILTINS_OBJS += test-parse-options.o\n>   TEST_BUILTINS_OBJS += test-path-utils.o\n>   TEST_BUILTINS_OBJS += test-pkt-line.o\n> +TEST_BUILTINS_OBJS += test-prefix-map.o\n>   TEST_BUILTINS_OBJS += test-prio-queue.o\n>   TEST_BUILTINS_OBJS += test-reach.o\n>   TEST_BUILTINS_OBJS += test-read-cache.o\n> @@ -967,6 +968,7 @@ LIB_OBJS += patch-ids.o\n>   LIB_OBJS += path.o\n>   LIB_OBJS += pathspec.o\n>   LIB_OBJS += pkt-line.o\n> +LIB_OBJS += prefix-map.o\n>   LIB_OBJS += preload-index.o\n>   LIB_OBJS += pretty.o\n>   LIB_OBJS += prio-queue.o\n> diff --git a/prefix-map.c b/prefix-map.c\n> new file mode 100644\n> index 0000000000..3c5ae4ae0a\n> --- /dev/null\n> +++ b/prefix-map.c\n> @@ -0,0 +1,111 @@\n> +#include \"cache.h\"\n> +#include \"prefix-map.h\"\n> +\n> +static int map_cmp(const void *unused_cmp_data,\n> +\t\t   const void *entry,\n> +\t\t   const void *entry_or_key,\n> +\t\t   const void *unused_keydata)\n> +{\n> +\tconst struct prefix_map_entry *a = entry;\n> +\tconst struct prefix_map_entry *b = entry_or_key;\n> +\n> +\treturn a->prefix_length != b->prefix_length ||\n> +\t\tstrncmp(a->name, b->name, a->prefix_length);\n> +}\n> +\n> +static void add_prefix_entry(struct hashmap *map, const char *name,\n> +\t\t\t     size_t prefix_length, struct prefix_item *item)\n> +{\n> +\tstruct prefix_map_entry *result = xmalloc(sizeof(*result));\n> +\tresult->name = name;\n> +\tresult->prefix_length = prefix_length;\n> +\tresult->item = item;\n> +\thashmap_entry_init(result, memhash(name, prefix_length));\n> +\thashmap_add(map, result);\n> +}\n> +\n> +static void init_prefix_map(struct prefix_map *prefix_map,\n> +\t\t\t    int min_prefix_length, int max_prefix_length)\n> +{\n> +\thashmap_init(&prefix_map->map, map_cmp, NULL, 0);\n> +\tprefix_map->min_length = min_prefix_length;\n> +\tprefix_map->max_length = max_prefix_length;\n> +}\n> +\n> +static void add_prefix_item(struct prefix_map *prefix_map,\n> +\t\t\t    struct prefix_item *item)\n> +{\n> +\tstruct prefix_map_entry *e = xmalloc(sizeof(*e)), *e2;\n> +\tint j;\n> +\n> +\te->item = item;\n> +\te->name = e->item->name;\n> +\n> +\tfor (j = prefix_map->min_length; j <= prefix_map->max_length; j++) {\n> +\t\tif (!isascii(e->name[j])) {\n\nThis feels odd, if I understand the intent.\n\nFirst, why \"isascii()\" rather than just non-zero?\n\nBut mainly, can we walk off the end of the array and read\npotentially uninitialized memory?  Shouldn't we have something\nat the top of the function like:\n\n     len = strlen(item->name);\n     if (len < prefix_map->min_length)\n         return;\n\n(And maybe avoid the xmalloc() too?)\n\nAnd maybe do \" j <= min(len, max_length) \" in the loop?\nBut I see you're modifying \"j\" down in the body of the loop,\nso I'll wait on suggesting that.\n\n> +\t\t\tfree(e);\n> +\t\t\tbreak;\n> +\t\t}\n> +\n> +\t\te->prefix_length = j;\n> +\t\thashmap_entry_init(e, memhash(e->name, j));\n> +\t\te2 = hashmap_get(&prefix_map->map, e, NULL);\n> +\t\tif (!e2) {\n> +\t\t\t/* prefix is unique so far */\n> +\t\t\te->item->prefix_length = j;\n> +\t\t\thashmap_add(&prefix_map->map, e);\n> +\t\t\tbreak;\n> +\t\t}\n> +\n> +\t\tif (!e2->item)\n> +\t\t\tcontinue; /* non-unique prefix */\n> +\n> +\t\tif (j != e2->item->prefix_length)\n> +\t\t\tBUG(\"unexpected prefix length: %d != %d\",\n> +\t\t\t    (int)j, (int)e2->item->prefix_length);\n\nIIUC, this assurance comes directly from map_cmp(), right?\nWe could strengthen this to\n      (j != e2->item->prefix_length || strncmp(...))\nif we wanted to, right?\n\n> +\n> +\t\t/* skip common prefix */\n> +\t\tfor (; j < prefix_map->max_length && e->name[j]; j++) {\n> +\t\t\tif (e->item->name[j] != e2->item->name[j])\n> +\t\t\t\tbreak;\n\nSame comment here about walking off of the defined end of both arrays.\n\nI'm going to stop here.  I'm getting confused.\n\n> +\t\t\tadd_prefix_entry(&prefix_map->map, e->name, j + 1,\n> +\t\t\t\t\t NULL);\n> +\t\t}\n> +\n> +\t\t/* e2 no longer refers to a unique prefix */\n> +\t\tif (j < prefix_map->max_length && e2->name[j]) {\n> +\t\t\t/* found a new unique prefix for e2's item */\n> +\t\t\te2->item->prefix_length = j + 1;\n> +\t\t\tadd_prefix_entry(&prefix_map->map, e2->name, j + 1,\n> +\t\t\t\t\t e2->item);\n> +\t\t}\n> +\t\telse\n> +\t\t\te2->item->prefix_length = 0;\n> +\t\te2->item = NULL;\n> +\n> +\t\tif (j < prefix_map->max_length && e->name[j]) {\n> +\t\t\t/* found a unique prefix for the item */\n> +\t\t\te->item->prefix_length = j + 1;\n> +\t\t\tadd_prefix_entry(&prefix_map->map, e->name, j + 1,\n> +\t\t\t\t\t e->item);\n> +\t\t} else {\n> +\t\t\t/* item has no (short enough) unique prefix */\n> +\t\t\te->item->prefix_length = 0;\n> +\t\t\tfree(e);\n> +\t\t}\n> +\n> +\t\tbreak;\n> +\t}\n> +}\n> +\n> +void find_unique_prefixes(struct prefix_item **list, size_t nr,\n> +\t\t\t  int min_length, int max_length)\n> +{\n> +\tint i;\n> +\tstruct prefix_map prefix_map;\n> +\n> +\tinit_prefix_map(&prefix_map, min_length, max_length);\n> +\tfor (i = 0; i < nr; i++)\n> +\t\tadd_prefix_item(&prefix_map, list[i]);\n> +\thashmap_free(&prefix_map.map, 1);\n> +}\n> diff --git a/prefix-map.h b/prefix-map.h\n> new file mode 100644\n> index 0000000000..ce3b8a4a32\n> --- /dev/null\n> +++ b/prefix-map.h\n> @@ -0,0 +1,40 @@\n> +#ifndef PREFIX_MAP_H\n> +#define PREFIX_MAP_H\n> +\n> +#include \"hashmap.h\"\n> +\n> +struct prefix_item {\n> +\tconst char *name;\n> +\tsize_t prefix_length;\n> +};\n> +\n> +struct prefix_map_entry {\n> +\tstruct hashmap_entry e;\n> +\tconst char *name;\n> +\tsize_t prefix_length;\n> +\t/* if item is NULL, the prefix is not unique */\n> +\tstruct prefix_item *item;\n> +};\n> +\n> +struct prefix_map {\n> +\tstruct hashmap map;\n> +\tint min_length, max_length;\n> +};\n> +\n> +/*\n> + * Find unique prefixes in a given list of strings.\n> + *\n> + * Typically, the `struct prefix_item` information will be but a field in the\n> + * actual item struct; For this reason, the `list` parameter is specified as a\n> + * list of pointers to the items.\n> + *\n> + * The `min_length`/`max_length` parameters define what length the unique\n> + * prefixes should have.\n> + *\n> + * If no unique prefix could be found for a given item, its `prefix_length`\n> + * will be set to 0.\n> + */\n> +void find_unique_prefixes(struct prefix_item **list, size_t nr,\n> +\t\t\t  int min_length, int max_length);\n> +\n> +#endif\n> diff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c\n> new file mode 100644\n> index 0000000000..3f1c90eaf0\n> --- /dev/null\n> +++ b/t/helper/test-prefix-map.c\n> @@ -0,0 +1,58 @@\n> +#include \"test-tool.h\"\n> +#include \"cache.h\"\n> +#include \"prefix-map.h\"\n> +\n> +static size_t test_count, failed_count;\n> +\n> +static void check(int succeeded, const char *file, size_t line_no,\n> +\t\t  const char *fmt, ...)\n> +{\n> +\tva_list ap;\n> +\n> +\ttest_count++;\n> +\tif (succeeded)\n> +\t\treturn;\n> +\n> +\tva_start(ap, fmt);\n> +\tfprintf(stderr, \"%s:%d: \", file, (int)line_no);\n> +\tvfprintf(stderr, fmt, ap);\n> +\tfputc('\\n', stderr);\n> +\tva_end(ap);\n> +\n> +\tfailed_count++;\n> +}\n> +\n> +#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \\\n> +\tcheck(expect == actual, __FILE__, __LINE__, \\\n> +\t      \"size_t's do not match: %\" \\\n> +\t      PRIdMAX \" != %\" PRIdMAX \" (%s) (%s)\", \\\n> +\t      (intmax_t)expect, (intmax_t)actual, #actual, hint)\n> +\n> +int cmd__prefix_map(int argc, const char **argv)\n> +{\n> +#define NR 5\n> +\tstruct prefix_item items[NR] = {\n> +\t\t{ \"unique\" },\n> +\t\t{ \"hell\" },\n> +\t\t{ \"hello\" },\n> +\t\t{ \"wok\" },\n> +\t\t{ \"world\" },\n> +\t};\n> +\tstruct prefix_item *list[NR] = {\n> +\t\titems, items + 1, items + 2, items + 3, items + 4\n> +\t};\n> +\n> +\tfind_unique_prefixes(list, NR, 1, 3);\n> +\n> +#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \\\n> +\tEXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \\\n> +\t\t\t     list[index]->name)\n> +\n> +\tEXPECT_PREFIX_LENGTH_EQUALS(1, 0);\n> +\tEXPECT_PREFIX_LENGTH_EQUALS(0, 1);\n> +\tEXPECT_PREFIX_LENGTH_EQUALS(0, 2);\n> +\tEXPECT_PREFIX_LENGTH_EQUALS(3, 3);\n> +\tEXPECT_PREFIX_LENGTH_EQUALS(3, 4);\n> +\n> +\treturn !!failed_count;\n> +}\n> diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\n> index 99db7409b8..d6a92a8699 100644\n> --- a/t/helper/test-tool.c\n> +++ b/t/helper/test-tool.c\n> @@ -32,6 +32,7 @@ static struct test_cmd cmds[] = {\n>   \t{ \"parse-options\", cmd__parse_options },\n>   \t{ \"path-utils\", cmd__path_utils },\n>   \t{ \"pkt-line\", cmd__pkt_line },\n> +\t{ \"prefix-map\", cmd__prefix_map },\n>   \t{ \"prio-queue\", cmd__prio_queue },\n>   \t{ \"reach\", cmd__reach },\n>   \t{ \"read-cache\", cmd__read_cache },\n> diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\n> index 25abed1cf2..33a089ee4e 100644\n> --- a/t/helper/test-tool.h\n> +++ b/t/helper/test-tool.h\n> @@ -29,6 +29,7 @@ int cmd__online_cpus(int argc, const char **argv);\n>   int cmd__parse_options(int argc, const char **argv);\n>   int cmd__path_utils(int argc, const char **argv);\n>   int cmd__pkt_line(int argc, const char **argv);\n> +int cmd__prefix_map(int argc, const char **argv);\n>   int cmd__prio_queue(int argc, const char **argv);\n>   int cmd__reach(int argc, const char **argv);\n>   int cmd__read_cache(int argc, const char **argv);\n> diff --git a/t/t0016-prefix-map.sh b/t/t0016-prefix-map.sh\n> new file mode 100755\n> index 0000000000..187fa92aec\n> --- /dev/null\n> +++ b/t/t0016-prefix-map.sh\n> @@ -0,0 +1,10 @@\n> +#!/bin/sh\n> +\n> +test_description='basic tests for prefix map'\n> +. ./test-lib.sh\n> +\n> +test_expect_success 'prefix map' '\n> +\ttest-tool prefix-map\n> +'\n> +\n> +test_done\n> \n"},{"id":"374736","messageId":"nycvar.QRO.7.76.6.1904301935400.45@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"20190418160633.GB8054@sigill.intra.peff.net","subject":"Re: [PATCH 01/11] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-04-30T23:40:06Z","receivedAt":"2019-04-30T23:40:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jeff & Jeff,\n\nOn Thu, 18 Apr 2019, Jeff King wrote:\n\n> On Thu, Apr 18, 2019 at 10:31:30AM -0400, Jeff Hostetler wrote:\n>\n> > Currently, neither function looks at any other k/v pairs, so\n> > this is a bit of a moot point, but I'm wondering if this should\n> > look like this:\n> >\n> >     int add_config(...)\n> >     {\n> >         // give add-interactive.c a chance to look at k/v pair, but\n> >         // do not short-cut because we don't know yet whether we\n> >         // will be interactive or not yet.\n> >         (void)add_i_config(...);\n> >\n> >         ...ignore_add_errors...\n> >         ...use_builtin_add_i...\n> >\n> >         return git_default_config(...);\n> >     }\n>\n> Yeah, I agree this split seems a bit more natural. It is worth\n> propagating errors from add_i_config(), though, like:\n>\n>   if (add_i_config(var, value, data))\n> \treturn -1;\n>\n> so that any key-specific errors (e.g., config_error_nonbool) stop the\n> parsing in the usual way.\n\nThe only problem there is that `add_i_config()` (like all the other\n`git_config()` callbacks) does not report whether it consumed the\nkey/value pair or not. I tried to avoid deviating from the standard\npractice to avoid calling `git_default_config()` when we already consumed\nthe config setting.\n\nAnd I also tried pretty hard to *not* bleed any internal state of\n`add-interactive` into `builtin/add`, as I wanted the new code to be as\nlibified as possible (in a nearby thread, somebody wished for a new `-p`\nmode that would essentially be a combined `git stash -p` and `git add -p`,\nand with properly libified code such a beast is a lot more feasible).\n\nAny idea how to deal with that?\n\nI guess I could invert the order, where `add_config()` would be called\nas a fall-back from `add_i_config()`...\n\nOr I invent a new convention where `add_i_config()` returns 1 when it\nconsumed the key/value pair. But that would set a precedent that is\ninconsistent with the entire existing code base, something I am\nuncomfortable to do for the sake of `add -i`...\n\n\nCiao,\nDscho\n"},{"id":"374737","messageId":"20190501022123.GA22549@sigill.intra.peff.net","threadId":"50913","inReplyTo":"nycvar.QRO.7.76.6.1904301935400.45@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 01/11] Start to implement a built-in version of `git add --interactive`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2019-05-01T02:21:24Z","receivedAt":"2019-05-01T02:21:28Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Apr 30, 2019 at 07:40:06PM -0400, Johannes Schindelin wrote:\n\n> > Yeah, I agree this split seems a bit more natural. It is worth\n> > propagating errors from add_i_config(), though, like:\n> >\n> >   if (add_i_config(var, value, data))\n> > \treturn -1;\n> >\n> > so that any key-specific errors (e.g., config_error_nonbool) stop the\n> > parsing in the usual way.\n> \n> The only problem there is that `add_i_config()` (like all the other\n> `git_config()` callbacks) does not report whether it consumed the\n> key/value pair or not. I tried to avoid deviating from the standard\n> practice to avoid calling `git_default_config()` when we already consumed\n> the config setting.\n\nI don't think it's worth worrying too much about that. We wouldn't match\nthe keys in multiple places anyway (and even if we did, it would\narguably be the right thing to give every callback a chance to see\nthem).\n\nThe only thing it does is short-circuit the rest of the checks that we\nknow won't match. But that doesn't really change the performance\nsubstantially; the worst case is already that we have to hit every\npossible strcmp().\n\nAnd most of our config code does not worry about this, and is OK with\nbranching (it just needs to propagate errors, as above).  For some more\ndiscussion, see 6680a0874f (drop odd return value semantics from\nuserdiff_config, 2012-02-07).\n\nAll that said...\n\n> And I also tried pretty hard to *not* bleed any internal state of\n> `add-interactive` into `builtin/add`, as I wanted the new code to be as\n> libified as possible (in a nearby thread, somebody wished for a new `-p`\n> mode that would essentially be a combined `git stash -p` and `git add -p`,\n> and with properly libified code such a beast is a lot more feasible).\n> \n> Any idea how to deal with that?\n\nThe most lib-ified thing is to just use the configset code. I.e.,\nwherever you need the config, just load it on demand via\ngit_config_get_int or whatever.\n\n> Or I invent a new convention where `add_i_config()` returns 1 when it\n> consumed the key/value pair. But that would set a precedent that is\n> inconsistent with the entire existing code base, something I am\n> uncomfortable to do for the sake of `add -i`...\n\nYes, don't do that. :) That was the same thing we finally got rid of for\nuserdiff_config().\n\n-Peff\n"},{"id":"375378","messageId":"nycvar.QRO.7.76.6.1905131313180.44@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"20190501022123.GA22549@sigill.intra.peff.net","subject":"Re: [PATCH 01/11] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-05-13T11:14:36Z","receivedAt":"2019-05-13T11:15:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Tue, 30 Apr 2019, Jeff King wrote:\n\n> On Tue, Apr 30, 2019 at 07:40:06PM -0400, Johannes Schindelin wrote:\n>\n> > And I also tried pretty hard to *not* bleed any internal state of\n> > `add-interactive` into `builtin/add`, as I wanted the new code to be\n> > as libified as possible (in a nearby thread, somebody wished for a new\n> > `-p` mode that would essentially be a combined `git stash -p` and `git\n> > add -p`, and with properly libified code such a beast is a lot more\n> > feasible).\n> >\n> > Any idea how to deal with that?\n>\n> The most lib-ified thing is to just use the configset code. I.e.,\n> wherever you need the config, just load it on demand via\n> git_config_get_int or whatever.\n\nTrue.\n\nAnd it cost me *quite* a few days to implement the changes. But the result\nis definitely a lot better, in my opinion.\n\n> > Or I invent a new convention where `add_i_config()` returns 1 when it\n> > consumed the key/value pair. But that would set a precedent that is\n> > inconsistent with the entire existing code base, something I am\n> > uncomfortable to do for the sake of `add -i`...\n>\n> Yes, don't do that. :) That was the same thing we finally got rid of for\n> userdiff_config().\n\nThanks for stopping me. I did not remember about the userdiff_config()\nthing.\n\nCiao,\nDscho\n"},{"id":"375380","messageId":"nycvar.QRO.7.76.6.1905131401510.44@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"27ebbb44-1208-563c-2419-edfc6570fefb@jeffhostetler.com","subject":"Re: [PATCH 06/11] built-in add -i: implement the main loop","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-05-13T12:04:00Z","receivedAt":"2019-05-13T12:04:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jeff,\n\nOn Thu, 18 Apr 2019, Jeff Hostetler wrote:\n\n> On 4/10/2019 1:37 PM, Johannes Schindelin via GitGitGadget wrote:\n>\n> > [...]\n> > +\n> > +/*\n> > + * Returns the selected index.\n> > + */\n> > +static ssize_t list_and_choose(struct item **items, size_t nr,\n> > +\t\t\t       struct list_and_choose_options *opts)\n> > +{\n> > +\tstruct strbuf input = STRBUF_INIT;\n> > +\tssize_t res = -1;\n> > +\n> > +\tfor (;;) {\n> > +\t\tchar *p, *endp;\n> > +\n> > +\t\tstrbuf_reset(&input);\n> > +\n> > +\t\tlist(items, nr, &opts->list_opts);\n> > +\n> > +\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n> > +\t\tfflush(stdout);\n> > +\n> > +\t\tif (strbuf_getline(&input, stdin) == EOF) {\n> > +\t\t\tputchar('\\n');\n> > +\t\t\tres = -2;\n>\n> It would be nice to know what -1 and -2 mean if\n> they get returned to our caller.  Maybe a #define\n> for these??\n\nMakes a total lot of sense. I changed that.\n\nThanks!\nDscho\n"},{"id":"375382","messageId":"nycvar.QRO.7.76.6.1905131405210.44@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"1be9b470-6daa-a50e-ba3a-432520721b0f@jeffhostetler.com","subject":"Re: [PATCH 07/11] Add a function to determine unique prefixes for a list of strings","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-05-13T12:48:18Z","receivedAt":"2019-05-13T12:48:47Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jeff,\n\nOn Thu, 18 Apr 2019, Jeff Hostetler wrote:\n\n> On 4/10/2019 1:37 PM, Slavica Djukic via GitGitGadget wrote:\n> > From: Slavica Djukic <slawica92@hotmail.com>\n> >\n> > In the `git add -i` command, we show unique prefixes of the commands and\n> > files, to give an indication what prefix would select them.\n> >\n> > Naturally, the C implementation looks a lot different than the Perl\n> > implementation: in Perl, a trie is much easier implemented, while we\n> > already have a pretty neat hashmap implementation in C that we use for\n> > the purpose of storing (not necessarily unique) prefixes.\n> >\n> > The idea: for each item that we add, we generate prefixes starting with\n> > the first letter, then the first two letters, then three, etc, until we\n> > find a prefix that is unique (or until the prefix length would be\n> > longer than we want). If we encounter a previously-unique prefix on the\n> > way, we adjust that item's prefix to make it unique again (or we mark it\n> > as having no unique prefix if we failed to find one). These partial\n> > prefixes are stored in a hash map (for quick lookup times).\n> >\n> > To make sure that this function works as expected, we add a test using a\n> > special-purpose test helper that was added for that purpose.\n> >\n> > Note: We expect the list of prefix items to be passed in as a list of\n> > pointers rather than as regular list to avoid having to copy information\n> > (the actual items will most likely contain more information than just\n> > the name and the length of the unique prefix, but passing in `struct\n> > prefix_item *` would not allow for that).\n> >\n> > Signed-off-by: Slavica Djukic <slawica92@hotmail.com>\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > diff --git a/prefix-map.c b/prefix-map.c\n> > new file mode 100644\n> > index 0000000000..3c5ae4ae0a\n> > --- /dev/null\n> > +++ b/prefix-map.c\n> > @@ -0,0 +1,111 @@\n> > +#include \"cache.h\"\n> > +#include \"prefix-map.h\"\n> > +\n> > +static int map_cmp(const void *unused_cmp_data,\n> > +\t\t   const void *entry,\n> > +\t\t   const void *entry_or_key,\n> > +\t\t   const void *unused_keydata)\n> > +{\n> > +\tconst struct prefix_map_entry *a = entry;\n> > +\tconst struct prefix_map_entry *b = entry_or_key;\n> > +\n> > +\treturn a->prefix_length != b->prefix_length ||\n> > +\t\tstrncmp(a->name, b->name, a->prefix_length);\n> > +}\n> > +\n> > +static void add_prefix_entry(struct hashmap *map, const char *name,\n> > +\t\t\t     size_t prefix_length, struct prefix_item *item)\n> > +{\n> > +\tstruct prefix_map_entry *result = xmalloc(sizeof(*result));\n> > +\tresult->name = name;\n> > +\tresult->prefix_length = prefix_length;\n> > +\tresult->item = item;\n> > +\thashmap_entry_init(result, memhash(name, prefix_length));\n> > +\thashmap_add(map, result);\n> > +}\n> > +\n> > +static void init_prefix_map(struct prefix_map *prefix_map,\n> > +\t\t\t    int min_prefix_length, int max_prefix_length)\n> > +{\n> > +\thashmap_init(&prefix_map->map, map_cmp, NULL, 0);\n> > +\tprefix_map->min_length = min_prefix_length;\n> > +\tprefix_map->max_length = max_prefix_length;\n> > +}\n> > +\n> > +static void add_prefix_item(struct prefix_map *prefix_map,\n> > +\t\t\t    struct prefix_item *item)\n> > +{\n> > +\tstruct prefix_map_entry *e = xmalloc(sizeof(*e)), *e2;\n> > +\tint j;\n> > +\n> > +\te->item = item;\n> > +\te->name = e->item->name;\n> > +\n> > +\tfor (j = prefix_map->min_length; j <= prefix_map->max_length; j++) {\n> > +\t\tif (!isascii(e->name[j])) {\n>\n> This feels odd, if I understand the intent.\n>\n> First, why \"isascii()\" rather than just non-zero?\n\nThat's to imitate `git-add--interactive.perl`'s\n\n\tif (ord($letters[0]) > 127 ||\n\t    ($soft_limit && $j + 1 > $soft_limit))\n\nSee https://github.com/git/git/blob/v2.21.0/git-add--interactive.perl#L410\nfor more complete context.\n\nI think the main benefit here is that we avoid running into the trap of\nusing incomplete UTF-8 multi-byte sequences in prefixes.\n\nI guess we could throw in an extra safety on the C side by excluding\ncontrol characters, too. But that would be a deviation from Perl, and I\nactually do not even feel strongly about excluding, say, a HT (horizontal\ntab) from the prefixes.\n\n> But mainly, can we walk off the end of the array and read\n> potentially uninitialized memory?  Shouldn't we have something\n> at the top of the function like:\n>\n>     len = strlen(item->name);\n>     if (len < prefix_map->min_length)\n>         return;\n\nOoops, you're right. But I would not use `strlen() here, we can easily\njust add `&& e->name[j]` to the loop condition.\n\n> (And maybe avoid the xmalloc() too?)\n\nHmm. At first, I thought: no, we use `*e` *both* for lookup and for adding\na new item once we did not find any existing for the current prefix\nlength.\n\nBut it does indeed become a lot clearer when I separate those. It's not\neven performance or memory critical a code path.\n\n> And maybe do \" j <= min(len, max_length) \" in the loop?\n> But I see you're modifying \"j\" down in the body of the loop,\n> so I'll wait on suggesting that.\n>\n> > +\t\t\tfree(e);\n> > +\t\t\tbreak;\n> > +\t\t}\n> > +\n> > +\t\te->prefix_length = j;\n> > +\t\thashmap_entry_init(e, memhash(e->name, j));\n> > +\t\te2 = hashmap_get(&prefix_map->map, e, NULL);\n> > +\t\tif (!e2) {\n> > +\t\t\t/* prefix is unique so far */\n> > +\t\t\te->item->prefix_length = j;\n> > +\t\t\thashmap_add(&prefix_map->map, e);\n> > +\t\t\tbreak;\n> > +\t\t}\n> > +\n> > +\t\tif (!e2->item)\n> > +\t\t\tcontinue; /* non-unique prefix */\n> > +\n> > +\t\tif (j != e2->item->prefix_length)\n> > +\t\t\tBUG(\"unexpected prefix length: %d != %d\",\n> > +\t\t\t    (int)j, (int)e2->item->prefix_length);\n>\n> IIUC, this assurance comes directly from map_cmp(), right?\n> We could strengthen this to\n>      (j != e2->item->prefix_length || strncmp(...))\n> if we wanted to, right?\n\nRight, I'll actually go for `memcmp()` here, but the idea is the same.\n\n> > +\n> > +\t\t/* skip common prefix */\n> > +\t\tfor (; j < prefix_map->max_length && e->name[j]; j++) {\n> > +\t\t\tif (e->item->name[j] != e2->item->name[j])\n> > +\t\t\t\tbreak;\n>\n> Same comment here about walking off of the defined end of both arrays.\n\nActually, no, not here, as I already test for `e->name[j]` in the loop\ncondition. If we reach the end of `e2->item->name`, the inner condition\nwill break out of the loop.\n\n> I'm going to stop here.  I'm getting confused.\n\nOh no ;-)\n\nThank you for your helpful comments!\n\nCiao,\nDscho\n"},{"id":"375402","messageId":"bc99009fbf0e01e9bbe77aa6410489beadf454dc.1557768471.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 02/11] diff: export diffstat interface","fromName":"Daniel Ferreira via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-05-13T17:27:54Z","receivedAt":"2019-05-13T17:27:58Z","isPatch":true,"sender":{"key":"bnmvco@gmail.com","avatar":"https://avatars.githubusercontent.com/u/735397?v=4"},"body":"From: Daniel Ferreira <bnmvco@gmail.com>\n\nMake the diffstat interface (namely, the diffstat_t struct and\ncompute_diffstat) no longer be internal to diff.c and allow it to be used\nby other parts of git.\n\nThis is helpful for code that may want to easily extract information\nfrom files using the diff machinery, while flushing it differently from\nhow the show_* functions used by diff_flush() do it. One example is the\nbuiltin implementation of git-add--interactive's status.\n\nSigned-off-by: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 37 +++++++++++++++----------------------\n diff.h | 19 +++++++++++++++++++\n 2 files changed, 34 insertions(+), 22 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 5306c48652..daa5f3a736 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -2489,22 +2489,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)\n \t}\n }\n \n-struct diffstat_t {\n-\tint nr;\n-\tint alloc;\n-\tstruct diffstat_file {\n-\t\tchar *from_name;\n-\t\tchar *name;\n-\t\tchar *print_name;\n-\t\tconst char *comments;\n-\t\tunsigned is_unmerged:1;\n-\t\tunsigned is_binary:1;\n-\t\tunsigned is_renamed:1;\n-\t\tunsigned is_interesting:1;\n-\t\tuintmax_t added, deleted;\n-\t} **files;\n-};\n-\n static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,\n \t\t\t\t\t  const char *name_a,\n \t\t\t\t\t  const char *name_b)\n@@ -6001,12 +5985,7 @@ void diff_flush(struct diff_options *options)\n \t    dirstat_by_line) {\n \t\tstruct diffstat_t diffstat;\n \n-\t\tmemset(&diffstat, 0, sizeof(struct diffstat_t));\n-\t\tfor (i = 0; i < q->nr; i++) {\n-\t\t\tstruct diff_filepair *p = q->queue[i];\n-\t\t\tif (check_pair_status(p))\n-\t\t\t\tdiff_flush_stat(p, options, &diffstat);\n-\t\t}\n+\t\tcompute_diffstat(options, &diffstat, q);\n \t\tif (output_format & DIFF_FORMAT_NUMSTAT)\n \t\t\tshow_numstat(&diffstat, options);\n \t\tif (output_format & DIFF_FORMAT_DIFFSTAT)\n@@ -6306,6 +6285,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)\n \treturn ignored;\n }\n \n+void compute_diffstat(struct diff_options *options,\n+\t\t      struct diffstat_t *diffstat,\n+\t\t      struct diff_queue_struct *q)\n+{\n+\tint i;\n+\n+\tmemset(diffstat, 0, sizeof(struct diffstat_t));\n+\tfor (i = 0; i < q->nr; i++) {\n+\t\tstruct diff_filepair *p = q->queue[i];\n+\t\tif (check_pair_status(p))\n+\t\t\tdiff_flush_stat(p, options, diffstat);\n+\t}\n+}\n+\n void diff_addremove(struct diff_options *options,\n \t\t    int addremove, unsigned mode,\n \t\t    const struct object_id *oid,\ndiff --git a/diff.h b/diff.h\nindex b512d0477a..ae9bedfab8 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -240,6 +240,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);\n void diff_emit_submodule_pipethrough(struct diff_options *o,\n \t\t\t\t     const char *line, int len);\n \n+struct diffstat_t {\n+\tint nr;\n+\tint alloc;\n+\tstruct diffstat_file {\n+\t\tchar *from_name;\n+\t\tchar *name;\n+\t\tchar *print_name;\n+\t\tconst char *comments;\n+\t\tunsigned is_unmerged:1;\n+\t\tunsigned is_binary:1;\n+\t\tunsigned is_renamed:1;\n+\t\tunsigned is_interesting:1;\n+\t\tuintmax_t added, deleted;\n+\t} **files;\n+};\n+\n enum color_diff {\n \tDIFF_RESET = 0,\n \tDIFF_CONTEXT = 1,\n@@ -328,6 +344,9 @@ void diff_change(struct diff_options *,\n \n struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);\n \n+void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,\n+\t\t      struct diff_queue_struct *q);\n+\n #define DIFF_SETUP_REVERSE      \t1\n #define DIFF_SETUP_USE_SIZE_CACHE\t4\n \n-- \ngitgitgadget\n\n"},{"id":"375403","messageId":"pull.170.v2.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.git.gitgitgadget@gmail.com","subject":"[PATCH v2 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far)","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-05-13T17:27:52Z","receivedAt":"2019-05-13T17:27:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is the first leg on the long journey to a fully built-in git add -i \n(next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3\n[https://github.com/gitgitgadget/git/pull/172], 4\n[https://github.com/gitgitgadget/git/pull/173], 5\n[https://github.com/gitgitgadget/git/pull/174], and 6\n[https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are\nnot necessarily up to date, and will be re-targeted to the appropriate\nbranches in https://github.com/gitster/git as soon as Junio picks them up.\n\nThis here patch series reflects the part that was submitted a couple of\ntimes (see https://github.com/gitgitgadget/git/pull/103) during the\nOutreachy project by Slavica Ðukic that continued the journey based on an\ninitial patch series by Daniel Ferreira.\n\nIt only implements the status and the help part, in the interest of making\nthe review remotely more reviewable.\n\nAs I am a heavy user of git add -p myself and use a patched version for\nweeks already (it is so nice to not suffer over one second startup until the\nMSYS2 Perl finally shows me anything, instead it feels instantaneous), I\nintegrated these patch series into Git for Windows' master already, as an\nopt-in feature guarded by the config variable add.interactive.useBuiltin \n(and Git for Windows' installer is prepared to detect this version and offer\nthe option in the graphical user interface).\n\nI had planned on submitting this before v2.22.0-rc0, but there was such a\nbacklog of builds from a big pushout that I had to wait ;-)\n\nChanges since v1:\n\n * The config machinery was reworked completely, to not use a callback to \n   git_config(), but instead to query the config via the repo_config_get_*() \n   functions. This also prevents a future \"Huh???\" moment: the internal add\n   --interactive API accepts a parameter of type struct repository *r, but\n   the previous configuration did not use that to query the config (and\n   could in the future be a repository other than the_repository).\n   \n   \n * As a consequence, the color sequences are no longer stored in file-local\n   variables, but passed around via a struct.\n   \n   \n * Instead of using the magical constant -2 to quit the main loop, it is now\n   defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is\n   defined as -1 and used where appropriate).\n   \n   \n * Improved the add_prefix_item() function by avoiding buffer overruns, not\n   reusing the struct that is used for lookup also for adding the new item,\n   and by strengthening the bug check.\n\nDaniel Ferreira (2):\n  diff: export diffstat interface\n  built-in add -i: implement the `status` command\n\nJohannes Schindelin (6):\n  Start to implement a built-in version of `git add --interactive`\n  built-in add -i: refresh the index before running `status`\n  built-in add -i: color the header in the `status` command\n  built-in add -i: implement the main loop\n  built-in add -i: support `?` (prompt help)\n  built-in add -i: implement the `help` command\n\nSlavica Djukic (3):\n  Add a function to determine unique prefixes for a list of strings\n  built-in add -i: show unique prefixes of the commands\n  built-in add -i: use color in the main loop\n\n Documentation/config/add.txt |   5 +\n Makefile                     |   3 +\n add-interactive.c            | 558 +++++++++++++++++++++++++++++++++++\n add-interactive.h            |   8 +\n builtin/add.c                |  10 +\n diff.c                       |  37 +--\n diff.h                       |  19 ++\n prefix-map.c                 | 109 +++++++\n prefix-map.h                 |  40 +++\n repository.c                 |  19 ++\n repository.h                 |   7 +\n t/README                     |   4 +\n t/helper/test-prefix-map.c   |  58 ++++\n t/helper/test-tool.c         |   1 +\n t/helper/test-tool.h         |   1 +\n t/t0016-prefix-map.sh        |  10 +\n t/t3701-add-interactive.sh   |  24 ++\n 17 files changed, 891 insertions(+), 22 deletions(-)\n create mode 100644 add-interactive.c\n create mode 100644 add-interactive.h\n create mode 100644 prefix-map.c\n create mode 100644 prefix-map.h\n create mode 100644 t/helper/test-prefix-map.c\n create mode 100755 t/t0016-prefix-map.sh\n\n\nbase-commit: 8104ec994ea3849a968b4667d072fedd1e688642\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/170\n\nRange-diff vs v1:\n\n  1:  12978dc248 !  1:  ed53346b92 Start to implement a built-in version of `git add --interactive`\n     @@ -62,12 +62,6 @@\n      @@\n      +#include \"cache.h\"\n      +#include \"add-interactive.h\"\n     -+#include \"config.h\"\n     -+\n     -+int add_i_config(const char *var, const char *value, void *cb)\n     -+{\n     -+\treturn git_default_config(var, value, cb);\n     -+}\n      +\n      +int run_add_i(struct repository *r, const struct pathspec *ps)\n      +{\n     @@ -82,8 +76,6 @@\n      +#ifndef ADD_INTERACTIVE_H\n      +#define ADD_INTERACTIVE_H\n      +\n     -+int add_i_config(const char *var, const char *value, void *cb);\n     -+\n      +struct repository;\n      +struct pathspec;\n      +int run_add_i(struct repository *r, const struct pathspec *ps);\n     @@ -102,50 +94,28 @@\n       static const char * const builtin_add_usage[] = {\n       \tN_(\"git add [<options>] [--] <pathspec>...\"),\n      @@\n     - static int patch_interactive, add_interactive, edit_interactive;\n     - static int take_worktree_changes;\n     - static int add_renormalize;\n     -+static int use_builtin_add_i;\n     - \n     - struct update_callback_data {\n     - \tint flags;\n     -@@\n     + {\n       \tint status, i;\n       \tstruct argv_array argv = ARGV_ARRAY_INIT;\n     - \n     -+\tif (use_builtin_add_i && !patch_mode)\n     -+\t\treturn !!run_add_i(the_repository, pathspec);\n     ++\tint use_builtin_add_i =\n     ++\t\tgit_env_bool(\"GIT_TEST_ADD_I_USE_BUILTIN\", -1);\n     ++\tif (use_builtin_add_i < 0)\n     ++\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n     ++\t\t\t\t    &use_builtin_add_i);\n      +\n     ++\tif (use_builtin_add_i == 1 && !patch_mode)\n     ++\t\treturn !!run_add_i(the_repository, pathspec);\n     + \n       \targv_array_push(&argv, \"add--interactive\");\n       \tif (patch_mode)\n     - \t\targv_array_push(&argv, patch_mode);\n      @@\n       \t\tignore_add_errors = git_config_bool(var, value);\n       \t\treturn 0;\n       \t}\n     --\treturn git_default_config(var, value, cb);\n     -+\tif (!strcmp(var, \"add.interactive.usebuiltin\")) {\n     -+\t\tuse_builtin_add_i = git_config_bool(var, value);\n     -+\t\treturn 0;\n     -+\t}\n      +\n     -+\treturn add_i_config(var, value, cb);\n     + \treturn git_default_config(var, value, cb);\n       }\n       \n     - static const char embedded_advice[] = N_(\n     -@@\n     - \tint require_pathspec;\n     - \tchar *seen = NULL;\n     - \tstruct lock_file lock_file = LOCK_INIT;\n     -+\tint use_builtin_add_i_env =\n     -+\t\tgit_env_bool(\"GIT_TEST_ADD_I_USE_BUILTIN\", -1);\n     - \n     - \tgit_config(add_config, NULL);\n     -+\tif (use_builtin_add_i_env >= 0)\n     -+\t\tuse_builtin_add_i = use_builtin_add_i_env;\n     - \n     - \targc = parse_options(argc, argv, prefix, builtin_add_options,\n     - \t\t\t  builtin_add_usage, PARSE_OPT_KEEP_ARGV0);\n      \n       diff --git a/t/README b/t/README\n       --- a/t/README\n  2:  06ba1ae344 =  2:  bc99009fbf diff: export diffstat interface\n  3:  be9ab904d1 !  3:  5e23c0756b built-in add -i: implement the `status` command\n     @@ -30,16 +30,10 @@\n      @@\n       #include \"cache.h\"\n       #include \"add-interactive.h\"\n     - #include \"config.h\"\n      +#include \"diffcore.h\"\n      +#include \"revision.h\"\n      +#include \"refs.h\"\n     - \n     - int add_i_config(const char *var, const char *value, void *cb)\n     - {\n     - \treturn git_default_config(var, value, cb);\n     - }\n     - \n     ++\n      +struct item {\n      +\tconst char *name;\n      +};\n     @@ -276,7 +270,7 @@\n      +\n      +\treturn 0;\n      +}\n     -+\n     + \n       int run_add_i(struct repository *r, const struct pathspec *ps)\n       {\n      -\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n  4:  a512e14609 =  4:  8cafc6ae8d built-in add -i: refresh the index before running `status`\n  5:  f7c8df058c <  -:  ---------- built-in add -i: color the header in the `status` command\n  -:  ---------- >  5:  83d92a9762 built-in add -i: color the header in the `status` command\n  6:  93b3151b6c !  6:  3eec219124 built-in add -i: implement the main loop\n     @@ -53,8 +53,8 @@\n       \tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n       \tvoid *print_item_data;\n      @@\n     - \n     - static void list(struct item **list, size_t nr, struct list_options *opts)\n     + static void list(struct item **list, size_t nr,\n     + \t\t struct add_i_state *s, struct list_options *opts)\n       {\n      -\tint i;\n      +\tint i, last_lf = 0;\n     @@ -85,28 +85,35 @@\n      +\tconst char *prompt;\n      +};\n      +\n     ++#define LIST_AND_CHOOSE_ERROR (-1)\n     ++#define LIST_AND_CHOOSE_QUIT  (-2)\n     ++\n      +/*\n      + * Returns the selected index.\n     ++ *\n     ++ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n     ++ * `LIST_AND_CHOOSE_QUIT` is returned.\n      + */\n      +static ssize_t list_and_choose(struct item **items, size_t nr,\n     ++\t\t\t       struct add_i_state *s,\n      +\t\t\t       struct list_and_choose_options *opts)\n      +{\n      +\tstruct strbuf input = STRBUF_INIT;\n     -+\tssize_t res = -1;\n     ++\tssize_t res = LIST_AND_CHOOSE_ERROR;\n      +\n      +\tfor (;;) {\n      +\t\tchar *p, *endp;\n      +\n      +\t\tstrbuf_reset(&input);\n      +\n     -+\t\tlist(items, nr, &opts->list_opts);\n     ++\t\tlist(items, nr, s, &opts->list_opts);\n      +\n      +\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n      +\t\tfflush(stdout);\n      +\n      +\t\tif (strbuf_getline(&input, stdin) == EOF) {\n      +\t\t\tputchar('\\n');\n     -+\t\t\tres = -2;\n     ++\t\t\tres = LIST_AND_CHOOSE_QUIT;\n      +\t\t\tbreak;\n      +\t\t}\n      +\t\tstrbuf_trim(&input);\n     @@ -143,7 +150,7 @@\n      +\t\t\tp += sep + 1;\n      +\t\t}\n      +\n     -+\t\tif (res >= 0)\n     ++\t\tif (res != LIST_AND_CHOOSE_ERROR)\n      +\t\t\tbreak;\n       \t}\n      +\n     @@ -164,12 +171,13 @@\n      +\n      +struct command_item {\n      +\tstruct item item;\n     -+\tint (*command)(struct repository *r, const struct pathspec *ps,\n     ++\tint (*command)(struct add_i_state *s, const struct pathspec *ps,\n      +\t\t       struct file_list *files, struct list_options *opts);\n      +};\n      +\n       int run_add_i(struct repository *r, const struct pathspec *ps)\n       {\n     + \tstruct add_i_state s = { NULL };\n      +\tstruct list_and_choose_options main_loop_opts = {\n      +\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n      +\t\tN_(\"What now\")\n     @@ -192,21 +200,21 @@\n      +\tssize_t i;\n       \tint res = 0;\n       \n     - \tstrbuf_addstr(&header, \"      \");\n     + \tif (init_add_i_state(r, &s))\n      @@\n     - \tif (run_status(r, ps, &files, &opts) < 0)\n     + \tif (run_status(&s, ps, &files, &opts) < 0)\n       \t\tres = -1;\n       \n      +\tfor (;;) {\n      +\t\ti = list_and_choose((struct item **)commands,\n     -+\t\t\t\t    ARRAY_SIZE(commands), &main_loop_opts);\n     -+\t\tif (i < -1) {\n     ++\t\t\t\t    ARRAY_SIZE(commands), &s, &main_loop_opts);\n     ++\t\tif (i == LIST_AND_CHOOSE_QUIT) {\n      +\t\t\tprintf(_(\"Bye.\\n\"));\n      +\t\t\tres = 0;\n      +\t\t\tbreak;\n      +\t\t}\n     -+\t\tif (i >= 0)\n     -+\t\t\tres = commands[i]->command(r, ps, &files, &opts);\n     ++\t\tif (i != LIST_AND_CHOOSE_ERROR)\n     ++\t\t\tres = commands[i]->command(&s, ps, &files, &opts);\n      +\t}\n      +\n       \trelease_file_list(&files);\n  7:  db1ede3636 !  7:  e02a52c3ac Add a function to determine unique prefixes for a list of strings\n     @@ -92,40 +92,40 @@\n      +static void add_prefix_item(struct prefix_map *prefix_map,\n      +\t\t\t    struct prefix_item *item)\n      +{\n     -+\tstruct prefix_map_entry *e = xmalloc(sizeof(*e)), *e2;\n     ++\tstruct prefix_map_entry e = { { NULL } }, *e2;\n      +\tint j;\n      +\n     -+\te->item = item;\n     -+\te->name = e->item->name;\n     ++\te.item = item;\n     ++\te.name = item->name;\n      +\n     -+\tfor (j = prefix_map->min_length; j <= prefix_map->max_length; j++) {\n     -+\t\tif (!isascii(e->name[j])) {\n     -+\t\t\tfree(e);\n     ++\tfor (j = prefix_map->min_length;\n     ++\t     j <= prefix_map->max_length && e.name[j]; j++) {\n     ++\t\t/* Avoid breaking UTF-8 multi-byte sequences */\n     ++\t\tif (!isascii(e.name[j]))\n      +\t\t\tbreak;\n     -+\t\t}\n      +\n     -+\t\te->prefix_length = j;\n     -+\t\thashmap_entry_init(e, memhash(e->name, j));\n     -+\t\te2 = hashmap_get(&prefix_map->map, e, NULL);\n     ++\t\te.prefix_length = j;\n     ++\t\thashmap_entry_init(&e, memhash(e.name, j));\n     ++\t\te2 = hashmap_get(&prefix_map->map, &e, NULL);\n      +\t\tif (!e2) {\n     -+\t\t\t/* prefix is unique so far */\n     -+\t\t\te->item->prefix_length = j;\n     -+\t\t\thashmap_add(&prefix_map->map, e);\n     ++\t\t\t/* prefix is unique at this stage */\n     ++\t\t\titem->prefix_length = j;\n     ++\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j, item);\n      +\t\t\tbreak;\n      +\t\t}\n      +\n      +\t\tif (!e2->item)\n      +\t\t\tcontinue; /* non-unique prefix */\n      +\n     -+\t\tif (j != e2->item->prefix_length)\n     -+\t\t\tBUG(\"unexpected prefix length: %d != %d\",\n     -+\t\t\t    (int)j, (int)e2->item->prefix_length);\n     ++\t\tif (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))\n     ++\t\t\tBUG(\"unexpected prefix length: %d != %d (%s != %s)\",\n     ++\t\t\t    j, (int)e2->item->prefix_length, e.name, e2->name);\n      +\n      +\t\t/* skip common prefix */\n     -+\t\tfor (; j < prefix_map->max_length && e->name[j]; j++) {\n     -+\t\t\tif (e->item->name[j] != e2->item->name[j])\n     ++\t\tfor (; j < prefix_map->max_length && e.name[j]; j++) {\n     ++\t\t\tif (e.item->name[j] != e2->item->name[j])\n      +\t\t\t\tbreak;\n     -+\t\t\tadd_prefix_entry(&prefix_map->map, e->name, j + 1,\n     ++\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n      +\t\t\t\t\t NULL);\n      +\t\t}\n      +\n     @@ -140,16 +140,14 @@\n      +\t\t\te2->item->prefix_length = 0;\n      +\t\te2->item = NULL;\n      +\n     -+\t\tif (j < prefix_map->max_length && e->name[j]) {\n     ++\t\tif (j < prefix_map->max_length && e.name[j]) {\n      +\t\t\t/* found a unique prefix for the item */\n     -+\t\t\te->item->prefix_length = j + 1;\n     -+\t\t\tadd_prefix_entry(&prefix_map->map, e->name, j + 1,\n     -+\t\t\t\t\t e->item);\n     -+\t\t} else {\n     ++\t\t\te.item->prefix_length = j + 1;\n     ++\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n     ++\t\t\t\t\t e.item);\n     ++\t\t} else\n      +\t\t\t/* item has no (short enough) unique prefix */\n     -+\t\t\te->item->prefix_length = 0;\n     -+\t\t\tfree(e);\n     -+\t\t}\n     ++\t\t\te.item->prefix_length = 0;\n      +\n      +\t\tbreak;\n      +\t}\n  8:  56acc31e96 !  8:  ced9b6aced built-in add -i: show unique prefixes of the commands\n     @@ -22,10 +22,10 @@\n       #include \"refs.h\"\n      +#include \"prefix-map.h\"\n       \n     - static int use_color = -1;\n     - \n     + struct add_i_state {\n     + \tstruct repository *r;\n      @@\n     - \treturn git_color_default_config(var, value, cb);\n     + \treturn 0;\n       }\n       \n      -struct item {\n     @@ -57,22 +57,22 @@\n       \tvoid *print_item_data;\n       };\n       \n     --static void list(struct item **list, size_t nr, struct list_options *opts)\n     +-static void list(struct item **list, size_t nr,\n      +static void list(struct prefix_item **list, size_t nr,\n     -+\t\t struct list_options *opts)\n     + \t\t struct add_i_state *s, struct list_options *opts)\n       {\n       \tint i, last_lf = 0;\n     - \n      @@\n     - /*\n     -  * Returns the selected index.\n     +  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n     +  * `LIST_AND_CHOOSE_QUIT` is returned.\n        */\n      -static ssize_t list_and_choose(struct item **items, size_t nr,\n      +static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n     + \t\t\t       struct add_i_state *s,\n       \t\t\t       struct list_and_choose_options *opts)\n       {\n       \tstruct strbuf input = STRBUF_INIT;\n     - \tssize_t res = -1;\n     + \tssize_t res = LIST_AND_CHOOSE_ERROR;\n       \n      +\tfind_unique_prefixes(items, nr, 1, 4);\n      +\n     @@ -133,8 +133,8 @@\n       \t\treturn -1;\n       \n       \tif (files->nr)\n     --\t\tlist((struct item **)files->file, files->nr, opts);\n     -+\t\tlist((struct prefix_item **)files->file, files->nr, opts);\n     +-\t\tlist((struct item **)files->file, files->nr, s, opts);\n     ++\t\tlist((struct prefix_item **)files->file, files->nr, s, opts);\n       \tputchar('\\n');\n       \n       \treturn 0;\n     @@ -157,7 +157,7 @@\n       struct command_item {\n      -\tstruct item item;\n      +\tstruct prefix_item item;\n     - \tint (*command)(struct repository *r, const struct pathspec *ps,\n     + \tint (*command)(struct add_i_state *s, const struct pathspec *ps,\n       \t\t       struct file_list *files, struct list_options *opts);\n       };\n      @@\n     @@ -166,6 +166,6 @@\n       \tfor (;;) {\n      -\t\ti = list_and_choose((struct item **)commands,\n      +\t\ti = list_and_choose((struct prefix_item **)commands,\n     - \t\t\t\t    ARRAY_SIZE(commands), &main_loop_opts);\n     - \t\tif (i < -1) {\n     + \t\t\t\t    ARRAY_SIZE(commands), &s, &main_loop_opts);\n     + \t\tif (i == LIST_AND_CHOOSE_QUIT) {\n       \t\t\tprintf(_(\"Bye.\\n\"));\n  9:  ddd0ee51f6 !  9:  7378af60ad built-in add -i: support `?` (prompt help)\n     @@ -12,41 +12,35 @@\n       --- a/add-interactive.c\n       +++ b/add-interactive.c\n      @@\n     - \n     - enum color_add_i {\n     - \tCOLOR_HEADER = 0,\n     -+\tCOLOR_HELP,\n     - };\n     - \n     - static char list_colors[][COLOR_MAXLEN] = {\n     - \tGIT_COLOR_BOLD,      /* Header */\n     -+\tGIT_COLOR_BOLD_RED,  /* Help */\n     + \tstruct repository *r;\n     + \tint use_color;\n     + \tchar header_color[COLOR_MAXLEN];\n     ++\tchar help_color[COLOR_MAXLEN];\n       };\n       \n     - static const char *get_add_i_color(enum color_add_i ix)\n     + static void init_color(struct repository *r, struct add_i_state *s,\n      @@\n     - {\n     - \tif (!strcasecmp(slot, \"header\"))\n     - \t\treturn COLOR_HEADER;\n     -+\tif (!strcasecmp(slot, \"help\"))\n     -+\t\treturn COLOR_HELP;\n     + \ts->use_color = want_color(s->use_color);\n     + \n     + \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n     ++\tinit_color(r, s, \"help\", s->help_color, GIT_COLOR_BOLD_RED);\n       \n     - \treturn -1;\n     + \treturn 0;\n       }\n      @@\n       \tstruct list_options list_opts;\n       \n       \tconst char *prompt;\n     -+\tvoid (*print_help)(void);\n     ++\tvoid (*print_help)(struct add_i_state *s);\n       };\n       \n     - /*\n     + #define LIST_AND_CHOOSE_ERROR (-1)\n      @@\n       \t\tif (!input.len)\n       \t\t\tbreak;\n       \n      +\t\tif (!strcmp(input.buf, \"?\")) {\n     -+\t\t\topts->print_help();\n     ++\t\t\topts->print_help(s);\n      +\t\t\tcontinue;\n      +\t\t}\n      +\n     @@ -57,9 +51,9 @@\n       \t\t       struct file_list *files, struct list_options *opts);\n       };\n       \n     -+static void command_prompt_help(void)\n     ++static void command_prompt_help(struct add_i_state *s)\n      +{\n     -+\tconst char *help_color = get_add_i_color(COLOR_HELP);\n     ++\tconst char *help_color = s->help_color;\n      +\tcolor_fprintf_ln(stdout, help_color, \"%s\", _(\"Prompt help:\"));\n      +\tcolor_fprintf_ln(stdout, help_color, \"1          - %s\",\n      +\t\t\t _(\"select a numbered item\"));\n     @@ -71,6 +65,7 @@\n      +\n       int run_add_i(struct repository *r, const struct pathspec *ps)\n       {\n     + \tstruct add_i_state s = { NULL };\n       \tstruct list_and_choose_options main_loop_opts = {\n       \t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n      -\t\tN_(\"What now\")\n 10:  3e1a88d9c0 ! 10:  36edef85e6 built-in add -i: use color in the main loop\n     @@ -12,51 +12,31 @@\n       --- a/add-interactive.c\n       +++ b/add-interactive.c\n      @@\n     - enum color_add_i {\n     - \tCOLOR_HEADER = 0,\n     - \tCOLOR_HELP,\n     -+\tCOLOR_PROMPT,\n     -+\tCOLOR_ERROR,\n     -+\tCOLOR_RESET,\n     + \tint use_color;\n     + \tchar header_color[COLOR_MAXLEN];\n     + \tchar help_color[COLOR_MAXLEN];\n     ++\tchar prompt_color[COLOR_MAXLEN];\n     ++\tchar error_color[COLOR_MAXLEN];\n     ++\tchar reset_color[COLOR_MAXLEN];\n       };\n       \n     - static char list_colors[][COLOR_MAXLEN] = {\n     - \tGIT_COLOR_BOLD,      /* Header */\n     - \tGIT_COLOR_BOLD_RED,  /* Help */\n     -+\tGIT_COLOR_BOLD_BLUE, /* Prompt */\n     -+\tGIT_COLOR_BOLD_RED,  /* Error */\n     -+\tGIT_COLOR_RESET,     /* Reset */\n     - };\n     - \n     - static const char *get_add_i_color(enum color_add_i ix)\n     + static void init_color(struct repository *r, struct add_i_state *s,\n      @@\n     - \t\treturn COLOR_HEADER;\n     - \tif (!strcasecmp(slot, \"help\"))\n     - \t\treturn COLOR_HELP;\n     -+\tif (!strcasecmp(slot, \"prompt\"))\n     -+\t\treturn COLOR_PROMPT;\n     -+\tif (!strcasecmp(slot, \"error\"))\n     -+\t\treturn COLOR_ERROR;\n     -+\tif (!strcasecmp(slot, \"reset\"))\n     -+\t\treturn COLOR_RESET;\n       \n     - \treturn -1;\n     - }\n     -@@\n     - static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n     - \t\t\t       struct list_and_choose_options *opts)\n     - {\n     -+\tconst char *prompt_color = get_add_i_color(COLOR_PROMPT);\n     -+\tconst char *error_color = get_add_i_color(COLOR_ERROR);\n     - \tstruct strbuf input = STRBUF_INIT;\n     - \tssize_t res = -1;\n     + \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n     + \tinit_color(r, s, \"help\", s->help_color, GIT_COLOR_BOLD_RED);\n     ++\tinit_color(r, s, \"prompt\", s->prompt_color, GIT_COLOR_BOLD_BLUE);\n     ++\tinit_color(r, s, \"error\", s->error_color, GIT_COLOR_BOLD_RED);\n     ++\tinit_color(r, s, \"reset\", s->reset_color, GIT_COLOR_RESET);\n       \n     + \treturn 0;\n     + }\n      @@\n       \n     - \t\tlist(items, nr, &opts->list_opts);\n     + \t\tlist(items, nr, s, &opts->list_opts);\n       \n      -\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n     -+\t\tcolor_fprintf(stdout, prompt_color, \"%s\", opts->prompt);\n     ++\t\tcolor_fprintf(stdout, s->prompt_color, \"%s\", opts->prompt);\n      +\t\tfputs(\"> \", stdout);\n       \t\tfflush(stdout);\n       \n     @@ -66,7 +46,7 @@\n       \n       \t\t\tif (index < 0 || index >= nr)\n      -\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n     -+\t\t\t\tcolor_fprintf_ln(stdout, error_color,\n     ++\t\t\t\tcolor_fprintf_ln(stdout, s->error_color,\n      +\t\t\t\t\t\t _(\"Huh (%s)?\"), p);\n       \t\t\telse {\n       \t\t\t\tres = index;\n     @@ -96,20 +76,32 @@\n       }\n       \n      @@\n     - \n       int run_add_i(struct repository *r, const struct pathspec *ps)\n       {\n     -+\tstruct print_command_item_data data = {\n     -+\t\t/*\n     -+\t\t * When color was asked for, use the prompt color for\n     -+\t\t * highlighting, otherwise use square brackets.\n     -+\t\t */\n     -+\t\twant_color(use_color) ? get_add_i_color(COLOR_PROMPT) : \"[\",\n     -+\t\twant_color(use_color) ? get_add_i_color(COLOR_RESET) : \"]\"\n     -+\t};\n     + \tstruct add_i_state s = { NULL };\n     ++\tstruct print_command_item_data data;\n       \tstruct list_and_choose_options main_loop_opts = {\n      -\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n      +\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, &data },\n       \t\tN_(\"What now\"), command_prompt_help\n       \t};\n       \tstruct command_item\n     +@@\n     + \tif (init_add_i_state(r, &s))\n     + \t\treturn error(\"could not parse `add -i` config\");\n     + \n     ++\t/*\n     ++\t * When color was asked for, use the prompt color for\n     ++\t * highlighting, otherwise use square brackets.\n     ++\t */\n     ++\tif (s.use_color) {\n     ++\t\tdata.color = s.prompt_color;\n     ++\t\tdata.reset = s.reset_color;\n     ++\t} else {\n     ++\t\tdata.color = \"[\";\n     ++\t\tdata.reset = \"]\";\n     ++\t}\n     ++\n     + \tstrbuf_addstr(&header, \"      \");\n     + \tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n     + \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n 11:  481e3316d5 ! 11:  266dbf2a6b built-in add -i: implement the `help` command\n     @@ -18,10 +18,10 @@\n       \treturn 0;\n       }\n       \n     -+static int run_help(struct repository *r, const struct pathspec *ps,\n     ++static int run_help(struct add_i_state *s, const struct pathspec *ps,\n      +\t\t    struct file_list *files, struct list_options *opts)\n      +{\n     -+\tconst char *help_color = get_add_i_color(COLOR_HELP);\n     ++\tconst char *help_color = s->help_color;\n      +\n      +\tcolor_fprintf_ln(stdout, help_color, \"status        - %s\",\n      +\t\t\t _(\"show paths with changes\"));\n\n-- \ngitgitgadget\n"},{"id":"375404","messageId":"8cafc6ae8d2dd434f46751874074940f13412b9f.1557768471.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 04/11] built-in add -i: refresh the index before running `status`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-05-13T17:27:56Z","receivedAt":"2019-05-13T17:28:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis is what the Perl version does, and therefore it is what the\nbuilt-in version should do, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c |  4 +++-\n repository.c      | 19 +++++++++++++++++++\n repository.h      |  7 +++++++\n 3 files changed, 29 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 59b28011f7..2dbf29dee2 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -258,7 +258,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n \topts.header = header.buf;\n \n-\tres = run_status(r, ps, &files, &opts);\n+\trepo_refresh_and_write_index(r, REFRESH_QUIET, 1);\n+\tif (run_status(r, ps, &files, &opts) < 0)\n+\t\tres = -1;\n \n \trelease_file_list(&files);\n \tstrbuf_release(&print_file_item_data.buf);\ndiff --git a/repository.c b/repository.c\nindex 65e6f8b8fd..c90f310093 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -272,3 +272,22 @@ int repo_hold_locked_index(struct repository *repo,\n \t\tBUG(\"the repo hasn't been setup\");\n \treturn hold_lock_file_for_update(lf, repo->index_file, flags);\n }\n+\n+int repo_refresh_and_write_index(struct repository *r,\n+\t\t\t\t unsigned int flags, int gentle)\n+{\n+\tstruct lock_file lock_file = LOCK_INIT;\n+\tint fd;\n+\n+\tif (repo_read_index_preload(r, NULL, 0) < 0)\n+\t\treturn error(_(\"could not read index\"));\n+\tfd = repo_hold_locked_index(r, &lock_file, 0);\n+\tif (!gentle && fd < 0)\n+\t\treturn error(_(\"could not lock index for writing\"));\n+\trefresh_index(r->index, flags, NULL, NULL, NULL);\n+\tif (0 <= fd)\n+\t\trepo_update_index_if_able(r, &lock_file);\n+\trollback_lock_file(&lock_file);\n+\n+\treturn 0;\n+}\ndiff --git a/repository.h b/repository.h\nindex 8981649d43..fb49e0e328 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -154,5 +154,12 @@ int repo_read_index_unmerged(struct repository *);\n  */\n void repo_update_index_if_able(struct repository *, struct lock_file *);\n \n+/*\n+ * Refresh the index and write it out. If the index file could not be\n+ * locked, error out, except in gentle mode. The flags will be passed\n+ * through to refresh_index().\n+ */\n+int repo_refresh_and_write_index(struct repository *r,\n+\t\t\t\t unsigned int flags, int gentle);\n \n #endif /* REPOSITORY_H */\n-- \ngitgitgadget\n\n"},{"id":"375405","messageId":"3eec219124d2a44e5407665f3bd122df48ecc1e4.1557768471.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 06/11] built-in add -i: implement the main loop","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-05-13T17:27:57Z","receivedAt":"2019-05-13T17:28:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe reason why we did not start with the main loop to begin with is that\nit is the first user of `list_and_choose()`, which uses the `list()`\nfunction that we conveniently introduced for use by the `status`\ncommand.\n\nApart from the \"and choose\" part, there are more differences between the\nway the `status` command calls the `list_and_choose()` function in the\nPerl version of `git add -i` compared to the other callers of said\nfunction. The most important ones:\n\n- The list is not only shown, but the user is also asked to make a\n  choice, possibly selecting multiple entries.\n\n- The list of items is prefixed with a marker indicating what items have\n  been selected, if multi-selection is allowed.\n\n- Initially, for each item a unique prefix (if there exists any within\n  the given parameters) is determined, and shown in the list, and\n  accepted as a shortcut for the selection.\n\nThese features will be implemented later, except the part where the user\ncan choose a command. At this stage, though, the built-in `git add -i`\nstill only supports the `status` command, with the remaining commands to\nfollow over the course of the next commits.\n\nIn addition, we also modify `list()` to support displaying the commands\nin columns, even if there is currently only one.\n\nThe Perl script `git-add--interactive.perl` mixed the purposes of the\n\"list\" and the \"and choose\" part into the same function. In the C\nversion, we will keep them separate instead, calling the `list()`\nfunction from the `list_and_choose()` function.\n\nNote that we only have a prompt ending in a single \">\" at this stage;\nlater commits will add commands that display a double \">>\" to indicate\nthat the user is in a different loop than the main one.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 129 +++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 127 insertions(+), 2 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 6c2fca12c1..0ea4f3edb8 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -51,6 +51,7 @@ struct item {\n };\n \n struct list_options {\n+\tint columns;\n \tconst char *header;\n \tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n \tvoid *print_item_data;\n@@ -59,7 +60,7 @@ struct list_options {\n static void list(struct item **list, size_t nr,\n \t\t struct add_i_state *s, struct list_options *opts)\n {\n-\tint i;\n+\tint i, last_lf = 0;\n \n \tif (!nr)\n \t\treturn;\n@@ -70,8 +71,97 @@ static void list(struct item **list, size_t nr,\n \n \tfor (i = 0; i < nr; i++) {\n \t\topts->print_item(i, list[i], opts->print_item_data);\n+\n+\t\tif ((opts->columns) && ((i + 1) % (opts->columns))) {\n+\t\t\tputchar('\\t');\n+\t\t\tlast_lf = 0;\n+\t\t}\n+\t\telse {\n+\t\t\tputchar('\\n');\n+\t\t\tlast_lf = 1;\n+\t\t}\n+\t}\n+\n+\tif (!last_lf)\n \t\tputchar('\\n');\n+}\n+struct list_and_choose_options {\n+\tstruct list_options list_opts;\n+\n+\tconst char *prompt;\n+};\n+\n+#define LIST_AND_CHOOSE_ERROR (-1)\n+#define LIST_AND_CHOOSE_QUIT  (-2)\n+\n+/*\n+ * Returns the selected index.\n+ *\n+ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n+ * `LIST_AND_CHOOSE_QUIT` is returned.\n+ */\n+static ssize_t list_and_choose(struct item **items, size_t nr,\n+\t\t\t       struct add_i_state *s,\n+\t\t\t       struct list_and_choose_options *opts)\n+{\n+\tstruct strbuf input = STRBUF_INIT;\n+\tssize_t res = LIST_AND_CHOOSE_ERROR;\n+\n+\tfor (;;) {\n+\t\tchar *p, *endp;\n+\n+\t\tstrbuf_reset(&input);\n+\n+\t\tlist(items, nr, s, &opts->list_opts);\n+\n+\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n+\t\tfflush(stdout);\n+\n+\t\tif (strbuf_getline(&input, stdin) == EOF) {\n+\t\t\tputchar('\\n');\n+\t\t\tres = LIST_AND_CHOOSE_QUIT;\n+\t\t\tbreak;\n+\t\t}\n+\t\tstrbuf_trim(&input);\n+\n+\t\tif (!input.len)\n+\t\t\tbreak;\n+\n+\t\tp = input.buf;\n+\t\tfor (;;) {\n+\t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n+\t\t\tssize_t index = -1;\n+\n+\t\t\tif (!sep) {\n+\t\t\t\tif (!*p)\n+\t\t\t\t\tbreak;\n+\t\t\t\tp++;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (isdigit(*p)) {\n+\t\t\t\tindex = strtoul(p, &endp, 10) - 1;\n+\t\t\t\tif (endp != p + sep)\n+\t\t\t\t\tindex = -1;\n+\t\t\t}\n+\n+\t\t\tp[sep] = '\\0';\n+\t\t\tif (index < 0 || index >= nr)\n+\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n+\t\t\telse {\n+\t\t\t\tres = index;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tp += sep + 1;\n+\t\t}\n+\n+\t\tif (res != LIST_AND_CHOOSE_ERROR)\n+\t\t\tbreak;\n \t}\n+\n+\tstrbuf_release(&input);\n+\treturn res;\n }\n \n struct adddel {\n@@ -285,17 +375,40 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \treturn 0;\n }\n \n+static void print_command_item(int i, struct item *item,\n+\t\t\t       void *print_command_item_data)\n+{\n+\tprintf(\" %2d: %s\", i + 1, item->name);\n+}\n+\n+struct command_item {\n+\tstruct item item;\n+\tint (*command)(struct add_i_state *s, const struct pathspec *ps,\n+\t\t       struct file_list *files, struct list_options *opts);\n+};\n+\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n+\tstruct list_and_choose_options main_loop_opts = {\n+\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n+\t\tN_(\"What now\")\n+\t};\n+\tstruct command_item\n+\t\tstatus = { { \"status\" }, run_status };\n+\tstruct command_item *commands[] = {\n+\t\t&status\n+\t};\n+\n \tstruct print_file_item_data print_file_item_data = {\n \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n \t};\n \tstruct list_options opts = {\n-\t\tNULL, print_file_item, &print_file_item_data\n+\t\t0, NULL, print_file_item, &print_file_item_data\n \t};\n \tstruct strbuf header = STRBUF_INIT;\n \tstruct file_list files = { NULL };\n+\tssize_t i;\n \tint res = 0;\n \n \tif (init_add_i_state(r, &s))\n@@ -310,6 +423,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tif (run_status(&s, ps, &files, &opts) < 0)\n \t\tres = -1;\n \n+\tfor (;;) {\n+\t\ti = list_and_choose((struct item **)commands,\n+\t\t\t\t    ARRAY_SIZE(commands), &s, &main_loop_opts);\n+\t\tif (i == LIST_AND_CHOOSE_QUIT) {\n+\t\t\tprintf(_(\"Bye.\\n\"));\n+\t\t\tres = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (i != LIST_AND_CHOOSE_ERROR)\n+\t\t\tres = commands[i]->command(&s, ps, &files, &opts);\n+\t}\n+\n \trelease_file_list(&files);\n \tstrbuf_release(&print_file_item_data.buf);\n \tstrbuf_release(&print_file_item_data.index);\n-- \ngitgitgadget\n\n"},{"id":"375406","messageId":"ced9b6acedf0450555488c0cbda55716d6bc636e.1557768472.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 08/11] built-in add -i: show unique prefixes of the commands","fromName":"Slavica Djukic via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-05-13T17:27:59Z","receivedAt":"2019-05-13T17:28:05Z","isPatch":true,"sender":{"key":"slavicadj.ip2018@gmail.com","avatar":null},"body":"From: Slavica Djukic <slawica92@hotmail.com>\n\nJust like in the Perl script `git-add--interactive.perl`, for each\ncommand a unique prefix is determined (if there exists any within the\ngiven parameters), and shown in the list, and accepted as a shortcut for\nthe command.\n\nWe use the prefix map implementation that we just added in the previous\ncommit for that purpose.\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n---\n add-interactive.c | 69 ++++++++++++++++++++++++++++++++++++++---------\n 1 file changed, 56 insertions(+), 13 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 0ea4f3edb8..7ff87bae1b 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -5,6 +5,7 @@\n #include \"diffcore.h\"\n #include \"revision.h\"\n #include \"refs.h\"\n+#include \"prefix-map.h\"\n \n struct add_i_state {\n \tstruct repository *r;\n@@ -46,18 +47,32 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)\n \treturn 0;\n }\n \n-struct item {\n-\tconst char *name;\n-};\n+static ssize_t find_unique(const char *string,\n+\t\t\t   struct prefix_item **list, size_t nr)\n+{\n+\tssize_t found = -1, i;\n+\n+\tfor (i = 0; i < nr; i++) {\n+\t\tstruct prefix_item *item = list[i];\n+\t\tif (!starts_with(item->name, string))\n+\t\t\tcontinue;\n+\t\tif (found >= 0)\n+\t\t\treturn -1;\n+\t\tfound = i;\n+\t}\n+\n+\treturn found;\n+}\n \n struct list_options {\n \tint columns;\n \tconst char *header;\n-\tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n+\tvoid (*print_item)(int i, struct prefix_item *item,\n+\t\t\t   void *print_item_data);\n \tvoid *print_item_data;\n };\n \n-static void list(struct item **list, size_t nr,\n+static void list(struct prefix_item **list, size_t nr,\n \t\t struct add_i_state *s, struct list_options *opts)\n {\n \tint i, last_lf = 0;\n@@ -100,13 +115,15 @@ struct list_and_choose_options {\n  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n  * `LIST_AND_CHOOSE_QUIT` is returned.\n  */\n-static ssize_t list_and_choose(struct item **items, size_t nr,\n+static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \t\t\t       struct add_i_state *s,\n \t\t\t       struct list_and_choose_options *opts)\n {\n \tstruct strbuf input = STRBUF_INIT;\n \tssize_t res = LIST_AND_CHOOSE_ERROR;\n \n+\tfind_unique_prefixes(items, nr, 1, 4);\n+\n \tfor (;;) {\n \t\tchar *p, *endp;\n \n@@ -146,6 +163,9 @@ static ssize_t list_and_choose(struct item **items, size_t nr,\n \t\t\t}\n \n \t\t\tp[sep] = '\\0';\n+\t\t\tif (index < 0)\n+\t\t\t\tindex = find_unique(p, items, nr);\n+\n \t\t\tif (index < 0 || index >= nr)\n \t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n \t\t\telse {\n@@ -171,7 +191,7 @@ struct adddel {\n \n struct file_list {\n \tstruct file_item {\n-\t\tstruct item item;\n+\t\tstruct prefix_item item;\n \t\tstruct adddel index, worktree;\n \t} **file;\n \tsize_t nr, alloc;\n@@ -337,12 +357,29 @@ static void populate_wi_changes(struct strbuf *buf,\n \t\tstrbuf_addstr(buf, no_changes);\n }\n \n+/* filters out prefixes which have special meaning to list_and_choose() */\n+static int is_valid_prefix(const char *prefix, size_t prefix_len)\n+{\n+\treturn prefix_len && prefix &&\n+\t\t/*\n+\t\t * We expect `prefix` to be NUL terminated, therefore this\n+\t\t * `strcspn()` call is okay, even if it might do much more\n+\t\t * work than strictly necessary.\n+\t\t */\n+\t\tstrcspn(prefix, \" \\t\\r\\n,\") >= prefix_len &&\t/* separators */\n+\t\t*prefix != '-' &&\t\t\t\t/* deselection */\n+\t\t!isdigit(*prefix) &&\t\t\t\t/* selection */\n+\t\t(prefix_len != 1 ||\n+\t\t (*prefix != '*' &&\t\t\t\t/* \"all\" wildcard */\n+\t\t  *prefix != '?'));\t\t\t\t/* prompt help */\n+}\n+\n struct print_file_item_data {\n \tconst char *modified_fmt;\n \tstruct strbuf buf, index, worktree;\n };\n \n-static void print_file_item(int i, struct item *item,\n+static void print_file_item(int i, struct prefix_item *item,\n \t\t\t    void *print_file_item_data)\n {\n \tstruct file_item *c = (struct file_item *)item;\n@@ -369,20 +406,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \t\treturn -1;\n \n \tif (files->nr)\n-\t\tlist((struct item **)files->file, files->nr, s, opts);\n+\t\tlist((struct prefix_item **)files->file, files->nr, s, opts);\n \tputchar('\\n');\n \n \treturn 0;\n }\n \n-static void print_command_item(int i, struct item *item,\n+static void print_command_item(int i, struct prefix_item *item,\n \t\t\t       void *print_command_item_data)\n {\n-\tprintf(\" %2d: %s\", i + 1, item->name);\n+\tif (!item->prefix_length ||\n+\t    !is_valid_prefix(item->name, item->prefix_length))\n+\t\tprintf(\" %2d: %s\", i + 1, item->name);\n+\telse\n+\t\tprintf(\" %3d: [%.*s]%s\", i + 1,\n+\t\t       (int)item->prefix_length, item->name,\n+\t\t       item->name + item->prefix_length);\n }\n \n struct command_item {\n-\tstruct item item;\n+\tstruct prefix_item item;\n \tint (*command)(struct add_i_state *s, const struct pathspec *ps,\n \t\t       struct file_list *files, struct list_options *opts);\n };\n@@ -424,7 +467,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\tres = -1;\n \n \tfor (;;) {\n-\t\ti = list_and_choose((struct item **)commands,\n+\t\ti = list_and_choose((struct prefix_item **)commands,\n \t\t\t\t    ARRAY_SIZE(commands), &s, &main_loop_opts);\n \t\tif (i == LIST_AND_CHOOSE_QUIT) {\n \t\t\tprintf(_(\"Bye.\\n\"));\n-- \ngitgitgadget\n\n"},{"id":"375407","messageId":"266dbf2a6bd32a60fbcf25d26b40189430e21c9a.1557768472.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 11/11] built-in add -i: implement the `help` command","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-05-13T17:28:02Z","receivedAt":"2019-05-13T17:28:11Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis imitates the code to show the help text from the Perl script\n`git-add--interactive.perl` in the built-in version.\n\nTo make sure that it renders exactly like the Perl version of `git add\n-i`, we also add a test case for that to `t3701-add-interactive.sh`.\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c          | 27 +++++++++++++++++++++++++--\n t/t3701-add-interactive.sh | 24 ++++++++++++++++++++++++\n 2 files changed, 49 insertions(+), 2 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 538658dfa7..c431c72e3f 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -428,6 +428,27 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \treturn 0;\n }\n \n+static int run_help(struct add_i_state *s, const struct pathspec *ps,\n+\t\t    struct file_list *files, struct list_options *opts)\n+{\n+\tconst char *help_color = s->help_color;\n+\n+\tcolor_fprintf_ln(stdout, help_color, \"status        - %s\",\n+\t\t\t _(\"show paths with changes\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"update        - %s\",\n+\t\t\t _(\"add working tree state to the staged set of changes\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"revert        - %s\",\n+\t\t\t _(\"revert staged set of changes back to the HEAD version\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"patch         - %s\",\n+\t\t\t _(\"pick hunks and update selectively\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"diff          - %s\",\n+\t\t\t _(\"view diff between HEAD and index\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"add untracked - %s\",\n+\t\t\t _(\"add contents of untracked files to the staged set of changes\"));\n+\n+\treturn 0;\n+}\n+\n struct print_command_item_data {\n \tconst char *color, *reset;\n };\n@@ -473,9 +494,11 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct command_item\n-\t\tstatus = { { \"status\" }, run_status };\n+\t\tstatus = { { \"status\" }, run_status },\n+\t\thelp = { { \"help\" }, run_help };\n \tstruct command_item *commands[] = {\n-\t\t&status\n+\t\t&status,\n+\t\t&help\n \t};\n \n \tstruct print_file_item_data print_file_item_data = {\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex 65dfbc033a..91aaef2932 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -639,4 +639,28 @@ test_expect_success 'add -p patch editing works with pathological context lines'\n \ttest_cmp expected-2 actual\n '\n \n+test_expect_success 'show help from add--helper' '\n+\tgit reset --hard &&\n+\tcat >expect <<-EOF &&\n+\n+\t<BOLD>*** Commands ***<RESET>\n+\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>\n+\t<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>\n+\t<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>\n+\t<BOLD;RED>patch         - pick hunks and update selectively<RESET>\n+\t<BOLD;RED>diff          - view diff between HEAD and index<RESET>\n+\t<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>\n+\t<BOLD>*** Commands ***<RESET>\n+\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t<BOLD;BLUE>What now<RESET>>$SP\n+\tBye.\n+\tEOF\n+\ttest_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&\n+\ttest_decode_color <actual.colored >actual &&\n+\ttest_i18ncmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"375408","messageId":"36edef85e625ac6b84a10f8ce5738b7ef5902ebc.1557768472.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 10/11] built-in add -i: use color in the main loop","fromName":"Slavica Djukic via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-05-13T17:28:01Z","receivedAt":"2019-05-13T17:28:13Z","isPatch":true,"sender":{"key":"slavicadj.ip2018@gmail.com","avatar":null},"body":"From: Slavica Djukic <slawica92@hotmail.com>\n\nThe error messages as well as the unique prefixes are colored in `git\nadd -i` by default; We need to do the same in the built-in version.\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 37 ++++++++++++++++++++++++++++++++-----\n 1 file changed, 32 insertions(+), 5 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 1435c12be9..538658dfa7 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -12,6 +12,9 @@ struct add_i_state {\n \tint use_color;\n \tchar header_color[COLOR_MAXLEN];\n \tchar help_color[COLOR_MAXLEN];\n+\tchar prompt_color[COLOR_MAXLEN];\n+\tchar error_color[COLOR_MAXLEN];\n+\tchar reset_color[COLOR_MAXLEN];\n };\n \n static void init_color(struct repository *r, struct add_i_state *s,\n@@ -45,6 +48,9 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)\n \n \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n \tinit_color(r, s, \"help\", s->help_color, GIT_COLOR_BOLD_RED);\n+\tinit_color(r, s, \"prompt\", s->prompt_color, GIT_COLOR_BOLD_BLUE);\n+\tinit_color(r, s, \"error\", s->error_color, GIT_COLOR_BOLD_RED);\n+\tinit_color(r, s, \"reset\", s->reset_color, GIT_COLOR_RESET);\n \n \treturn 0;\n }\n@@ -134,7 +140,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \n \t\tlist(items, nr, s, &opts->list_opts);\n \n-\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n+\t\tcolor_fprintf(stdout, s->prompt_color, \"%s\", opts->prompt);\n+\t\tfputs(\"> \", stdout);\n \t\tfflush(stdout);\n \n \t\tif (strbuf_getline(&input, stdin) == EOF) {\n@@ -175,7 +182,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \t\t\t\tindex = find_unique(p, items, nr);\n \n \t\t\tif (index < 0 || index >= nr)\n-\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n+\t\t\t\tcolor_fprintf_ln(stdout, s->error_color,\n+\t\t\t\t\t\t _(\"Huh (%s)?\"), p);\n \t\t\telse {\n \t\t\t\tres = index;\n \t\t\t\tbreak;\n@@ -420,15 +428,21 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \treturn 0;\n }\n \n+struct print_command_item_data {\n+\tconst char *color, *reset;\n+};\n+\n static void print_command_item(int i, struct prefix_item *item,\n \t\t\t       void *print_command_item_data)\n {\n+\tstruct print_command_item_data *d = print_command_item_data;\n+\n \tif (!item->prefix_length ||\n \t    !is_valid_prefix(item->name, item->prefix_length))\n \t\tprintf(\" %2d: %s\", i + 1, item->name);\n \telse\n-\t\tprintf(\" %3d: [%.*s]%s\", i + 1,\n-\t\t       (int)item->prefix_length, item->name,\n+\t\tprintf(\" %2d: %s%.*s%s%s\", i + 1,\n+\t\t       d->color, (int)item->prefix_length, item->name, d->reset,\n \t\t       item->name + item->prefix_length);\n }\n \n@@ -453,8 +467,9 @@ static void command_prompt_help(struct add_i_state *s)\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n+\tstruct print_command_item_data data;\n \tstruct list_and_choose_options main_loop_opts = {\n-\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n+\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, &data },\n \t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct command_item\n@@ -477,6 +492,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tif (init_add_i_state(r, &s))\n \t\treturn error(\"could not parse `add -i` config\");\n \n+\t/*\n+\t * When color was asked for, use the prompt color for\n+\t * highlighting, otherwise use square brackets.\n+\t */\n+\tif (s.use_color) {\n+\t\tdata.color = s.prompt_color;\n+\t\tdata.reset = s.reset_color;\n+\t} else {\n+\t\tdata.color = \"[\";\n+\t\tdata.reset = \"]\";\n+\t}\n+\n \tstrbuf_addstr(&header, \"      \");\n \tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n-- \ngitgitgadget\n\n"},{"id":"375409","messageId":"e02a52c3acf64657c012744108d7ccc3ecdf1b43.1557768471.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 07/11] Add a function to determine unique prefixes for a list of strings","fromName":"Slavica Djukic via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-05-13T17:27:58Z","receivedAt":"2019-05-13T17:28:14Z","isPatch":true,"sender":{"key":"slavicadj.ip2018@gmail.com","avatar":null},"body":"From: Slavica Djukic <slawica92@hotmail.com>\n\nIn the `git add -i` command, we show unique prefixes of the commands and\nfiles, to give an indication what prefix would select them.\n\nNaturally, the C implementation looks a lot different than the Perl\nimplementation: in Perl, a trie is much easier implemented, while we\nalready have a pretty neat hashmap implementation in C that we use for\nthe purpose of storing (not necessarily unique) prefixes.\n\nThe idea: for each item that we add, we generate prefixes starting with\nthe first letter, then the first two letters, then three, etc, until we\nfind a prefix that is unique (or until the prefix length would be\nlonger than we want). If we encounter a previously-unique prefix on the\nway, we adjust that item's prefix to make it unique again (or we mark it\nas having no unique prefix if we failed to find one). These partial\nprefixes are stored in a hash map (for quick lookup times).\n\nTo make sure that this function works as expected, we add a test using a\nspecial-purpose test helper that was added for that purpose.\n\nNote: We expect the list of prefix items to be passed in as a list of\npointers rather than as regular list to avoid having to copy information\n(the actual items will most likely contain more information than just\nthe name and the length of the unique prefix, but passing in `struct\nprefix_item *` would not allow for that).\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile                   |   2 +\n prefix-map.c               | 109 +++++++++++++++++++++++++++++++++++++\n prefix-map.h               |  40 ++++++++++++++\n t/helper/test-prefix-map.c |  58 ++++++++++++++++++++\n t/helper/test-tool.c       |   1 +\n t/helper/test-tool.h       |   1 +\n t/t0016-prefix-map.sh      |  10 ++++\n 7 files changed, 221 insertions(+)\n create mode 100644 prefix-map.c\n create mode 100644 prefix-map.h\n create mode 100644 t/helper/test-prefix-map.c\n create mode 100755 t/t0016-prefix-map.sh\n\ndiff --git a/Makefile b/Makefile\nindex 18e656a32f..8299b3f17d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -754,6 +754,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o\n TEST_BUILTINS_OBJS += test-parse-options.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n+TEST_BUILTINS_OBJS += test-prefix-map.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\n@@ -967,6 +968,7 @@ LIB_OBJS += patch-ids.o\n LIB_OBJS += path.o\n LIB_OBJS += pathspec.o\n LIB_OBJS += pkt-line.o\n+LIB_OBJS += prefix-map.o\n LIB_OBJS += preload-index.o\n LIB_OBJS += pretty.o\n LIB_OBJS += prio-queue.o\ndiff --git a/prefix-map.c b/prefix-map.c\nnew file mode 100644\nindex 0000000000..747ddb4ebc\n--- /dev/null\n+++ b/prefix-map.c\n@@ -0,0 +1,109 @@\n+#include \"cache.h\"\n+#include \"prefix-map.h\"\n+\n+static int map_cmp(const void *unused_cmp_data,\n+\t\t   const void *entry,\n+\t\t   const void *entry_or_key,\n+\t\t   const void *unused_keydata)\n+{\n+\tconst struct prefix_map_entry *a = entry;\n+\tconst struct prefix_map_entry *b = entry_or_key;\n+\n+\treturn a->prefix_length != b->prefix_length ||\n+\t\tstrncmp(a->name, b->name, a->prefix_length);\n+}\n+\n+static void add_prefix_entry(struct hashmap *map, const char *name,\n+\t\t\t     size_t prefix_length, struct prefix_item *item)\n+{\n+\tstruct prefix_map_entry *result = xmalloc(sizeof(*result));\n+\tresult->name = name;\n+\tresult->prefix_length = prefix_length;\n+\tresult->item = item;\n+\thashmap_entry_init(result, memhash(name, prefix_length));\n+\thashmap_add(map, result);\n+}\n+\n+static void init_prefix_map(struct prefix_map *prefix_map,\n+\t\t\t    int min_prefix_length, int max_prefix_length)\n+{\n+\thashmap_init(&prefix_map->map, map_cmp, NULL, 0);\n+\tprefix_map->min_length = min_prefix_length;\n+\tprefix_map->max_length = max_prefix_length;\n+}\n+\n+static void add_prefix_item(struct prefix_map *prefix_map,\n+\t\t\t    struct prefix_item *item)\n+{\n+\tstruct prefix_map_entry e = { { NULL } }, *e2;\n+\tint j;\n+\n+\te.item = item;\n+\te.name = item->name;\n+\n+\tfor (j = prefix_map->min_length;\n+\t     j <= prefix_map->max_length && e.name[j]; j++) {\n+\t\t/* Avoid breaking UTF-8 multi-byte sequences */\n+\t\tif (!isascii(e.name[j]))\n+\t\t\tbreak;\n+\n+\t\te.prefix_length = j;\n+\t\thashmap_entry_init(&e, memhash(e.name, j));\n+\t\te2 = hashmap_get(&prefix_map->map, &e, NULL);\n+\t\tif (!e2) {\n+\t\t\t/* prefix is unique at this stage */\n+\t\t\titem->prefix_length = j;\n+\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j, item);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (!e2->item)\n+\t\t\tcontinue; /* non-unique prefix */\n+\n+\t\tif (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))\n+\t\t\tBUG(\"unexpected prefix length: %d != %d (%s != %s)\",\n+\t\t\t    j, (int)e2->item->prefix_length, e.name, e2->name);\n+\n+\t\t/* skip common prefix */\n+\t\tfor (; j < prefix_map->max_length && e.name[j]; j++) {\n+\t\t\tif (e.item->name[j] != e2->item->name[j])\n+\t\t\t\tbreak;\n+\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n+\t\t\t\t\t NULL);\n+\t\t}\n+\n+\t\t/* e2 no longer refers to a unique prefix */\n+\t\tif (j < prefix_map->max_length && e2->name[j]) {\n+\t\t\t/* found a new unique prefix for e2's item */\n+\t\t\te2->item->prefix_length = j + 1;\n+\t\t\tadd_prefix_entry(&prefix_map->map, e2->name, j + 1,\n+\t\t\t\t\t e2->item);\n+\t\t}\n+\t\telse\n+\t\t\te2->item->prefix_length = 0;\n+\t\te2->item = NULL;\n+\n+\t\tif (j < prefix_map->max_length && e.name[j]) {\n+\t\t\t/* found a unique prefix for the item */\n+\t\t\te.item->prefix_length = j + 1;\n+\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n+\t\t\t\t\t e.item);\n+\t\t} else\n+\t\t\t/* item has no (short enough) unique prefix */\n+\t\t\te.item->prefix_length = 0;\n+\n+\t\tbreak;\n+\t}\n+}\n+\n+void find_unique_prefixes(struct prefix_item **list, size_t nr,\n+\t\t\t  int min_length, int max_length)\n+{\n+\tint i;\n+\tstruct prefix_map prefix_map;\n+\n+\tinit_prefix_map(&prefix_map, min_length, max_length);\n+\tfor (i = 0; i < nr; i++)\n+\t\tadd_prefix_item(&prefix_map, list[i]);\n+\thashmap_free(&prefix_map.map, 1);\n+}\ndiff --git a/prefix-map.h b/prefix-map.h\nnew file mode 100644\nindex 0000000000..ce3b8a4a32\n--- /dev/null\n+++ b/prefix-map.h\n@@ -0,0 +1,40 @@\n+#ifndef PREFIX_MAP_H\n+#define PREFIX_MAP_H\n+\n+#include \"hashmap.h\"\n+\n+struct prefix_item {\n+\tconst char *name;\n+\tsize_t prefix_length;\n+};\n+\n+struct prefix_map_entry {\n+\tstruct hashmap_entry e;\n+\tconst char *name;\n+\tsize_t prefix_length;\n+\t/* if item is NULL, the prefix is not unique */\n+\tstruct prefix_item *item;\n+};\n+\n+struct prefix_map {\n+\tstruct hashmap map;\n+\tint min_length, max_length;\n+};\n+\n+/*\n+ * Find unique prefixes in a given list of strings.\n+ *\n+ * Typically, the `struct prefix_item` information will be but a field in the\n+ * actual item struct; For this reason, the `list` parameter is specified as a\n+ * list of pointers to the items.\n+ *\n+ * The `min_length`/`max_length` parameters define what length the unique\n+ * prefixes should have.\n+ *\n+ * If no unique prefix could be found for a given item, its `prefix_length`\n+ * will be set to 0.\n+ */\n+void find_unique_prefixes(struct prefix_item **list, size_t nr,\n+\t\t\t  int min_length, int max_length);\n+\n+#endif\ndiff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c\nnew file mode 100644\nindex 0000000000..3f1c90eaf0\n--- /dev/null\n+++ b/t/helper/test-prefix-map.c\n@@ -0,0 +1,58 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"prefix-map.h\"\n+\n+static size_t test_count, failed_count;\n+\n+static void check(int succeeded, const char *file, size_t line_no,\n+\t\t  const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\ttest_count++;\n+\tif (succeeded)\n+\t\treturn;\n+\n+\tva_start(ap, fmt);\n+\tfprintf(stderr, \"%s:%d: \", file, (int)line_no);\n+\tvfprintf(stderr, fmt, ap);\n+\tfputc('\\n', stderr);\n+\tva_end(ap);\n+\n+\tfailed_count++;\n+}\n+\n+#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \\\n+\tcheck(expect == actual, __FILE__, __LINE__, \\\n+\t      \"size_t's do not match: %\" \\\n+\t      PRIdMAX \" != %\" PRIdMAX \" (%s) (%s)\", \\\n+\t      (intmax_t)expect, (intmax_t)actual, #actual, hint)\n+\n+int cmd__prefix_map(int argc, const char **argv)\n+{\n+#define NR 5\n+\tstruct prefix_item items[NR] = {\n+\t\t{ \"unique\" },\n+\t\t{ \"hell\" },\n+\t\t{ \"hello\" },\n+\t\t{ \"wok\" },\n+\t\t{ \"world\" },\n+\t};\n+\tstruct prefix_item *list[NR] = {\n+\t\titems, items + 1, items + 2, items + 3, items + 4\n+\t};\n+\n+\tfind_unique_prefixes(list, NR, 1, 3);\n+\n+#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \\\n+\tEXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \\\n+\t\t\t     list[index]->name)\n+\n+\tEXPECT_PREFIX_LENGTH_EQUALS(1, 0);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(0, 1);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(0, 2);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(3, 3);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(3, 4);\n+\n+\treturn !!failed_count;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 99db7409b8..d6a92a8699 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -32,6 +32,7 @@ static struct test_cmd cmds[] = {\n \t{ \"parse-options\", cmd__parse_options },\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n+\t{ \"prefix-map\", cmd__prefix_map },\n \t{ \"prio-queue\", cmd__prio_queue },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 25abed1cf2..33a089ee4e 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -29,6 +29,7 @@ int cmd__online_cpus(int argc, const char **argv);\n int cmd__parse_options(int argc, const char **argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n+int cmd__prefix_map(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t0016-prefix-map.sh b/t/t0016-prefix-map.sh\nnew file mode 100755\nindex 0000000000..187fa92aec\n--- /dev/null\n+++ b/t/t0016-prefix-map.sh\n@@ -0,0 +1,10 @@\n+#!/bin/sh\n+\n+test_description='basic tests for prefix map'\n+. ./test-lib.sh\n+\n+test_expect_success 'prefix map' '\n+\ttest-tool prefix-map\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"375411","messageId":"83d92a9762b4c10d74d804a6104b5ae76330ca17.1557768471.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 05/11] built-in add -i: color the header in the `status` command","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-05-13T17:27:57Z","receivedAt":"2019-05-13T17:28:14Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nFor simplicity, we only implemented the `status` command without colors.\nThis patch starts adding color, matching what the Perl script\n`git-add--interactive.perl` does.\n\nOriginal-Patch-By: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 60 ++++++++++++++++++++++++++++++++++++++++++-----\n 1 file changed, 54 insertions(+), 6 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 2dbf29dee2..6c2fca12c1 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -1,9 +1,51 @@\n #include \"cache.h\"\n #include \"add-interactive.h\"\n+#include \"color.h\"\n+#include \"config.h\"\n #include \"diffcore.h\"\n #include \"revision.h\"\n #include \"refs.h\"\n \n+struct add_i_state {\n+\tstruct repository *r;\n+\tint use_color;\n+\tchar header_color[COLOR_MAXLEN];\n+};\n+\n+static void init_color(struct repository *r, struct add_i_state *s,\n+\t\t       const char *slot_name, char *dst,\n+\t\t       const char *default_color)\n+{\n+\tchar *key = xstrfmt(\"color.interactive.%s\", slot_name);\n+\tconst char *value;\n+\n+\tif (!s->use_color)\n+\t\tdst[0] = '\\0';\n+\telse if (repo_config_get_value(r, key, &value) ||\n+\t\t color_parse(value, dst))\n+\t\tstrlcpy(dst, default_color, COLOR_MAXLEN);\n+\n+\tfree(key);\n+}\n+\n+static int init_add_i_state(struct repository *r, struct add_i_state *s)\n+{\n+\tconst char *value;\n+\n+\ts->r = r;\n+\n+\tif (repo_config_get_value(r, \"color.interactive\", &value))\n+\t\ts->use_color = -1;\n+\telse\n+\t\ts->use_color =\n+\t\t\tgit_config_colorbool(\"color.interactive\", value);\n+\ts->use_color = want_color(s->use_color);\n+\n+\tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n+\n+\treturn 0;\n+}\n+\n struct item {\n \tconst char *name;\n };\n@@ -14,7 +56,8 @@ struct list_options {\n \tvoid *print_item_data;\n };\n \n-static void list(struct item **list, size_t nr, struct list_options *opts)\n+static void list(struct item **list, size_t nr,\n+\t\t struct add_i_state *s, struct list_options *opts)\n {\n \tint i;\n \n@@ -22,7 +65,8 @@ static void list(struct item **list, size_t nr, struct list_options *opts)\n \t\treturn;\n \n \tif (opts->header)\n-\t\tprintf(\"%s\\n\", opts->header);\n+\t\tcolor_fprintf_ln(stdout, s->header_color,\n+\t\t\t\t \"%s\", opts->header);\n \n \tfor (i = 0; i < nr; i++) {\n \t\topts->print_item(i, list[i], opts->print_item_data);\n@@ -226,16 +270,16 @@ static void print_file_item(int i, struct item *item,\n \tprintf(\" %2d: %s\", i + 1, d->buf.buf);\n }\n \n-static int run_status(struct repository *r, const struct pathspec *ps,\n+static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \t\t      struct file_list *files, struct list_options *opts)\n {\n \treset_file_list(files);\n \n-\tif (get_modified_files(r, files, ps) < 0)\n+\tif (get_modified_files(s->r, files, ps) < 0)\n \t\treturn -1;\n \n \tif (files->nr)\n-\t\tlist((struct item **)files->file, files->nr, opts);\n+\t\tlist((struct item **)files->file, files->nr, s, opts);\n \tputchar('\\n');\n \n \treturn 0;\n@@ -243,6 +287,7 @@ static int run_status(struct repository *r, const struct pathspec *ps,\n \n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n+\tstruct add_i_state s = { NULL };\n \tstruct print_file_item_data print_file_item_data = {\n \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n \t};\n@@ -253,13 +298,16 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tstruct file_list files = { NULL };\n \tint res = 0;\n \n+\tif (init_add_i_state(r, &s))\n+\t\treturn error(\"could not parse `add -i` config\");\n+\n \tstrbuf_addstr(&header, \"      \");\n \tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n \topts.header = header.buf;\n \n \trepo_refresh_and_write_index(r, REFRESH_QUIET, 1);\n-\tif (run_status(r, ps, &files, &opts) < 0)\n+\tif (run_status(&s, ps, &files, &opts) < 0)\n \t\tres = -1;\n \n \trelease_file_list(&files);\n-- \ngitgitgadget\n\n"},{"id":"375410","messageId":"7378af60ad48bc7a6b8597cff06bedba757b20e3.1557768472.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 09/11] built-in add -i: support `?` (prompt help)","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-05-13T17:28:00Z","receivedAt":"2019-05-13T17:28:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWith this change, we print out the same colored help text that the\nPerl-based `git add -i` prints in the main loop when question mark is\nentered.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 22 +++++++++++++++++++++-\n 1 file changed, 21 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 7ff87bae1b..1435c12be9 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -11,6 +11,7 @@ struct add_i_state {\n \tstruct repository *r;\n \tint use_color;\n \tchar header_color[COLOR_MAXLEN];\n+\tchar help_color[COLOR_MAXLEN];\n };\n \n static void init_color(struct repository *r, struct add_i_state *s,\n@@ -43,6 +44,7 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)\n \ts->use_color = want_color(s->use_color);\n \n \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n+\tinit_color(r, s, \"help\", s->help_color, GIT_COLOR_BOLD_RED);\n \n \treturn 0;\n }\n@@ -104,6 +106,7 @@ struct list_and_choose_options {\n \tstruct list_options list_opts;\n \n \tconst char *prompt;\n+\tvoid (*print_help)(struct add_i_state *s);\n };\n \n #define LIST_AND_CHOOSE_ERROR (-1)\n@@ -144,6 +147,11 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \t\tif (!input.len)\n \t\t\tbreak;\n \n+\t\tif (!strcmp(input.buf, \"?\")) {\n+\t\t\topts->print_help(s);\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tp = input.buf;\n \t\tfor (;;) {\n \t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n@@ -430,12 +438,24 @@ struct command_item {\n \t\t       struct file_list *files, struct list_options *opts);\n };\n \n+static void command_prompt_help(struct add_i_state *s)\n+{\n+\tconst char *help_color = s->help_color;\n+\tcolor_fprintf_ln(stdout, help_color, \"%s\", _(\"Prompt help:\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"1          - %s\",\n+\t\t\t _(\"select a numbered item\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"foo        - %s\",\n+\t\t\t _(\"select item based on unique prefix\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"           - %s\",\n+\t\t\t _(\"(empty) select nothing\"));\n+}\n+\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n \tstruct list_and_choose_options main_loop_opts = {\n \t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n-\t\tN_(\"What now\")\n+\t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct command_item\n \t\tstatus = { { \"status\" }, run_status };\n-- \ngitgitgadget\n\n"},{"id":"375412","messageId":"ed53346b92ea9573e07d588243da303a8b63c610.1557768471.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 01/11] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-05-13T17:27:53Z","receivedAt":"2019-05-13T17:28:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis is hardly the first conversion of a Git command that is implemented\nas a script to a built-in. So far, the most successful strategy for such\nconversions has been to add a built-in helper and call that for more and\nmore functionality from the script, as more and more parts are\nconverted.\n\nWith the interactive add, we choose a different strategy. The sole\nreason for this is that on Windows (where such a conversion has the most\nbenefits in terms of speed and robustness) we face the very specific\nproblem that a `system()` call in Perl seems to close `stdin` in the\nparent process when the spawned process consumes even one character from\n`stdin`. And that just does not work for us here, as it would stop the\nmain loop as soon as any interactive command was performed by the\nhelper. Which is almost all of the commands in `git add -i`.\n\nIt is almost as if Perl told us once again that it does not want us to\nuse it on Windows.\n\nInstead, we follow the opposite route where we start with a bare-bones\nversion of the built-in interactive add, guarded by the new\n`add.interactive.useBuiltin` config variable, and then add more and more\nfunctionality to it, until it is feature complete.\n\nAt this point, the built-in version of `git add -i` only states that it\ncannot do anything yet ;-)\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config/add.txt |  5 +++++\n Makefile                     |  1 +\n add-interactive.c            |  7 +++++++\n add-interactive.h            |  8 ++++++++\n builtin/add.c                | 10 ++++++++++\n t/README                     |  4 ++++\n 6 files changed, 35 insertions(+)\n create mode 100644 add-interactive.c\n create mode 100644 add-interactive.h\n\ndiff --git a/Documentation/config/add.txt b/Documentation/config/add.txt\nindex 4d753f006e..c9f748f81c 100644\n--- a/Documentation/config/add.txt\n+++ b/Documentation/config/add.txt\n@@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::\n \toption of linkgit:git-add[1].  `add.ignore-errors` is deprecated,\n \tas it does not follow the usual naming convention for configuration\n \tvariables.\n+\n+add.interactive.useBuiltin::\n+\t[EXPERIMENTAL] Set to `true` to use the experimental built-in\n+\timplementation of the interactive version of linkgit:git-add[1]\n+\tinstead of the Perl script version. Is `false` by default.\ndiff --git a/Makefile b/Makefile\nindex c5240942f2..18e656a32f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -848,6 +848,7 @@ LIB_H = $(shell $(FIND) . \\\n \t-name '*.h' -print)\n \n LIB_OBJS += abspath.o\n+LIB_OBJS += add-interactive.o\n LIB_OBJS += advice.o\n LIB_OBJS += alias.o\n LIB_OBJS += alloc.o\ndiff --git a/add-interactive.c b/add-interactive.c\nnew file mode 100644\nindex 0000000000..482e458dc6\n--- /dev/null\n+++ b/add-interactive.c\n@@ -0,0 +1,7 @@\n+#include \"cache.h\"\n+#include \"add-interactive.h\"\n+\n+int run_add_i(struct repository *r, const struct pathspec *ps)\n+{\n+\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n+}\ndiff --git a/add-interactive.h b/add-interactive.h\nnew file mode 100644\nindex 0000000000..7043b8741d\n--- /dev/null\n+++ b/add-interactive.h\n@@ -0,0 +1,8 @@\n+#ifndef ADD_INTERACTIVE_H\n+#define ADD_INTERACTIVE_H\n+\n+struct repository;\n+struct pathspec;\n+int run_add_i(struct repository *r, const struct pathspec *ps);\n+\n+#endif\ndiff --git a/builtin/add.c b/builtin/add.c\nindex db2dfa4350..e19d66e894 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -20,6 +20,7 @@\n #include \"bulk-checkin.h\"\n #include \"argv-array.h\"\n #include \"submodule.h\"\n+#include \"add-interactive.h\"\n \n static const char * const builtin_add_usage[] = {\n \tN_(\"git add [<options>] [--] <pathspec>...\"),\n@@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,\n {\n \tint status, i;\n \tstruct argv_array argv = ARGV_ARRAY_INIT;\n+\tint use_builtin_add_i =\n+\t\tgit_env_bool(\"GIT_TEST_ADD_I_USE_BUILTIN\", -1);\n+\tif (use_builtin_add_i < 0)\n+\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n+\t\t\t\t    &use_builtin_add_i);\n+\n+\tif (use_builtin_add_i == 1 && !patch_mode)\n+\t\treturn !!run_add_i(the_repository, pathspec);\n \n \targv_array_push(&argv, \"add--interactive\");\n \tif (patch_mode)\n@@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb)\n \t\tignore_add_errors = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \ndiff --git a/t/README b/t/README\nindex 886bbec5bc..6408a1847e 100644\n--- a/t/README\n+++ b/t/README\n@@ -383,6 +383,10 @@ GIT_TEST_REBASE_USE_BUILTIN=<boolean>, when false, disables the\n builtin version of git-rebase. See 'rebase.useBuiltin' in\n git-config(1).\n \n+GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the\n+builtin version of git add -i. See 'add.interactive.useBuiltin' in\n+git-config(1).\n+\n GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading\n of the index for the whole test suite by bypassing the default number of\n cache entries and thread minimums. Setting this to 1 will make the\n-- \ngitgitgadget\n\n"},{"id":"375413","messageId":"5e23c0756b5ee543437d0dbf4e9f685df6341bdf.1557768471.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 03/11] built-in add -i: implement the `status` command","fromName":"Daniel Ferreira via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-05-13T17:27:55Z","receivedAt":"2019-05-13T17:28:17Z","isPatch":true,"sender":{"key":"bnmvco@gmail.com","avatar":"https://avatars.githubusercontent.com/u/735397?v=4"},"body":"From: Daniel Ferreira <bnmvco@gmail.com>\n\nThis implements the `status` command of `git add -i`. The data\nstructures introduced in this commit will be extended as needed later.\n\nAt this point, we re-implement only part of the `list_and_choose()`\nfunction of the Perl script `git-add--interactive.perl` and call it\n`list()`. It does not yet color anything, or do columns, or allow user\ninput.\n\nOver the course of the next commits, we will introduce a\n`list_and_choose()` function that uses `list()` to display the list of\noptions and let the user choose one or more of the displayed items. This\nwill be used to implement the main loop of the built-in `git add -i`, at\nwhich point the new `status` command can actually be used.\n\nNote that we pass the list of items as a `struct item **` as opposed to\na `struct item *`, to allow for the actual items to contain much more\ninformation than merely the name.\n\nSigned-off-by: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 265 +++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 264 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 482e458dc6..59b28011f7 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -1,7 +1,270 @@\n #include \"cache.h\"\n #include \"add-interactive.h\"\n+#include \"diffcore.h\"\n+#include \"revision.h\"\n+#include \"refs.h\"\n+\n+struct item {\n+\tconst char *name;\n+};\n+\n+struct list_options {\n+\tconst char *header;\n+\tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n+\tvoid *print_item_data;\n+};\n+\n+static void list(struct item **list, size_t nr, struct list_options *opts)\n+{\n+\tint i;\n+\n+\tif (!nr)\n+\t\treturn;\n+\n+\tif (opts->header)\n+\t\tprintf(\"%s\\n\", opts->header);\n+\n+\tfor (i = 0; i < nr; i++) {\n+\t\topts->print_item(i, list[i], opts->print_item_data);\n+\t\tputchar('\\n');\n+\t}\n+}\n+\n+struct adddel {\n+\tuintmax_t add, del;\n+\tunsigned seen:1, binary:1;\n+};\n+\n+struct file_list {\n+\tstruct file_item {\n+\t\tstruct item item;\n+\t\tstruct adddel index, worktree;\n+\t} **file;\n+\tsize_t nr, alloc;\n+};\n+\n+static void add_file_item(struct file_list *list, const char *name)\n+{\n+\tstruct file_item *item;\n+\n+\tFLEXPTR_ALLOC_STR(item, item.name, name);\n+\n+\tALLOC_GROW(list->file, list->nr + 1, list->alloc);\n+\tlist->file[list->nr++] = item;\n+}\n+\n+static void reset_file_list(struct file_list *list)\n+{\n+\tsize_t i;\n+\n+\tfor (i = 0; i < list->nr; i++)\n+\t\tfree(list->file[i]);\n+\tlist->nr = 0;\n+}\n+\n+static void release_file_list(struct file_list *list)\n+{\n+\treset_file_list(list);\n+\tFREE_AND_NULL(list->file);\n+\tlist->alloc = 0;\n+}\n+\n+static int file_item_cmp(const void *a, const void *b)\n+{\n+\tconst struct file_item * const *f1 = a;\n+\tconst struct file_item * const *f2 = b;\n+\n+\treturn strcmp((*f1)->item.name, (*f2)->item.name);\n+}\n+\n+struct pathname_entry {\n+\tstruct hashmap_entry ent;\n+\tsize_t index;\n+\tchar pathname[FLEX_ARRAY];\n+};\n+\n+static int pathname_entry_cmp(const void *unused_cmp_data,\n+\t\t\t      const void *entry, const void *entry_or_key,\n+\t\t\t      const void *pathname)\n+{\n+\tconst struct pathname_entry *e1 = entry, *e2 = entry_or_key;\n+\n+\treturn strcmp(e1->pathname,\n+\t\t      pathname ? (const char *)pathname : e2->pathname);\n+}\n+\n+struct collection_status {\n+\tenum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase;\n+\n+\tconst char *reference;\n+\n+\tstruct file_list *list;\n+\tstruct hashmap file_map;\n+};\n+\n+static void collect_changes_cb(struct diff_queue_struct *q,\n+\t\t\t       struct diff_options *options,\n+\t\t\t       void *data)\n+{\n+\tstruct collection_status *s = data;\n+\tstruct diffstat_t stat = { 0 };\n+\tint i;\n+\n+\tif (!q->nr)\n+\t\treturn;\n+\n+\tcompute_diffstat(options, &stat, q);\n+\n+\tfor (i = 0; i < stat.nr; i++) {\n+\t\tconst char *name = stat.files[i]->name;\n+\t\tint hash = strhash(name);\n+\t\tstruct pathname_entry *entry;\n+\t\tsize_t file_index;\n+\t\tstruct file_item *file;\n+\t\tstruct adddel *adddel;\n+\n+\t\tentry = hashmap_get_from_hash(&s->file_map, hash, name);\n+\t\tif (entry)\n+\t\t\tfile_index = entry->index;\n+\t\telse {\n+\t\t\tFLEX_ALLOC_STR(entry, pathname, name);\n+\t\t\thashmap_entry_init(entry, hash);\n+\t\t\tentry->index = file_index = s->list->nr;\n+\t\t\thashmap_add(&s->file_map, entry);\n+\n+\t\t\tadd_file_item(s->list, name);\n+\t\t}\n+\t\tfile = s->list->file[file_index];\n+\n+\t\tadddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;\n+\t\tadddel->seen = 1;\n+\t\tadddel->add = stat.files[i]->added;\n+\t\tadddel->del = stat.files[i]->deleted;\n+\t\tif (stat.files[i]->is_binary)\n+\t\t\tadddel->binary = 1;\n+\t}\n+}\n+\n+static int get_modified_files(struct repository *r, struct file_list *list,\n+\t\t\t      const struct pathspec *ps)\n+{\n+\tstruct object_id head_oid;\n+\tint is_initial = !resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING,\n+\t\t\t\t\t     &head_oid, NULL);\n+\tstruct collection_status s = { FROM_WORKTREE };\n+\n+\tif (repo_read_index_preload(r, ps, 0) < 0)\n+\t\treturn error(_(\"could not read index\"));\n+\n+\ts.list = list;\n+\thashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);\n+\n+\tfor (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {\n+\t\tstruct rev_info rev;\n+\t\tstruct setup_revision_opt opt = { 0 };\n+\n+\t\topt.def = is_initial ?\n+\t\t\tempty_tree_oid_hex() : oid_to_hex(&head_oid);\n+\n+\t\tinit_revisions(&rev, NULL);\n+\t\tsetup_revisions(0, NULL, &rev, &opt);\n+\n+\t\trev.diffopt.output_format = DIFF_FORMAT_CALLBACK;\n+\t\trev.diffopt.format_callback = collect_changes_cb;\n+\t\trev.diffopt.format_callback_data = &s;\n+\n+\t\tif (ps)\n+\t\t\tcopy_pathspec(&rev.prune_data, ps);\n+\n+\t\tif (s.phase == FROM_INDEX)\n+\t\t\trun_diff_index(&rev, 1);\n+\t\telse {\n+\t\t\trev.diffopt.flags.ignore_dirty_submodules = 1;\n+\t\t\trun_diff_files(&rev, 0);\n+\t\t}\n+\t}\n+\thashmap_free(&s.file_map, 1);\n+\n+\t/* While the diffs are ordered already, we ran *two* diffs... */\n+\tQSORT(list->file, list->nr, file_item_cmp);\n+\n+\treturn 0;\n+}\n+\n+static void populate_wi_changes(struct strbuf *buf,\n+\t\t\t\tstruct adddel *ad, const char *no_changes)\n+{\n+\tif (ad->binary)\n+\t\tstrbuf_addstr(buf, _(\"binary\"));\n+\telse if (ad->seen)\n+\t\tstrbuf_addf(buf, \"+%\"PRIuMAX\"/-%\"PRIuMAX,\n+\t\t\t    (uintmax_t)ad->add, (uintmax_t)ad->del);\n+\telse\n+\t\tstrbuf_addstr(buf, no_changes);\n+}\n+\n+struct print_file_item_data {\n+\tconst char *modified_fmt;\n+\tstruct strbuf buf, index, worktree;\n+};\n+\n+static void print_file_item(int i, struct item *item,\n+\t\t\t    void *print_file_item_data)\n+{\n+\tstruct file_item *c = (struct file_item *)item;\n+\tstruct print_file_item_data *d = print_file_item_data;\n+\n+\tstrbuf_reset(&d->index);\n+\tstrbuf_reset(&d->worktree);\n+\tstrbuf_reset(&d->buf);\n+\n+\tpopulate_wi_changes(&d->worktree, &c->worktree, _(\"nothing\"));\n+\tpopulate_wi_changes(&d->index, &c->index, _(\"unchanged\"));\n+\tstrbuf_addf(&d->buf, d->modified_fmt,\n+\t\t    d->index.buf, d->worktree.buf, item->name);\n+\n+\tprintf(\" %2d: %s\", i + 1, d->buf.buf);\n+}\n+\n+static int run_status(struct repository *r, const struct pathspec *ps,\n+\t\t      struct file_list *files, struct list_options *opts)\n+{\n+\treset_file_list(files);\n+\n+\tif (get_modified_files(r, files, ps) < 0)\n+\t\treturn -1;\n+\n+\tif (files->nr)\n+\t\tlist((struct item **)files->file, files->nr, opts);\n+\tputchar('\\n');\n+\n+\treturn 0;\n+}\n \n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n-\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n+\tstruct print_file_item_data print_file_item_data = {\n+\t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n+\t};\n+\tstruct list_options opts = {\n+\t\tNULL, print_file_item, &print_file_item_data\n+\t};\n+\tstruct strbuf header = STRBUF_INIT;\n+\tstruct file_list files = { NULL };\n+\tint res = 0;\n+\n+\tstrbuf_addstr(&header, \"      \");\n+\tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n+\t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n+\topts.header = header.buf;\n+\n+\tres = run_status(r, ps, &files, &opts);\n+\n+\trelease_file_list(&files);\n+\tstrbuf_release(&print_file_item_data.buf);\n+\tstrbuf_release(&print_file_item_data.index);\n+\tstrbuf_release(&print_file_item_data.worktree);\n+\tstrbuf_release(&header);\n+\n+\treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"378964","messageId":"pull.170.v3.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far)","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-16T14:58:36Z","receivedAt":"2019-07-16T14:58:41Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is the first leg on the long journey to a fully built-in git add -i \n(next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3\n[https://github.com/gitgitgadget/git/pull/172], 4\n[https://github.com/gitgitgadget/git/pull/173], 5\n[https://github.com/gitgitgadget/git/pull/174], and 6\n[https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are\nnot necessarily up to date, and will be re-targeted to the appropriate\nbranches in https://github.com/gitster/git as soon as Junio picks them up.\n\nThis here patch series reflects the part that was submitted a couple of\ntimes (see https://github.com/gitgitgadget/git/pull/103) during the\nOutreachy project by Slavica Ðukic that continued the journey based on an\ninitial patch series by Daniel Ferreira.\n\nIt only implements the status and the help part, in the interest of making\nthe review remotely more reviewable.\n\nAs I am a heavy user of git add -p myself and use a patched version for\nweeks already (it is so nice to not suffer over one second startup until the\nMSYS2 Perl finally shows me anything, instead it feels instantaneous), I\nintegrated these patch series into Git for Windows' master already, as an\nopt-in feature guarded by the config variable add.interactive.useBuiltin \n(and Git for Windows' installer is prepared to detect this version and offer\nthe option in the graphical user interface).\n\nI had planned on submitting this before v2.22.0-rc0, but there was such a\nbacklog of builds from a big pushout that I had to wait ;-)\n\nChanges since v2:\n\n * Rebased to master to avoid merge conflicts.\n * Renumbered the prefix-map test to avoid conflicts with two patch series\n   that are currently in-flight in pu.\n\nChanges since v1:\n\n * The config machinery was reworked completely, to not use a callback to \n   git_config(), but instead to query the config via the repo_config_get_*() \n   functions. This also prevents a future \"Huh???\" moment: the internal add\n   --interactive API accepts a parameter of type struct repository *r, but\n   the previous configuration did not use that to query the config (and\n   could in the future be a repository other than the_repository).\n   \n   \n * As a consequence, the color sequences are no longer stored in file-local\n   variables, but passed around via a struct.\n   \n   \n * Instead of using the magical constant -2 to quit the main loop, it is now\n   defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is\n   defined as -1 and used where appropriate).\n   \n   \n * Improved the add_prefix_item() function by avoiding buffer overruns, not\n   reusing the struct that is used for lookup also for adding the new item,\n   and by strengthening the bug check.\n\nDaniel Ferreira (2):\n  diff: export diffstat interface\n  built-in add -i: implement the `status` command\n\nJohannes Schindelin (6):\n  Start to implement a built-in version of `git add --interactive`\n  built-in add -i: refresh the index before running `status`\n  built-in add -i: color the header in the `status` command\n  built-in add -i: implement the main loop\n  built-in add -i: support `?` (prompt help)\n  built-in add -i: implement the `help` command\n\nSlavica Djukic (3):\n  Add a function to determine unique prefixes for a list of strings\n  built-in add -i: show unique prefixes of the commands\n  built-in add -i: use color in the main loop\n\n Documentation/config/add.txt |   5 +\n Makefile                     |   3 +\n add-interactive.c            | 558 +++++++++++++++++++++++++++++++++++\n add-interactive.h            |   8 +\n builtin/add.c                |  10 +\n diff.c                       |  37 +--\n diff.h                       |  19 ++\n prefix-map.c                 | 109 +++++++\n prefix-map.h                 |  40 +++\n repository.c                 |  19 ++\n repository.h                 |   7 +\n t/README                     |   4 +\n t/helper/test-prefix-map.c   |  58 ++++\n t/helper/test-tool.c         |   1 +\n t/helper/test-tool.h         |   1 +\n t/t0018-prefix-map.sh        |  10 +\n t/t3701-add-interactive.sh   |  25 ++\n 17 files changed, 892 insertions(+), 22 deletions(-)\n create mode 100644 add-interactive.c\n create mode 100644 add-interactive.h\n create mode 100644 prefix-map.c\n create mode 100644 prefix-map.h\n create mode 100644 t/helper/test-prefix-map.c\n create mode 100755 t/t0018-prefix-map.sh\n\n\nbase-commit: 9d418600f4d10dcbbfb0b5fdbc71d509e03ba719\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/170\n\nRange-diff vs v2:\n\n  1:  ed53346b92 !  1:  0a5ec9345d Start to implement a built-in version of `git add --interactive`\n     @@ -47,7 +47,7 @@\n       --- a/Makefile\n       +++ b/Makefile\n      @@\n     - \t-name '*.h' -print)\n     + \t-name '*.h' -print))\n       \n       LIB_OBJS += abspath.o\n      +LIB_OBJS += add-interactive.o\n     @@ -121,7 +121,7 @@\n       --- a/t/README\n       +++ b/t/README\n      @@\n     - builtin version of git-rebase. See 'rebase.useBuiltin' in\n     + built-in version of git-stash. See 'stash.useBuiltin' in\n       git-config(1).\n       \n      +GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the\n  2:  bc99009fbf =  2:  c7a377890d diff: export diffstat interface\n  3:  5e23c0756b =  3:  b93b055ebe built-in add -i: implement the `status` command\n  4:  8cafc6ae8d =  4:  daff24074a built-in add -i: refresh the index before running `status`\n  5:  83d92a9762 =  5:  15f18f5b3e built-in add -i: color the header in the `status` command\n  6:  3eec219124 =  6:  175409aaae built-in add -i: implement the main loop\n  7:  e02a52c3ac !  7:  3000d7d08d Add a function to determine unique prefixes for a list of strings\n     @@ -299,10 +299,10 @@\n       int cmd__reach(int argc, const char **argv);\n       int cmd__read_cache(int argc, const char **argv);\n      \n     - diff --git a/t/t0016-prefix-map.sh b/t/t0016-prefix-map.sh\n     + diff --git a/t/t0018-prefix-map.sh b/t/t0018-prefix-map.sh\n       new file mode 100755\n       --- /dev/null\n     - +++ b/t/t0016-prefix-map.sh\n     + +++ b/t/t0018-prefix-map.sh\n      @@\n      +#!/bin/sh\n      +\n  8:  ced9b6aced =  8:  e23ddebfbf built-in add -i: show unique prefixes of the commands\n  9:  7378af60ad =  9:  d8c012fce8 built-in add -i: support `?` (prompt help)\n 10:  36edef85e6 = 10:  8121a3ca1b built-in add -i: use color in the main loop\n 11:  266dbf2a6b ! 11:  db70c6475d built-in add -i: implement the `help` command\n     @@ -61,9 +61,10 @@\n       --- a/t/t3701-add-interactive.sh\n       +++ b/t/t3701-add-interactive.sh\n      @@\n     - \ttest_cmp expected-2 actual\n     + \ttest_write_lines a b a b a a b a b a >expect &&\n     + \ttest_cmp expect a\n       '\n     - \n     ++\n      +test_expect_success 'show help from add--helper' '\n      +\tgit reset --hard &&\n      +\tcat >expect <<-EOF &&\n\n-- \ngitgitgadget\n"},{"id":"378965","messageId":"0a5ec9345d2f9cc6cd348231219d4af428a28e94.1563289115.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-16T14:58:37Z","receivedAt":"2019-07-16T14:58:41Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis is hardly the first conversion of a Git command that is implemented\nas a script to a built-in. So far, the most successful strategy for such\nconversions has been to add a built-in helper and call that for more and\nmore functionality from the script, as more and more parts are\nconverted.\n\nWith the interactive add, we choose a different strategy. The sole\nreason for this is that on Windows (where such a conversion has the most\nbenefits in terms of speed and robustness) we face the very specific\nproblem that a `system()` call in Perl seems to close `stdin` in the\nparent process when the spawned process consumes even one character from\n`stdin`. And that just does not work for us here, as it would stop the\nmain loop as soon as any interactive command was performed by the\nhelper. Which is almost all of the commands in `git add -i`.\n\nIt is almost as if Perl told us once again that it does not want us to\nuse it on Windows.\n\nInstead, we follow the opposite route where we start with a bare-bones\nversion of the built-in interactive add, guarded by the new\n`add.interactive.useBuiltin` config variable, and then add more and more\nfunctionality to it, until it is feature complete.\n\nAt this point, the built-in version of `git add -i` only states that it\ncannot do anything yet ;-)\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config/add.txt |  5 +++++\n Makefile                     |  1 +\n add-interactive.c            |  7 +++++++\n add-interactive.h            |  8 ++++++++\n builtin/add.c                | 10 ++++++++++\n t/README                     |  4 ++++\n 6 files changed, 35 insertions(+)\n create mode 100644 add-interactive.c\n create mode 100644 add-interactive.h\n\ndiff --git a/Documentation/config/add.txt b/Documentation/config/add.txt\nindex 4d753f006e..c9f748f81c 100644\n--- a/Documentation/config/add.txt\n+++ b/Documentation/config/add.txt\n@@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::\n \toption of linkgit:git-add[1].  `add.ignore-errors` is deprecated,\n \tas it does not follow the usual naming convention for configuration\n \tvariables.\n+\n+add.interactive.useBuiltin::\n+\t[EXPERIMENTAL] Set to `true` to use the experimental built-in\n+\timplementation of the interactive version of linkgit:git-add[1]\n+\tinstead of the Perl script version. Is `false` by default.\ndiff --git a/Makefile b/Makefile\nindex 98a0588416..0a47200f47 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -824,6 +824,7 @@ LIB_H := $(sort $(shell git ls-files '*.h' ':!t/' ':!Documentation/' 2>/dev/null\n \t-name '*.h' -print))\n \n LIB_OBJS += abspath.o\n+LIB_OBJS += add-interactive.o\n LIB_OBJS += advice.o\n LIB_OBJS += alias.o\n LIB_OBJS += alloc.o\ndiff --git a/add-interactive.c b/add-interactive.c\nnew file mode 100644\nindex 0000000000..482e458dc6\n--- /dev/null\n+++ b/add-interactive.c\n@@ -0,0 +1,7 @@\n+#include \"cache.h\"\n+#include \"add-interactive.h\"\n+\n+int run_add_i(struct repository *r, const struct pathspec *ps)\n+{\n+\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n+}\ndiff --git a/add-interactive.h b/add-interactive.h\nnew file mode 100644\nindex 0000000000..7043b8741d\n--- /dev/null\n+++ b/add-interactive.h\n@@ -0,0 +1,8 @@\n+#ifndef ADD_INTERACTIVE_H\n+#define ADD_INTERACTIVE_H\n+\n+struct repository;\n+struct pathspec;\n+int run_add_i(struct repository *r, const struct pathspec *ps);\n+\n+#endif\ndiff --git a/builtin/add.c b/builtin/add.c\nindex dd18e5c9b6..4f625691b5 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -20,6 +20,7 @@\n #include \"bulk-checkin.h\"\n #include \"argv-array.h\"\n #include \"submodule.h\"\n+#include \"add-interactive.h\"\n \n static const char * const builtin_add_usage[] = {\n \tN_(\"git add [<options>] [--] <pathspec>...\"),\n@@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,\n {\n \tint status, i;\n \tstruct argv_array argv = ARGV_ARRAY_INIT;\n+\tint use_builtin_add_i =\n+\t\tgit_env_bool(\"GIT_TEST_ADD_I_USE_BUILTIN\", -1);\n+\tif (use_builtin_add_i < 0)\n+\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n+\t\t\t\t    &use_builtin_add_i);\n+\n+\tif (use_builtin_add_i == 1 && !patch_mode)\n+\t\treturn !!run_add_i(the_repository, pathspec);\n \n \targv_array_push(&argv, \"add--interactive\");\n \tif (patch_mode)\n@@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb)\n \t\tignore_add_errors = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \ndiff --git a/t/README b/t/README\nindex 9747971d58..28999cebd3 100644\n--- a/t/README\n+++ b/t/README\n@@ -397,6 +397,10 @@ GIT_TEST_STASH_USE_BUILTIN=<boolean>, when false, disables the\n built-in version of git-stash. See 'stash.useBuiltin' in\n git-config(1).\n \n+GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the\n+builtin version of git add -i. See 'add.interactive.useBuiltin' in\n+git-config(1).\n+\n GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading\n of the index for the whole test suite by bypassing the default number of\n cache entries and thread minimums. Setting this to 1 will make the\n-- \ngitgitgadget\n\n"},{"id":"378966","messageId":"c7a377890d84849ea2f63099cfc081420a4de15d.1563289115.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 02/11] diff: export diffstat interface","fromName":"Daniel Ferreira via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-16T14:58:38Z","receivedAt":"2019-07-16T14:58:42Z","isPatch":true,"sender":{"key":"bnmvco@gmail.com","avatar":"https://avatars.githubusercontent.com/u/735397?v=4"},"body":"From: Daniel Ferreira <bnmvco@gmail.com>\n\nMake the diffstat interface (namely, the diffstat_t struct and\ncompute_diffstat) no longer be internal to diff.c and allow it to be used\nby other parts of git.\n\nThis is helpful for code that may want to easily extract information\nfrom files using the diff machinery, while flushing it differently from\nhow the show_* functions used by diff_flush() do it. One example is the\nbuiltin implementation of git-add--interactive's status.\n\nSigned-off-by: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 37 +++++++++++++++----------------------\n diff.h | 19 +++++++++++++++++++\n 2 files changed, 34 insertions(+), 22 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 1ee04e321b..03d6f00d97 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -2489,22 +2489,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)\n \t}\n }\n \n-struct diffstat_t {\n-\tint nr;\n-\tint alloc;\n-\tstruct diffstat_file {\n-\t\tchar *from_name;\n-\t\tchar *name;\n-\t\tchar *print_name;\n-\t\tconst char *comments;\n-\t\tunsigned is_unmerged:1;\n-\t\tunsigned is_binary:1;\n-\t\tunsigned is_renamed:1;\n-\t\tunsigned is_interesting:1;\n-\t\tuintmax_t added, deleted;\n-\t} **files;\n-};\n-\n static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,\n \t\t\t\t\t  const char *name_a,\n \t\t\t\t\t  const char *name_b)\n@@ -6273,12 +6257,7 @@ void diff_flush(struct diff_options *options)\n \t    dirstat_by_line) {\n \t\tstruct diffstat_t diffstat;\n \n-\t\tmemset(&diffstat, 0, sizeof(struct diffstat_t));\n-\t\tfor (i = 0; i < q->nr; i++) {\n-\t\t\tstruct diff_filepair *p = q->queue[i];\n-\t\t\tif (check_pair_status(p))\n-\t\t\t\tdiff_flush_stat(p, options, &diffstat);\n-\t\t}\n+\t\tcompute_diffstat(options, &diffstat, q);\n \t\tif (output_format & DIFF_FORMAT_NUMSTAT)\n \t\t\tshow_numstat(&diffstat, options);\n \t\tif (output_format & DIFF_FORMAT_DIFFSTAT)\n@@ -6611,6 +6590,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)\n \treturn ignored;\n }\n \n+void compute_diffstat(struct diff_options *options,\n+\t\t      struct diffstat_t *diffstat,\n+\t\t      struct diff_queue_struct *q)\n+{\n+\tint i;\n+\n+\tmemset(diffstat, 0, sizeof(struct diffstat_t));\n+\tfor (i = 0; i < q->nr; i++) {\n+\t\tstruct diff_filepair *p = q->queue[i];\n+\t\tif (check_pair_status(p))\n+\t\t\tdiff_flush_stat(p, options, diffstat);\n+\t}\n+}\n+\n void diff_addremove(struct diff_options *options,\n \t\t    int addremove, unsigned mode,\n \t\t    const struct object_id *oid,\ndiff --git a/diff.h b/diff.h\nindex b680b377b2..34fc658946 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -244,6 +244,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);\n void diff_emit_submodule_pipethrough(struct diff_options *o,\n \t\t\t\t     const char *line, int len);\n \n+struct diffstat_t {\n+\tint nr;\n+\tint alloc;\n+\tstruct diffstat_file {\n+\t\tchar *from_name;\n+\t\tchar *name;\n+\t\tchar *print_name;\n+\t\tconst char *comments;\n+\t\tunsigned is_unmerged:1;\n+\t\tunsigned is_binary:1;\n+\t\tunsigned is_renamed:1;\n+\t\tunsigned is_interesting:1;\n+\t\tuintmax_t added, deleted;\n+\t} **files;\n+};\n+\n enum color_diff {\n \tDIFF_RESET = 0,\n \tDIFF_CONTEXT = 1,\n@@ -333,6 +349,9 @@ void diff_change(struct diff_options *,\n \n struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);\n \n+void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,\n+\t\t      struct diff_queue_struct *q);\n+\n #define DIFF_SETUP_REVERSE      \t1\n #define DIFF_SETUP_USE_SIZE_CACHE\t4\n \n-- \ngitgitgadget\n\n"},{"id":"378967","messageId":"15f18f5b3ebe77a79886dbcdb7a900647be943dd.1563289115.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 05/11] built-in add -i: color the header in the `status` command","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-16T14:58:40Z","receivedAt":"2019-07-16T14:58:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nFor simplicity, we only implemented the `status` command without colors.\nThis patch starts adding color, matching what the Perl script\n`git-add--interactive.perl` does.\n\nOriginal-Patch-By: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 60 ++++++++++++++++++++++++++++++++++++++++++-----\n 1 file changed, 54 insertions(+), 6 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 2dbf29dee2..6c2fca12c1 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -1,9 +1,51 @@\n #include \"cache.h\"\n #include \"add-interactive.h\"\n+#include \"color.h\"\n+#include \"config.h\"\n #include \"diffcore.h\"\n #include \"revision.h\"\n #include \"refs.h\"\n \n+struct add_i_state {\n+\tstruct repository *r;\n+\tint use_color;\n+\tchar header_color[COLOR_MAXLEN];\n+};\n+\n+static void init_color(struct repository *r, struct add_i_state *s,\n+\t\t       const char *slot_name, char *dst,\n+\t\t       const char *default_color)\n+{\n+\tchar *key = xstrfmt(\"color.interactive.%s\", slot_name);\n+\tconst char *value;\n+\n+\tif (!s->use_color)\n+\t\tdst[0] = '\\0';\n+\telse if (repo_config_get_value(r, key, &value) ||\n+\t\t color_parse(value, dst))\n+\t\tstrlcpy(dst, default_color, COLOR_MAXLEN);\n+\n+\tfree(key);\n+}\n+\n+static int init_add_i_state(struct repository *r, struct add_i_state *s)\n+{\n+\tconst char *value;\n+\n+\ts->r = r;\n+\n+\tif (repo_config_get_value(r, \"color.interactive\", &value))\n+\t\ts->use_color = -1;\n+\telse\n+\t\ts->use_color =\n+\t\t\tgit_config_colorbool(\"color.interactive\", value);\n+\ts->use_color = want_color(s->use_color);\n+\n+\tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n+\n+\treturn 0;\n+}\n+\n struct item {\n \tconst char *name;\n };\n@@ -14,7 +56,8 @@ struct list_options {\n \tvoid *print_item_data;\n };\n \n-static void list(struct item **list, size_t nr, struct list_options *opts)\n+static void list(struct item **list, size_t nr,\n+\t\t struct add_i_state *s, struct list_options *opts)\n {\n \tint i;\n \n@@ -22,7 +65,8 @@ static void list(struct item **list, size_t nr, struct list_options *opts)\n \t\treturn;\n \n \tif (opts->header)\n-\t\tprintf(\"%s\\n\", opts->header);\n+\t\tcolor_fprintf_ln(stdout, s->header_color,\n+\t\t\t\t \"%s\", opts->header);\n \n \tfor (i = 0; i < nr; i++) {\n \t\topts->print_item(i, list[i], opts->print_item_data);\n@@ -226,16 +270,16 @@ static void print_file_item(int i, struct item *item,\n \tprintf(\" %2d: %s\", i + 1, d->buf.buf);\n }\n \n-static int run_status(struct repository *r, const struct pathspec *ps,\n+static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \t\t      struct file_list *files, struct list_options *opts)\n {\n \treset_file_list(files);\n \n-\tif (get_modified_files(r, files, ps) < 0)\n+\tif (get_modified_files(s->r, files, ps) < 0)\n \t\treturn -1;\n \n \tif (files->nr)\n-\t\tlist((struct item **)files->file, files->nr, opts);\n+\t\tlist((struct item **)files->file, files->nr, s, opts);\n \tputchar('\\n');\n \n \treturn 0;\n@@ -243,6 +287,7 @@ static int run_status(struct repository *r, const struct pathspec *ps,\n \n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n+\tstruct add_i_state s = { NULL };\n \tstruct print_file_item_data print_file_item_data = {\n \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n \t};\n@@ -253,13 +298,16 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tstruct file_list files = { NULL };\n \tint res = 0;\n \n+\tif (init_add_i_state(r, &s))\n+\t\treturn error(\"could not parse `add -i` config\");\n+\n \tstrbuf_addstr(&header, \"      \");\n \tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n \topts.header = header.buf;\n \n \trepo_refresh_and_write_index(r, REFRESH_QUIET, 1);\n-\tif (run_status(r, ps, &files, &opts) < 0)\n+\tif (run_status(&s, ps, &files, &opts) < 0)\n \t\tres = -1;\n \n \trelease_file_list(&files);\n-- \ngitgitgadget\n\n"},{"id":"378968","messageId":"daff24074a604271d66e6a854ea44546b951f489.1563289115.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 04/11] built-in add -i: refresh the index before running `status`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-16T14:58:39Z","receivedAt":"2019-07-16T14:58:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis is what the Perl version does, and therefore it is what the\nbuilt-in version should do, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c |  4 +++-\n repository.c      | 19 +++++++++++++++++++\n repository.h      |  7 +++++++\n 3 files changed, 29 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 59b28011f7..2dbf29dee2 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -258,7 +258,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n \topts.header = header.buf;\n \n-\tres = run_status(r, ps, &files, &opts);\n+\trepo_refresh_and_write_index(r, REFRESH_QUIET, 1);\n+\tif (run_status(r, ps, &files, &opts) < 0)\n+\t\tres = -1;\n \n \trelease_file_list(&files);\n \tstrbuf_release(&print_file_item_data.buf);\ndiff --git a/repository.c b/repository.c\nindex 682c239fe3..def35c40fc 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -275,3 +275,22 @@ int repo_hold_locked_index(struct repository *repo,\n \t\tBUG(\"the repo hasn't been setup\");\n \treturn hold_lock_file_for_update(lf, repo->index_file, flags);\n }\n+\n+int repo_refresh_and_write_index(struct repository *r,\n+\t\t\t\t unsigned int flags, int gentle)\n+{\n+\tstruct lock_file lock_file = LOCK_INIT;\n+\tint fd;\n+\n+\tif (repo_read_index_preload(r, NULL, 0) < 0)\n+\t\treturn error(_(\"could not read index\"));\n+\tfd = repo_hold_locked_index(r, &lock_file, 0);\n+\tif (!gentle && fd < 0)\n+\t\treturn error(_(\"could not lock index for writing\"));\n+\trefresh_index(r->index, flags, NULL, NULL, NULL);\n+\tif (0 <= fd)\n+\t\trepo_update_index_if_able(r, &lock_file);\n+\trollback_lock_file(&lock_file);\n+\n+\treturn 0;\n+}\ndiff --git a/repository.h b/repository.h\nindex 4fb6a5885f..cf5d5bab48 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -157,5 +157,12 @@ int repo_read_index_unmerged(struct repository *);\n  */\n void repo_update_index_if_able(struct repository *, struct lock_file *);\n \n+/*\n+ * Refresh the index and write it out. If the index file could not be\n+ * locked, error out, except in gentle mode. The flags will be passed\n+ * through to refresh_index().\n+ */\n+int repo_refresh_and_write_index(struct repository *r,\n+\t\t\t\t unsigned int flags, int gentle);\n \n #endif /* REPOSITORY_H */\n-- \ngitgitgadget\n\n"},{"id":"378969","messageId":"175409aaae0f3127b92126dc201faa4b41da0870.1563289115.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 06/11] built-in add -i: implement the main loop","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-16T14:58:41Z","receivedAt":"2019-07-16T14:58:47Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe reason why we did not start with the main loop to begin with is that\nit is the first user of `list_and_choose()`, which uses the `list()`\nfunction that we conveniently introduced for use by the `status`\ncommand.\n\nApart from the \"and choose\" part, there are more differences between the\nway the `status` command calls the `list_and_choose()` function in the\nPerl version of `git add -i` compared to the other callers of said\nfunction. The most important ones:\n\n- The list is not only shown, but the user is also asked to make a\n  choice, possibly selecting multiple entries.\n\n- The list of items is prefixed with a marker indicating what items have\n  been selected, if multi-selection is allowed.\n\n- Initially, for each item a unique prefix (if there exists any within\n  the given parameters) is determined, and shown in the list, and\n  accepted as a shortcut for the selection.\n\nThese features will be implemented later, except the part where the user\ncan choose a command. At this stage, though, the built-in `git add -i`\nstill only supports the `status` command, with the remaining commands to\nfollow over the course of the next commits.\n\nIn addition, we also modify `list()` to support displaying the commands\nin columns, even if there is currently only one.\n\nThe Perl script `git-add--interactive.perl` mixed the purposes of the\n\"list\" and the \"and choose\" part into the same function. In the C\nversion, we will keep them separate instead, calling the `list()`\nfunction from the `list_and_choose()` function.\n\nNote that we only have a prompt ending in a single \">\" at this stage;\nlater commits will add commands that display a double \">>\" to indicate\nthat the user is in a different loop than the main one.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 129 +++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 127 insertions(+), 2 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 6c2fca12c1..0ea4f3edb8 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -51,6 +51,7 @@ struct item {\n };\n \n struct list_options {\n+\tint columns;\n \tconst char *header;\n \tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n \tvoid *print_item_data;\n@@ -59,7 +60,7 @@ struct list_options {\n static void list(struct item **list, size_t nr,\n \t\t struct add_i_state *s, struct list_options *opts)\n {\n-\tint i;\n+\tint i, last_lf = 0;\n \n \tif (!nr)\n \t\treturn;\n@@ -70,8 +71,97 @@ static void list(struct item **list, size_t nr,\n \n \tfor (i = 0; i < nr; i++) {\n \t\topts->print_item(i, list[i], opts->print_item_data);\n+\n+\t\tif ((opts->columns) && ((i + 1) % (opts->columns))) {\n+\t\t\tputchar('\\t');\n+\t\t\tlast_lf = 0;\n+\t\t}\n+\t\telse {\n+\t\t\tputchar('\\n');\n+\t\t\tlast_lf = 1;\n+\t\t}\n+\t}\n+\n+\tif (!last_lf)\n \t\tputchar('\\n');\n+}\n+struct list_and_choose_options {\n+\tstruct list_options list_opts;\n+\n+\tconst char *prompt;\n+};\n+\n+#define LIST_AND_CHOOSE_ERROR (-1)\n+#define LIST_AND_CHOOSE_QUIT  (-2)\n+\n+/*\n+ * Returns the selected index.\n+ *\n+ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n+ * `LIST_AND_CHOOSE_QUIT` is returned.\n+ */\n+static ssize_t list_and_choose(struct item **items, size_t nr,\n+\t\t\t       struct add_i_state *s,\n+\t\t\t       struct list_and_choose_options *opts)\n+{\n+\tstruct strbuf input = STRBUF_INIT;\n+\tssize_t res = LIST_AND_CHOOSE_ERROR;\n+\n+\tfor (;;) {\n+\t\tchar *p, *endp;\n+\n+\t\tstrbuf_reset(&input);\n+\n+\t\tlist(items, nr, s, &opts->list_opts);\n+\n+\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n+\t\tfflush(stdout);\n+\n+\t\tif (strbuf_getline(&input, stdin) == EOF) {\n+\t\t\tputchar('\\n');\n+\t\t\tres = LIST_AND_CHOOSE_QUIT;\n+\t\t\tbreak;\n+\t\t}\n+\t\tstrbuf_trim(&input);\n+\n+\t\tif (!input.len)\n+\t\t\tbreak;\n+\n+\t\tp = input.buf;\n+\t\tfor (;;) {\n+\t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n+\t\t\tssize_t index = -1;\n+\n+\t\t\tif (!sep) {\n+\t\t\t\tif (!*p)\n+\t\t\t\t\tbreak;\n+\t\t\t\tp++;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (isdigit(*p)) {\n+\t\t\t\tindex = strtoul(p, &endp, 10) - 1;\n+\t\t\t\tif (endp != p + sep)\n+\t\t\t\t\tindex = -1;\n+\t\t\t}\n+\n+\t\t\tp[sep] = '\\0';\n+\t\t\tif (index < 0 || index >= nr)\n+\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n+\t\t\telse {\n+\t\t\t\tres = index;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tp += sep + 1;\n+\t\t}\n+\n+\t\tif (res != LIST_AND_CHOOSE_ERROR)\n+\t\t\tbreak;\n \t}\n+\n+\tstrbuf_release(&input);\n+\treturn res;\n }\n \n struct adddel {\n@@ -285,17 +375,40 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \treturn 0;\n }\n \n+static void print_command_item(int i, struct item *item,\n+\t\t\t       void *print_command_item_data)\n+{\n+\tprintf(\" %2d: %s\", i + 1, item->name);\n+}\n+\n+struct command_item {\n+\tstruct item item;\n+\tint (*command)(struct add_i_state *s, const struct pathspec *ps,\n+\t\t       struct file_list *files, struct list_options *opts);\n+};\n+\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n+\tstruct list_and_choose_options main_loop_opts = {\n+\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n+\t\tN_(\"What now\")\n+\t};\n+\tstruct command_item\n+\t\tstatus = { { \"status\" }, run_status };\n+\tstruct command_item *commands[] = {\n+\t\t&status\n+\t};\n+\n \tstruct print_file_item_data print_file_item_data = {\n \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n \t};\n \tstruct list_options opts = {\n-\t\tNULL, print_file_item, &print_file_item_data\n+\t\t0, NULL, print_file_item, &print_file_item_data\n \t};\n \tstruct strbuf header = STRBUF_INIT;\n \tstruct file_list files = { NULL };\n+\tssize_t i;\n \tint res = 0;\n \n \tif (init_add_i_state(r, &s))\n@@ -310,6 +423,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tif (run_status(&s, ps, &files, &opts) < 0)\n \t\tres = -1;\n \n+\tfor (;;) {\n+\t\ti = list_and_choose((struct item **)commands,\n+\t\t\t\t    ARRAY_SIZE(commands), &s, &main_loop_opts);\n+\t\tif (i == LIST_AND_CHOOSE_QUIT) {\n+\t\t\tprintf(_(\"Bye.\\n\"));\n+\t\t\tres = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (i != LIST_AND_CHOOSE_ERROR)\n+\t\t\tres = commands[i]->command(&s, ps, &files, &opts);\n+\t}\n+\n \trelease_file_list(&files);\n \tstrbuf_release(&print_file_item_data.buf);\n \tstrbuf_release(&print_file_item_data.index);\n-- \ngitgitgadget\n\n"},{"id":"378970","messageId":"e23ddebfbf17adfa13fe52516bd9130d7a1b6db7.1563289115.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 08/11] built-in add -i: show unique prefixes of the commands","fromName":"Slavica Djukic via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-16T14:58:42Z","receivedAt":"2019-07-16T14:58:48Z","isPatch":true,"sender":{"key":"slavicadj.ip2018@gmail.com","avatar":null},"body":"From: Slavica Djukic <slawica92@hotmail.com>\n\nJust like in the Perl script `git-add--interactive.perl`, for each\ncommand a unique prefix is determined (if there exists any within the\ngiven parameters), and shown in the list, and accepted as a shortcut for\nthe command.\n\nWe use the prefix map implementation that we just added in the previous\ncommit for that purpose.\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n---\n add-interactive.c | 69 ++++++++++++++++++++++++++++++++++++++---------\n 1 file changed, 56 insertions(+), 13 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 0ea4f3edb8..7ff87bae1b 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -5,6 +5,7 @@\n #include \"diffcore.h\"\n #include \"revision.h\"\n #include \"refs.h\"\n+#include \"prefix-map.h\"\n \n struct add_i_state {\n \tstruct repository *r;\n@@ -46,18 +47,32 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)\n \treturn 0;\n }\n \n-struct item {\n-\tconst char *name;\n-};\n+static ssize_t find_unique(const char *string,\n+\t\t\t   struct prefix_item **list, size_t nr)\n+{\n+\tssize_t found = -1, i;\n+\n+\tfor (i = 0; i < nr; i++) {\n+\t\tstruct prefix_item *item = list[i];\n+\t\tif (!starts_with(item->name, string))\n+\t\t\tcontinue;\n+\t\tif (found >= 0)\n+\t\t\treturn -1;\n+\t\tfound = i;\n+\t}\n+\n+\treturn found;\n+}\n \n struct list_options {\n \tint columns;\n \tconst char *header;\n-\tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n+\tvoid (*print_item)(int i, struct prefix_item *item,\n+\t\t\t   void *print_item_data);\n \tvoid *print_item_data;\n };\n \n-static void list(struct item **list, size_t nr,\n+static void list(struct prefix_item **list, size_t nr,\n \t\t struct add_i_state *s, struct list_options *opts)\n {\n \tint i, last_lf = 0;\n@@ -100,13 +115,15 @@ struct list_and_choose_options {\n  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n  * `LIST_AND_CHOOSE_QUIT` is returned.\n  */\n-static ssize_t list_and_choose(struct item **items, size_t nr,\n+static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \t\t\t       struct add_i_state *s,\n \t\t\t       struct list_and_choose_options *opts)\n {\n \tstruct strbuf input = STRBUF_INIT;\n \tssize_t res = LIST_AND_CHOOSE_ERROR;\n \n+\tfind_unique_prefixes(items, nr, 1, 4);\n+\n \tfor (;;) {\n \t\tchar *p, *endp;\n \n@@ -146,6 +163,9 @@ static ssize_t list_and_choose(struct item **items, size_t nr,\n \t\t\t}\n \n \t\t\tp[sep] = '\\0';\n+\t\t\tif (index < 0)\n+\t\t\t\tindex = find_unique(p, items, nr);\n+\n \t\t\tif (index < 0 || index >= nr)\n \t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n \t\t\telse {\n@@ -171,7 +191,7 @@ struct adddel {\n \n struct file_list {\n \tstruct file_item {\n-\t\tstruct item item;\n+\t\tstruct prefix_item item;\n \t\tstruct adddel index, worktree;\n \t} **file;\n \tsize_t nr, alloc;\n@@ -337,12 +357,29 @@ static void populate_wi_changes(struct strbuf *buf,\n \t\tstrbuf_addstr(buf, no_changes);\n }\n \n+/* filters out prefixes which have special meaning to list_and_choose() */\n+static int is_valid_prefix(const char *prefix, size_t prefix_len)\n+{\n+\treturn prefix_len && prefix &&\n+\t\t/*\n+\t\t * We expect `prefix` to be NUL terminated, therefore this\n+\t\t * `strcspn()` call is okay, even if it might do much more\n+\t\t * work than strictly necessary.\n+\t\t */\n+\t\tstrcspn(prefix, \" \\t\\r\\n,\") >= prefix_len &&\t/* separators */\n+\t\t*prefix != '-' &&\t\t\t\t/* deselection */\n+\t\t!isdigit(*prefix) &&\t\t\t\t/* selection */\n+\t\t(prefix_len != 1 ||\n+\t\t (*prefix != '*' &&\t\t\t\t/* \"all\" wildcard */\n+\t\t  *prefix != '?'));\t\t\t\t/* prompt help */\n+}\n+\n struct print_file_item_data {\n \tconst char *modified_fmt;\n \tstruct strbuf buf, index, worktree;\n };\n \n-static void print_file_item(int i, struct item *item,\n+static void print_file_item(int i, struct prefix_item *item,\n \t\t\t    void *print_file_item_data)\n {\n \tstruct file_item *c = (struct file_item *)item;\n@@ -369,20 +406,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \t\treturn -1;\n \n \tif (files->nr)\n-\t\tlist((struct item **)files->file, files->nr, s, opts);\n+\t\tlist((struct prefix_item **)files->file, files->nr, s, opts);\n \tputchar('\\n');\n \n \treturn 0;\n }\n \n-static void print_command_item(int i, struct item *item,\n+static void print_command_item(int i, struct prefix_item *item,\n \t\t\t       void *print_command_item_data)\n {\n-\tprintf(\" %2d: %s\", i + 1, item->name);\n+\tif (!item->prefix_length ||\n+\t    !is_valid_prefix(item->name, item->prefix_length))\n+\t\tprintf(\" %2d: %s\", i + 1, item->name);\n+\telse\n+\t\tprintf(\" %3d: [%.*s]%s\", i + 1,\n+\t\t       (int)item->prefix_length, item->name,\n+\t\t       item->name + item->prefix_length);\n }\n \n struct command_item {\n-\tstruct item item;\n+\tstruct prefix_item item;\n \tint (*command)(struct add_i_state *s, const struct pathspec *ps,\n \t\t       struct file_list *files, struct list_options *opts);\n };\n@@ -424,7 +467,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\tres = -1;\n \n \tfor (;;) {\n-\t\ti = list_and_choose((struct item **)commands,\n+\t\ti = list_and_choose((struct prefix_item **)commands,\n \t\t\t\t    ARRAY_SIZE(commands), &s, &main_loop_opts);\n \t\tif (i == LIST_AND_CHOOSE_QUIT) {\n \t\t\tprintf(_(\"Bye.\\n\"));\n-- \ngitgitgadget\n\n"},{"id":"378971","messageId":"3000d7d08dfb64511b4ebf9d05617897dd7252f7.1563289115.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings","fromName":"Slavica Djukic via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-16T14:58:42Z","receivedAt":"2019-07-16T14:58:49Z","isPatch":true,"sender":{"key":"slavicadj.ip2018@gmail.com","avatar":null},"body":"From: Slavica Djukic <slawica92@hotmail.com>\n\nIn the `git add -i` command, we show unique prefixes of the commands and\nfiles, to give an indication what prefix would select them.\n\nNaturally, the C implementation looks a lot different than the Perl\nimplementation: in Perl, a trie is much easier implemented, while we\nalready have a pretty neat hashmap implementation in C that we use for\nthe purpose of storing (not necessarily unique) prefixes.\n\nThe idea: for each item that we add, we generate prefixes starting with\nthe first letter, then the first two letters, then three, etc, until we\nfind a prefix that is unique (or until the prefix length would be\nlonger than we want). If we encounter a previously-unique prefix on the\nway, we adjust that item's prefix to make it unique again (or we mark it\nas having no unique prefix if we failed to find one). These partial\nprefixes are stored in a hash map (for quick lookup times).\n\nTo make sure that this function works as expected, we add a test using a\nspecial-purpose test helper that was added for that purpose.\n\nNote: We expect the list of prefix items to be passed in as a list of\npointers rather than as regular list to avoid having to copy information\n(the actual items will most likely contain more information than just\nthe name and the length of the unique prefix, but passing in `struct\nprefix_item *` would not allow for that).\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile                   |   2 +\n prefix-map.c               | 109 +++++++++++++++++++++++++++++++++++++\n prefix-map.h               |  40 ++++++++++++++\n t/helper/test-prefix-map.c |  58 ++++++++++++++++++++\n t/helper/test-tool.c       |   1 +\n t/helper/test-tool.h       |   1 +\n t/t0018-prefix-map.sh      |  10 ++++\n 7 files changed, 221 insertions(+)\n create mode 100644 prefix-map.c\n create mode 100644 prefix-map.h\n create mode 100644 t/helper/test-prefix-map.c\n create mode 100755 t/t0018-prefix-map.sh\n\ndiff --git a/Makefile b/Makefile\nindex 0a47200f47..545a0e8743 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -725,6 +725,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o\n TEST_BUILTINS_OBJS += test-parse-options.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n+TEST_BUILTINS_OBJS += test-prefix-map.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\n@@ -943,6 +944,7 @@ LIB_OBJS += patch-ids.o\n LIB_OBJS += path.o\n LIB_OBJS += pathspec.o\n LIB_OBJS += pkt-line.o\n+LIB_OBJS += prefix-map.o\n LIB_OBJS += preload-index.o\n LIB_OBJS += pretty.o\n LIB_OBJS += prio-queue.o\ndiff --git a/prefix-map.c b/prefix-map.c\nnew file mode 100644\nindex 0000000000..747ddb4ebc\n--- /dev/null\n+++ b/prefix-map.c\n@@ -0,0 +1,109 @@\n+#include \"cache.h\"\n+#include \"prefix-map.h\"\n+\n+static int map_cmp(const void *unused_cmp_data,\n+\t\t   const void *entry,\n+\t\t   const void *entry_or_key,\n+\t\t   const void *unused_keydata)\n+{\n+\tconst struct prefix_map_entry *a = entry;\n+\tconst struct prefix_map_entry *b = entry_or_key;\n+\n+\treturn a->prefix_length != b->prefix_length ||\n+\t\tstrncmp(a->name, b->name, a->prefix_length);\n+}\n+\n+static void add_prefix_entry(struct hashmap *map, const char *name,\n+\t\t\t     size_t prefix_length, struct prefix_item *item)\n+{\n+\tstruct prefix_map_entry *result = xmalloc(sizeof(*result));\n+\tresult->name = name;\n+\tresult->prefix_length = prefix_length;\n+\tresult->item = item;\n+\thashmap_entry_init(result, memhash(name, prefix_length));\n+\thashmap_add(map, result);\n+}\n+\n+static void init_prefix_map(struct prefix_map *prefix_map,\n+\t\t\t    int min_prefix_length, int max_prefix_length)\n+{\n+\thashmap_init(&prefix_map->map, map_cmp, NULL, 0);\n+\tprefix_map->min_length = min_prefix_length;\n+\tprefix_map->max_length = max_prefix_length;\n+}\n+\n+static void add_prefix_item(struct prefix_map *prefix_map,\n+\t\t\t    struct prefix_item *item)\n+{\n+\tstruct prefix_map_entry e = { { NULL } }, *e2;\n+\tint j;\n+\n+\te.item = item;\n+\te.name = item->name;\n+\n+\tfor (j = prefix_map->min_length;\n+\t     j <= prefix_map->max_length && e.name[j]; j++) {\n+\t\t/* Avoid breaking UTF-8 multi-byte sequences */\n+\t\tif (!isascii(e.name[j]))\n+\t\t\tbreak;\n+\n+\t\te.prefix_length = j;\n+\t\thashmap_entry_init(&e, memhash(e.name, j));\n+\t\te2 = hashmap_get(&prefix_map->map, &e, NULL);\n+\t\tif (!e2) {\n+\t\t\t/* prefix is unique at this stage */\n+\t\t\titem->prefix_length = j;\n+\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j, item);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (!e2->item)\n+\t\t\tcontinue; /* non-unique prefix */\n+\n+\t\tif (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))\n+\t\t\tBUG(\"unexpected prefix length: %d != %d (%s != %s)\",\n+\t\t\t    j, (int)e2->item->prefix_length, e.name, e2->name);\n+\n+\t\t/* skip common prefix */\n+\t\tfor (; j < prefix_map->max_length && e.name[j]; j++) {\n+\t\t\tif (e.item->name[j] != e2->item->name[j])\n+\t\t\t\tbreak;\n+\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n+\t\t\t\t\t NULL);\n+\t\t}\n+\n+\t\t/* e2 no longer refers to a unique prefix */\n+\t\tif (j < prefix_map->max_length && e2->name[j]) {\n+\t\t\t/* found a new unique prefix for e2's item */\n+\t\t\te2->item->prefix_length = j + 1;\n+\t\t\tadd_prefix_entry(&prefix_map->map, e2->name, j + 1,\n+\t\t\t\t\t e2->item);\n+\t\t}\n+\t\telse\n+\t\t\te2->item->prefix_length = 0;\n+\t\te2->item = NULL;\n+\n+\t\tif (j < prefix_map->max_length && e.name[j]) {\n+\t\t\t/* found a unique prefix for the item */\n+\t\t\te.item->prefix_length = j + 1;\n+\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n+\t\t\t\t\t e.item);\n+\t\t} else\n+\t\t\t/* item has no (short enough) unique prefix */\n+\t\t\te.item->prefix_length = 0;\n+\n+\t\tbreak;\n+\t}\n+}\n+\n+void find_unique_prefixes(struct prefix_item **list, size_t nr,\n+\t\t\t  int min_length, int max_length)\n+{\n+\tint i;\n+\tstruct prefix_map prefix_map;\n+\n+\tinit_prefix_map(&prefix_map, min_length, max_length);\n+\tfor (i = 0; i < nr; i++)\n+\t\tadd_prefix_item(&prefix_map, list[i]);\n+\thashmap_free(&prefix_map.map, 1);\n+}\ndiff --git a/prefix-map.h b/prefix-map.h\nnew file mode 100644\nindex 0000000000..ce3b8a4a32\n--- /dev/null\n+++ b/prefix-map.h\n@@ -0,0 +1,40 @@\n+#ifndef PREFIX_MAP_H\n+#define PREFIX_MAP_H\n+\n+#include \"hashmap.h\"\n+\n+struct prefix_item {\n+\tconst char *name;\n+\tsize_t prefix_length;\n+};\n+\n+struct prefix_map_entry {\n+\tstruct hashmap_entry e;\n+\tconst char *name;\n+\tsize_t prefix_length;\n+\t/* if item is NULL, the prefix is not unique */\n+\tstruct prefix_item *item;\n+};\n+\n+struct prefix_map {\n+\tstruct hashmap map;\n+\tint min_length, max_length;\n+};\n+\n+/*\n+ * Find unique prefixes in a given list of strings.\n+ *\n+ * Typically, the `struct prefix_item` information will be but a field in the\n+ * actual item struct; For this reason, the `list` parameter is specified as a\n+ * list of pointers to the items.\n+ *\n+ * The `min_length`/`max_length` parameters define what length the unique\n+ * prefixes should have.\n+ *\n+ * If no unique prefix could be found for a given item, its `prefix_length`\n+ * will be set to 0.\n+ */\n+void find_unique_prefixes(struct prefix_item **list, size_t nr,\n+\t\t\t  int min_length, int max_length);\n+\n+#endif\ndiff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c\nnew file mode 100644\nindex 0000000000..3f1c90eaf0\n--- /dev/null\n+++ b/t/helper/test-prefix-map.c\n@@ -0,0 +1,58 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"prefix-map.h\"\n+\n+static size_t test_count, failed_count;\n+\n+static void check(int succeeded, const char *file, size_t line_no,\n+\t\t  const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\ttest_count++;\n+\tif (succeeded)\n+\t\treturn;\n+\n+\tva_start(ap, fmt);\n+\tfprintf(stderr, \"%s:%d: \", file, (int)line_no);\n+\tvfprintf(stderr, fmt, ap);\n+\tfputc('\\n', stderr);\n+\tva_end(ap);\n+\n+\tfailed_count++;\n+}\n+\n+#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \\\n+\tcheck(expect == actual, __FILE__, __LINE__, \\\n+\t      \"size_t's do not match: %\" \\\n+\t      PRIdMAX \" != %\" PRIdMAX \" (%s) (%s)\", \\\n+\t      (intmax_t)expect, (intmax_t)actual, #actual, hint)\n+\n+int cmd__prefix_map(int argc, const char **argv)\n+{\n+#define NR 5\n+\tstruct prefix_item items[NR] = {\n+\t\t{ \"unique\" },\n+\t\t{ \"hell\" },\n+\t\t{ \"hello\" },\n+\t\t{ \"wok\" },\n+\t\t{ \"world\" },\n+\t};\n+\tstruct prefix_item *list[NR] = {\n+\t\titems, items + 1, items + 2, items + 3, items + 4\n+\t};\n+\n+\tfind_unique_prefixes(list, NR, 1, 3);\n+\n+#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \\\n+\tEXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \\\n+\t\t\t     list[index]->name)\n+\n+\tEXPECT_PREFIX_LENGTH_EQUALS(1, 0);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(0, 1);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(0, 2);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(3, 3);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(3, 4);\n+\n+\treturn !!failed_count;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 087a8c0cc9..e076771b96 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -39,6 +39,7 @@ static struct test_cmd cmds[] = {\n \t{ \"parse-options\", cmd__parse_options },\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n+\t{ \"prefix-map\", cmd__prefix_map },\n \t{ \"prio-queue\", cmd__prio_queue },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 7e703f3038..891bac703b 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -29,6 +29,7 @@ int cmd__online_cpus(int argc, const char **argv);\n int cmd__parse_options(int argc, const char **argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n+int cmd__prefix_map(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t0018-prefix-map.sh b/t/t0018-prefix-map.sh\nnew file mode 100755\nindex 0000000000..187fa92aec\n--- /dev/null\n+++ b/t/t0018-prefix-map.sh\n@@ -0,0 +1,10 @@\n+#!/bin/sh\n+\n+test_description='basic tests for prefix map'\n+. ./test-lib.sh\n+\n+test_expect_success 'prefix map' '\n+\ttest-tool prefix-map\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"378972","messageId":"d8c012fce83218a72de485ee6855bf80240d3964.1563289115.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 09/11] built-in add -i: support `?` (prompt help)","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-16T14:58:43Z","receivedAt":"2019-07-16T14:58:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWith this change, we print out the same colored help text that the\nPerl-based `git add -i` prints in the main loop when question mark is\nentered.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 22 +++++++++++++++++++++-\n 1 file changed, 21 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 7ff87bae1b..1435c12be9 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -11,6 +11,7 @@ struct add_i_state {\n \tstruct repository *r;\n \tint use_color;\n \tchar header_color[COLOR_MAXLEN];\n+\tchar help_color[COLOR_MAXLEN];\n };\n \n static void init_color(struct repository *r, struct add_i_state *s,\n@@ -43,6 +44,7 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)\n \ts->use_color = want_color(s->use_color);\n \n \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n+\tinit_color(r, s, \"help\", s->help_color, GIT_COLOR_BOLD_RED);\n \n \treturn 0;\n }\n@@ -104,6 +106,7 @@ struct list_and_choose_options {\n \tstruct list_options list_opts;\n \n \tconst char *prompt;\n+\tvoid (*print_help)(struct add_i_state *s);\n };\n \n #define LIST_AND_CHOOSE_ERROR (-1)\n@@ -144,6 +147,11 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \t\tif (!input.len)\n \t\t\tbreak;\n \n+\t\tif (!strcmp(input.buf, \"?\")) {\n+\t\t\topts->print_help(s);\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tp = input.buf;\n \t\tfor (;;) {\n \t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n@@ -430,12 +438,24 @@ struct command_item {\n \t\t       struct file_list *files, struct list_options *opts);\n };\n \n+static void command_prompt_help(struct add_i_state *s)\n+{\n+\tconst char *help_color = s->help_color;\n+\tcolor_fprintf_ln(stdout, help_color, \"%s\", _(\"Prompt help:\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"1          - %s\",\n+\t\t\t _(\"select a numbered item\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"foo        - %s\",\n+\t\t\t _(\"select item based on unique prefix\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"           - %s\",\n+\t\t\t _(\"(empty) select nothing\"));\n+}\n+\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n \tstruct list_and_choose_options main_loop_opts = {\n \t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n-\t\tN_(\"What now\")\n+\t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct command_item\n \t\tstatus = { { \"status\" }, run_status };\n-- \ngitgitgadget\n\n"},{"id":"378974","messageId":"b93b055ebeef98be11a1658453df6f85fed87347.1563289115.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 03/11] built-in add -i: implement the `status` command","fromName":"Daniel Ferreira via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-16T14:58:38Z","receivedAt":"2019-07-16T14:58:51Z","isPatch":true,"sender":{"key":"bnmvco@gmail.com","avatar":"https://avatars.githubusercontent.com/u/735397?v=4"},"body":"From: Daniel Ferreira <bnmvco@gmail.com>\n\nThis implements the `status` command of `git add -i`. The data\nstructures introduced in this commit will be extended as needed later.\n\nAt this point, we re-implement only part of the `list_and_choose()`\nfunction of the Perl script `git-add--interactive.perl` and call it\n`list()`. It does not yet color anything, or do columns, or allow user\ninput.\n\nOver the course of the next commits, we will introduce a\n`list_and_choose()` function that uses `list()` to display the list of\noptions and let the user choose one or more of the displayed items. This\nwill be used to implement the main loop of the built-in `git add -i`, at\nwhich point the new `status` command can actually be used.\n\nNote that we pass the list of items as a `struct item **` as opposed to\na `struct item *`, to allow for the actual items to contain much more\ninformation than merely the name.\n\nSigned-off-by: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 265 +++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 264 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 482e458dc6..59b28011f7 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -1,7 +1,270 @@\n #include \"cache.h\"\n #include \"add-interactive.h\"\n+#include \"diffcore.h\"\n+#include \"revision.h\"\n+#include \"refs.h\"\n+\n+struct item {\n+\tconst char *name;\n+};\n+\n+struct list_options {\n+\tconst char *header;\n+\tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n+\tvoid *print_item_data;\n+};\n+\n+static void list(struct item **list, size_t nr, struct list_options *opts)\n+{\n+\tint i;\n+\n+\tif (!nr)\n+\t\treturn;\n+\n+\tif (opts->header)\n+\t\tprintf(\"%s\\n\", opts->header);\n+\n+\tfor (i = 0; i < nr; i++) {\n+\t\topts->print_item(i, list[i], opts->print_item_data);\n+\t\tputchar('\\n');\n+\t}\n+}\n+\n+struct adddel {\n+\tuintmax_t add, del;\n+\tunsigned seen:1, binary:1;\n+};\n+\n+struct file_list {\n+\tstruct file_item {\n+\t\tstruct item item;\n+\t\tstruct adddel index, worktree;\n+\t} **file;\n+\tsize_t nr, alloc;\n+};\n+\n+static void add_file_item(struct file_list *list, const char *name)\n+{\n+\tstruct file_item *item;\n+\n+\tFLEXPTR_ALLOC_STR(item, item.name, name);\n+\n+\tALLOC_GROW(list->file, list->nr + 1, list->alloc);\n+\tlist->file[list->nr++] = item;\n+}\n+\n+static void reset_file_list(struct file_list *list)\n+{\n+\tsize_t i;\n+\n+\tfor (i = 0; i < list->nr; i++)\n+\t\tfree(list->file[i]);\n+\tlist->nr = 0;\n+}\n+\n+static void release_file_list(struct file_list *list)\n+{\n+\treset_file_list(list);\n+\tFREE_AND_NULL(list->file);\n+\tlist->alloc = 0;\n+}\n+\n+static int file_item_cmp(const void *a, const void *b)\n+{\n+\tconst struct file_item * const *f1 = a;\n+\tconst struct file_item * const *f2 = b;\n+\n+\treturn strcmp((*f1)->item.name, (*f2)->item.name);\n+}\n+\n+struct pathname_entry {\n+\tstruct hashmap_entry ent;\n+\tsize_t index;\n+\tchar pathname[FLEX_ARRAY];\n+};\n+\n+static int pathname_entry_cmp(const void *unused_cmp_data,\n+\t\t\t      const void *entry, const void *entry_or_key,\n+\t\t\t      const void *pathname)\n+{\n+\tconst struct pathname_entry *e1 = entry, *e2 = entry_or_key;\n+\n+\treturn strcmp(e1->pathname,\n+\t\t      pathname ? (const char *)pathname : e2->pathname);\n+}\n+\n+struct collection_status {\n+\tenum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase;\n+\n+\tconst char *reference;\n+\n+\tstruct file_list *list;\n+\tstruct hashmap file_map;\n+};\n+\n+static void collect_changes_cb(struct diff_queue_struct *q,\n+\t\t\t       struct diff_options *options,\n+\t\t\t       void *data)\n+{\n+\tstruct collection_status *s = data;\n+\tstruct diffstat_t stat = { 0 };\n+\tint i;\n+\n+\tif (!q->nr)\n+\t\treturn;\n+\n+\tcompute_diffstat(options, &stat, q);\n+\n+\tfor (i = 0; i < stat.nr; i++) {\n+\t\tconst char *name = stat.files[i]->name;\n+\t\tint hash = strhash(name);\n+\t\tstruct pathname_entry *entry;\n+\t\tsize_t file_index;\n+\t\tstruct file_item *file;\n+\t\tstruct adddel *adddel;\n+\n+\t\tentry = hashmap_get_from_hash(&s->file_map, hash, name);\n+\t\tif (entry)\n+\t\t\tfile_index = entry->index;\n+\t\telse {\n+\t\t\tFLEX_ALLOC_STR(entry, pathname, name);\n+\t\t\thashmap_entry_init(entry, hash);\n+\t\t\tentry->index = file_index = s->list->nr;\n+\t\t\thashmap_add(&s->file_map, entry);\n+\n+\t\t\tadd_file_item(s->list, name);\n+\t\t}\n+\t\tfile = s->list->file[file_index];\n+\n+\t\tadddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;\n+\t\tadddel->seen = 1;\n+\t\tadddel->add = stat.files[i]->added;\n+\t\tadddel->del = stat.files[i]->deleted;\n+\t\tif (stat.files[i]->is_binary)\n+\t\t\tadddel->binary = 1;\n+\t}\n+}\n+\n+static int get_modified_files(struct repository *r, struct file_list *list,\n+\t\t\t      const struct pathspec *ps)\n+{\n+\tstruct object_id head_oid;\n+\tint is_initial = !resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING,\n+\t\t\t\t\t     &head_oid, NULL);\n+\tstruct collection_status s = { FROM_WORKTREE };\n+\n+\tif (repo_read_index_preload(r, ps, 0) < 0)\n+\t\treturn error(_(\"could not read index\"));\n+\n+\ts.list = list;\n+\thashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);\n+\n+\tfor (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {\n+\t\tstruct rev_info rev;\n+\t\tstruct setup_revision_opt opt = { 0 };\n+\n+\t\topt.def = is_initial ?\n+\t\t\tempty_tree_oid_hex() : oid_to_hex(&head_oid);\n+\n+\t\tinit_revisions(&rev, NULL);\n+\t\tsetup_revisions(0, NULL, &rev, &opt);\n+\n+\t\trev.diffopt.output_format = DIFF_FORMAT_CALLBACK;\n+\t\trev.diffopt.format_callback = collect_changes_cb;\n+\t\trev.diffopt.format_callback_data = &s;\n+\n+\t\tif (ps)\n+\t\t\tcopy_pathspec(&rev.prune_data, ps);\n+\n+\t\tif (s.phase == FROM_INDEX)\n+\t\t\trun_diff_index(&rev, 1);\n+\t\telse {\n+\t\t\trev.diffopt.flags.ignore_dirty_submodules = 1;\n+\t\t\trun_diff_files(&rev, 0);\n+\t\t}\n+\t}\n+\thashmap_free(&s.file_map, 1);\n+\n+\t/* While the diffs are ordered already, we ran *two* diffs... */\n+\tQSORT(list->file, list->nr, file_item_cmp);\n+\n+\treturn 0;\n+}\n+\n+static void populate_wi_changes(struct strbuf *buf,\n+\t\t\t\tstruct adddel *ad, const char *no_changes)\n+{\n+\tif (ad->binary)\n+\t\tstrbuf_addstr(buf, _(\"binary\"));\n+\telse if (ad->seen)\n+\t\tstrbuf_addf(buf, \"+%\"PRIuMAX\"/-%\"PRIuMAX,\n+\t\t\t    (uintmax_t)ad->add, (uintmax_t)ad->del);\n+\telse\n+\t\tstrbuf_addstr(buf, no_changes);\n+}\n+\n+struct print_file_item_data {\n+\tconst char *modified_fmt;\n+\tstruct strbuf buf, index, worktree;\n+};\n+\n+static void print_file_item(int i, struct item *item,\n+\t\t\t    void *print_file_item_data)\n+{\n+\tstruct file_item *c = (struct file_item *)item;\n+\tstruct print_file_item_data *d = print_file_item_data;\n+\n+\tstrbuf_reset(&d->index);\n+\tstrbuf_reset(&d->worktree);\n+\tstrbuf_reset(&d->buf);\n+\n+\tpopulate_wi_changes(&d->worktree, &c->worktree, _(\"nothing\"));\n+\tpopulate_wi_changes(&d->index, &c->index, _(\"unchanged\"));\n+\tstrbuf_addf(&d->buf, d->modified_fmt,\n+\t\t    d->index.buf, d->worktree.buf, item->name);\n+\n+\tprintf(\" %2d: %s\", i + 1, d->buf.buf);\n+}\n+\n+static int run_status(struct repository *r, const struct pathspec *ps,\n+\t\t      struct file_list *files, struct list_options *opts)\n+{\n+\treset_file_list(files);\n+\n+\tif (get_modified_files(r, files, ps) < 0)\n+\t\treturn -1;\n+\n+\tif (files->nr)\n+\t\tlist((struct item **)files->file, files->nr, opts);\n+\tputchar('\\n');\n+\n+\treturn 0;\n+}\n \n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n-\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n+\tstruct print_file_item_data print_file_item_data = {\n+\t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n+\t};\n+\tstruct list_options opts = {\n+\t\tNULL, print_file_item, &print_file_item_data\n+\t};\n+\tstruct strbuf header = STRBUF_INIT;\n+\tstruct file_list files = { NULL };\n+\tint res = 0;\n+\n+\tstrbuf_addstr(&header, \"      \");\n+\tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n+\t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n+\topts.header = header.buf;\n+\n+\tres = run_status(r, ps, &files, &opts);\n+\n+\trelease_file_list(&files);\n+\tstrbuf_release(&print_file_item_data.buf);\n+\tstrbuf_release(&print_file_item_data.index);\n+\tstrbuf_release(&print_file_item_data.worktree);\n+\tstrbuf_release(&header);\n+\n+\treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"378973","messageId":"8121a3ca1bae0189ef720dfca455c2feb5d40e0d.1563289115.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 10/11] built-in add -i: use color in the main loop","fromName":"Slavica Djukic via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-16T14:58:44Z","receivedAt":"2019-07-16T14:58:52Z","isPatch":true,"sender":{"key":"slavicadj.ip2018@gmail.com","avatar":null},"body":"From: Slavica Djukic <slawica92@hotmail.com>\n\nThe error messages as well as the unique prefixes are colored in `git\nadd -i` by default; We need to do the same in the built-in version.\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 37 ++++++++++++++++++++++++++++++++-----\n 1 file changed, 32 insertions(+), 5 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 1435c12be9..538658dfa7 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -12,6 +12,9 @@ struct add_i_state {\n \tint use_color;\n \tchar header_color[COLOR_MAXLEN];\n \tchar help_color[COLOR_MAXLEN];\n+\tchar prompt_color[COLOR_MAXLEN];\n+\tchar error_color[COLOR_MAXLEN];\n+\tchar reset_color[COLOR_MAXLEN];\n };\n \n static void init_color(struct repository *r, struct add_i_state *s,\n@@ -45,6 +48,9 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)\n \n \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n \tinit_color(r, s, \"help\", s->help_color, GIT_COLOR_BOLD_RED);\n+\tinit_color(r, s, \"prompt\", s->prompt_color, GIT_COLOR_BOLD_BLUE);\n+\tinit_color(r, s, \"error\", s->error_color, GIT_COLOR_BOLD_RED);\n+\tinit_color(r, s, \"reset\", s->reset_color, GIT_COLOR_RESET);\n \n \treturn 0;\n }\n@@ -134,7 +140,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \n \t\tlist(items, nr, s, &opts->list_opts);\n \n-\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n+\t\tcolor_fprintf(stdout, s->prompt_color, \"%s\", opts->prompt);\n+\t\tfputs(\"> \", stdout);\n \t\tfflush(stdout);\n \n \t\tif (strbuf_getline(&input, stdin) == EOF) {\n@@ -175,7 +182,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \t\t\t\tindex = find_unique(p, items, nr);\n \n \t\t\tif (index < 0 || index >= nr)\n-\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n+\t\t\t\tcolor_fprintf_ln(stdout, s->error_color,\n+\t\t\t\t\t\t _(\"Huh (%s)?\"), p);\n \t\t\telse {\n \t\t\t\tres = index;\n \t\t\t\tbreak;\n@@ -420,15 +428,21 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \treturn 0;\n }\n \n+struct print_command_item_data {\n+\tconst char *color, *reset;\n+};\n+\n static void print_command_item(int i, struct prefix_item *item,\n \t\t\t       void *print_command_item_data)\n {\n+\tstruct print_command_item_data *d = print_command_item_data;\n+\n \tif (!item->prefix_length ||\n \t    !is_valid_prefix(item->name, item->prefix_length))\n \t\tprintf(\" %2d: %s\", i + 1, item->name);\n \telse\n-\t\tprintf(\" %3d: [%.*s]%s\", i + 1,\n-\t\t       (int)item->prefix_length, item->name,\n+\t\tprintf(\" %2d: %s%.*s%s%s\", i + 1,\n+\t\t       d->color, (int)item->prefix_length, item->name, d->reset,\n \t\t       item->name + item->prefix_length);\n }\n \n@@ -453,8 +467,9 @@ static void command_prompt_help(struct add_i_state *s)\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n+\tstruct print_command_item_data data;\n \tstruct list_and_choose_options main_loop_opts = {\n-\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n+\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, &data },\n \t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct command_item\n@@ -477,6 +492,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tif (init_add_i_state(r, &s))\n \t\treturn error(\"could not parse `add -i` config\");\n \n+\t/*\n+\t * When color was asked for, use the prompt color for\n+\t * highlighting, otherwise use square brackets.\n+\t */\n+\tif (s.use_color) {\n+\t\tdata.color = s.prompt_color;\n+\t\tdata.reset = s.reset_color;\n+\t} else {\n+\t\tdata.color = \"[\";\n+\t\tdata.reset = \"]\";\n+\t}\n+\n \tstrbuf_addstr(&header, \"      \");\n \tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n-- \ngitgitgadget\n\n"},{"id":"378975","messageId":"db70c6475d85dd77385d773274fa390fa7ed08c0.1563289115.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 11/11] built-in add -i: implement the `help` command","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-16T14:58:45Z","receivedAt":"2019-07-16T14:58:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis imitates the code to show the help text from the Perl script\n`git-add--interactive.perl` in the built-in version.\n\nTo make sure that it renders exactly like the Perl version of `git add\n-i`, we also add a test case for that to `t3701-add-interactive.sh`.\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c          | 27 +++++++++++++++++++++++++--\n t/t3701-add-interactive.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 50 insertions(+), 2 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 538658dfa7..c431c72e3f 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -428,6 +428,27 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \treturn 0;\n }\n \n+static int run_help(struct add_i_state *s, const struct pathspec *ps,\n+\t\t    struct file_list *files, struct list_options *opts)\n+{\n+\tconst char *help_color = s->help_color;\n+\n+\tcolor_fprintf_ln(stdout, help_color, \"status        - %s\",\n+\t\t\t _(\"show paths with changes\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"update        - %s\",\n+\t\t\t _(\"add working tree state to the staged set of changes\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"revert        - %s\",\n+\t\t\t _(\"revert staged set of changes back to the HEAD version\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"patch         - %s\",\n+\t\t\t _(\"pick hunks and update selectively\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"diff          - %s\",\n+\t\t\t _(\"view diff between HEAD and index\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"add untracked - %s\",\n+\t\t\t _(\"add contents of untracked files to the staged set of changes\"));\n+\n+\treturn 0;\n+}\n+\n struct print_command_item_data {\n \tconst char *color, *reset;\n };\n@@ -473,9 +494,11 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct command_item\n-\t\tstatus = { { \"status\" }, run_status };\n+\t\tstatus = { { \"status\" }, run_status },\n+\t\thelp = { { \"help\" }, run_help };\n \tstruct command_item *commands[] = {\n-\t\t&status\n+\t\t&status,\n+\t\t&help\n \t};\n \n \tstruct print_file_item_data print_file_item_data = {\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex 69991a3168..cf67756b85 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' '\n \ttest_write_lines a b a b a a b a b a >expect &&\n \ttest_cmp expect a\n '\n+\n+test_expect_success 'show help from add--helper' '\n+\tgit reset --hard &&\n+\tcat >expect <<-EOF &&\n+\n+\t<BOLD>*** Commands ***<RESET>\n+\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>\n+\t<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>\n+\t<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>\n+\t<BOLD;RED>patch         - pick hunks and update selectively<RESET>\n+\t<BOLD;RED>diff          - view diff between HEAD and index<RESET>\n+\t<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>\n+\t<BOLD>*** Commands ***<RESET>\n+\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t<BOLD;BLUE>What now<RESET>>$SP\n+\tBye.\n+\tEOF\n+\ttest_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&\n+\ttest_decode_color <actual.colored >actual &&\n+\ttest_i18ncmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"378985","messageId":"nycvar.QRO.7.76.6.1907162037430.47@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"pull.170.v3.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far)","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-16T18:38:20Z","receivedAt":"2019-07-16T18:39:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\n\nOn Tue, 16 Jul 2019, Johannes Schindelin via GitGitGadget wrote:\n\n> This is the first leg on the long journey to a fully built-in git add -i\n> (next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3\n> [https://github.com/gitgitgadget/git/pull/172], 4\n> [https://github.com/gitgitgadget/git/pull/173], 5\n> [https://github.com/gitgitgadget/git/pull/174], and 6\n> [https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are\n> not necessarily up to date, and will be re-targeted to the appropriate\n> branches in https://github.com/gitster/git as soon as Junio picks them up.\n>\n> This here patch series reflects the part that was submitted a couple of\n> times (see https://github.com/gitgitgadget/git/pull/103) during the\n> Outreachy project by Slavica Ðukic that continued the journey based on an\n> initial patch series by Daniel Ferreira.\n>\n> It only implements the status and the help part, in the interest of making\n> the review remotely more reviewable.\n>\n> As I am a heavy user of git add -p myself and use a patched version for\n> weeks already (it is so nice to not suffer over one second startup until the\n> MSYS2 Perl finally shows me anything, instead it feels instantaneous), I\n> integrated these patch series into Git for Windows' master already, as an\n> opt-in feature guarded by the config variable add.interactive.useBuiltin\n> (and Git for Windows' installer is prepared to detect this version and offer\n> the option in the graphical user interface).\n>\n> I had planned on submitting this before v2.22.0-rc0, but there was such a\n> backlog of builds from a big pushout that I had to wait ;-)\n\nFWIW the v2 round has not seen any objections...\n\nMaybe pick it up in `pu` this time round?\n\nThanks,\nDscho\n"},{"id":"379696","messageId":"xmqq1ry6qeg2.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"0a5ec9345d2f9cc6cd348231219d4af428a28e94.1563289115.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-31T17:52:29Z","receivedAt":"2019-07-31T17:52:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> +add.interactive.useBuiltin::\n\nI am not sure if three-level name is a good thing to use here.\n\nIf we have end-user controllable (like branch or remote names)\nunbounded number of subcommand/submode to \"add\", and \"interactive\"\nis merely one of it, then three-level name is a perfect fit, but\notherwise, not.\n\n> @@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,\n>  {\n>  \tint status, i;\n>  \tstruct argv_array argv = ARGV_ARRAY_INIT;\n> +\tint use_builtin_add_i =\n> +\t\tgit_env_bool(\"GIT_TEST_ADD_I_USE_BUILTIN\", -1);\n> +\tif (use_builtin_add_i < 0)\n> +\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n> +\t\t\t\t    &use_builtin_add_i);\n> +\n> +\tif (use_builtin_add_i == 1 && !patch_mode)\n> +\t\treturn !!run_add_i(the_repository, pathspec);\n\nI am hoping that eventually \"add -p\" will also be routed to the new\ncodepath.  Would it make sense to have \"&& !patch_mode\" here,\nespecially at this step where run_add_i() won't do anything useful\nanyway yet?\n\n> @@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb)\n>  \t\tignore_add_errors = git_config_bool(var, value);\n>  \t\treturn 0;\n>  \t}\n> +\n>  \treturn git_default_config(var, value, cb);\n>  }\n\nGood addition while at it.\n"},{"id":"379697","messageId":"xmqqwofyozka.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"c7a377890d84849ea2f63099cfc081420a4de15d.1563289115.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 02/11] diff: export diffstat interface","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-31T17:59:17Z","receivedAt":"2019-07-31T17:59:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Daniel Ferreira via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> @@ -6273,12 +6257,7 @@ void diff_flush(struct diff_options *options)\n>  \t    dirstat_by_line) {\n>  \t\tstruct diffstat_t diffstat;\n>  \n> -\t\tmemset(&diffstat, 0, sizeof(struct diffstat_t));\n> -\t\tfor (i = 0; i < q->nr; i++) {\n> -\t\t\tstruct diff_filepair *p = q->queue[i];\n> -\t\t\tif (check_pair_status(p))\n> -\t\t\t\tdiff_flush_stat(p, options, &diffstat);\n> -\t\t}\n> +\t\tcompute_diffstat(options, &diffstat, q);\n>  \t\tif (output_format & DIFF_FORMAT_NUMSTAT)\n>  \t\t\tshow_numstat(&diffstat, options);\n>  \t\tif (output_format & DIFF_FORMAT_DIFFSTAT)\n> @@ -6611,6 +6590,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)\n>  \treturn ignored;\n>  }\n>  \n> +void compute_diffstat(struct diff_options *options,\n> +\t\t      struct diffstat_t *diffstat,\n> +\t\t      struct diff_queue_struct *q)\n> +{\n> +\tint i;\n> +\n> +\tmemset(diffstat, 0, sizeof(struct diffstat_t));\n> +\tfor (i = 0; i < q->nr; i++) {\n> +\t\tstruct diff_filepair *p = q->queue[i];\n> +\t\tif (check_pair_status(p))\n> +\t\t\tdiff_flush_stat(p, options, diffstat);\n> +\t}\n> +}\n\nHmm, (1) clearing diffstat struct to initialize, (2) looping over\ndiff_queue to compute stat for each path, (3) using diffstat\ninformation and then (4) finally freeing the diffstat info is the\nbog-standard sequence of the user of this API.  Merging step (1) and\n(2) may probably be OK (iow, I do not think of a use pattern for\nfuture users where being able to do some custom things between steps\n(1) and (2) would be useful), which is this function is about.  (3)\nis what the user of this API would do, but shouldn't (4) be exported\nat the same time, if we are making (1+2) as an external API?\n\n>  void diff_addremove(struct diff_options *options,\n>  \t\t    int addremove, unsigned mode,\n>  \t\t    const struct object_id *oid,\n> diff --git a/diff.h b/diff.h\n> index b680b377b2..34fc658946 100644\n> --- a/diff.h\n> +++ b/diff.h\n> @@ -244,6 +244,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);\n>  void diff_emit_submodule_pipethrough(struct diff_options *o,\n>  \t\t\t\t     const char *line, int len);\n>  \n> +struct diffstat_t {\n> +\tint nr;\n> +\tint alloc;\n> +\tstruct diffstat_file {\n> +\t\tchar *from_name;\n> +\t\tchar *name;\n> +\t\tchar *print_name;\n> +\t\tconst char *comments;\n> +\t\tunsigned is_unmerged:1;\n> +\t\tunsigned is_binary:1;\n> +\t\tunsigned is_renamed:1;\n> +\t\tunsigned is_interesting:1;\n> +\t\tuintmax_t added, deleted;\n> +\t} **files;\n> +};\n> +\n>  enum color_diff {\n>  \tDIFF_RESET = 0,\n>  \tDIFF_CONTEXT = 1,\n> @@ -333,6 +349,9 @@ void diff_change(struct diff_options *,\n>  \n>  struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);\n>  \n> +void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,\n> +\t\t      struct diff_queue_struct *q);\n> +\n>  #define DIFF_SETUP_REVERSE      \t1\n>  #define DIFF_SETUP_USE_SIZE_CACHE\t4\n"},{"id":"379698","messageId":"xmqqsgqmoyz1.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"b93b055ebeef98be11a1658453df6f85fed87347.1563289115.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 03/11] built-in add -i: implement the `status` command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-31T18:12:02Z","receivedAt":"2019-07-31T18:12:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Daniel Ferreira via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +struct item {\n> +\tconst char *name;\n> +};\n> +\n> +struct list_options {\n> +\tconst char *header;\n> +\tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n> +\tvoid *print_item_data;\n> +};\n> +\n> +struct adddel {\n> +\tuintmax_t add, del;\n> +\tunsigned seen:1, binary:1;\n> +};\n> +\n> +struct file_list {\n> +\tstruct file_item {\n> +\t\tstruct item item;\n> +\t\tstruct adddel index, worktree;\n> +\t} **file;\n> +\tsize_t nr, alloc;\n> +};\n> +\n> +struct pathname_entry {\n> +\tstruct hashmap_entry ent;\n> +\tsize_t index;\n> +\tchar pathname[FLEX_ARRAY];\n> +};\n\nAll of the above are named too generic but assuming that add-i will\nstay to be a single file and these names will never leak outside the\nfile to become global, it would be perfectly fine.\n\n> +static void populate_wi_changes(struct strbuf *buf,\n> +\t\t\t\tstruct adddel *ad, const char *no_changes)\n> +{\n> +\tif (ad->binary)\n> +\t\tstrbuf_addstr(buf, _(\"binary\"));\n> +\telse if (ad->seen)\n> +\t\tstrbuf_addf(buf, \"+%\"PRIuMAX\"/-%\"PRIuMAX,\n> +\t\t\t    (uintmax_t)ad->add, (uintmax_t)ad->del);\n> +\telse\n> +\t\tstrbuf_addstr(buf, no_changes);\n> +}\n\nI offhand do not see the need for (uintmax_t) casts here...\n\n> +static int run_status(struct repository *r, const struct pathspec *ps,\n> +\t\t      struct file_list *files, struct list_options *opts)\n> +{\n> +\treset_file_list(files);\n> +\n> +\tif (get_modified_files(r, files, ps) < 0)\n> +\t\treturn -1;\n> +\n> +\tif (files->nr)\n> +\t\tlist((struct item **)files->file, files->nr, opts);\n> +\tputchar('\\n');\n\nSo, if there is anything to list, we show list() and then add an\nempty line; if there is nothing to list, we show an empty line\nanyway?\n\nAs long as that matches the current scripted \"add -i\", it's\nperfectly fine.  It's just that the code structure above looked\nsomewhat odd.\n\n> +static void collect_changes_cb(struct diff_queue_struct *q,\n> +\t\t\t       struct diff_options *options,\n> +\t\t\t       void *data)\n> +{\n> +\tstruct collection_status *s = data;\n> +\tstruct diffstat_t stat = { 0 };\n> +\tint i;\n> +\n> +\tif (!q->nr)\n> +\t\treturn;\n> +\n> +\tcompute_diffstat(options, &stat, q);\n> +\n> +\tfor (i = 0; i < stat.nr; i++) {\n> +\t\tconst char *name = stat.files[i]->name;\n> +\t\tint hash = strhash(name);\n> +\t\tstruct pathname_entry *entry;\n> +\t\tsize_t file_index;\n> +\t\tstruct file_item *file;\n> +\t\tstruct adddel *adddel;\n> +\n> +\t\tentry = hashmap_get_from_hash(&s->file_map, hash, name);\n> +\t\tif (entry)\n> +\t\t\tfile_index = entry->index;\n> +\t\telse {\n> +\t\t\tFLEX_ALLOC_STR(entry, pathname, name);\n> +\t\t\thashmap_entry_init(entry, hash);\n> +\t\t\tentry->index = file_index = s->list->nr;\n> +\t\t\thashmap_add(&s->file_map, entry);\n> +\n> +\t\t\tadd_file_item(s->list, name);\n> +\t\t}\n> +\t\tfile = s->list->file[file_index];\n> +\n> +\t\tadddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;\n> +\t\tadddel->seen = 1;\n> +\t\tadddel->add = stat.files[i]->added;\n> +\t\tadddel->del = stat.files[i]->deleted;\n> +\t\tif (stat.files[i]->is_binary)\n> +\t\t\tadddel->binary = 1;\n> +\t}\n> +}\n\nWould resources held in the \"stat\" structure leak at the end of this\nfunction?\n"},{"id":"379699","messageId":"xmqqo91aoyu9.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"175409aaae0f3127b92126dc201faa4b41da0870.1563289115.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 06/11] built-in add -i: implement the main loop","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-31T18:14:54Z","receivedAt":"2019-07-31T18:14:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> The Perl script `git-add--interactive.perl` mixed the purposes of the\n> \"list\" and the \"and choose\" part into the same function. In the C\n> version, we will keep them separate instead, calling the `list()`\n> function from the `list_and_choose()` function.\n\nMakes sense.  After all, I recall writing \"add -i\" in a few\niterations of lunch-time hack.  If we are to rewrite it from\nscratch, we should engineer it the right way ;-)\n"},{"id":"379703","messageId":"xmqqk1byoxp3.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"3000d7d08dfb64511b4ebf9d05617897dd7252f7.1563289115.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-31T18:39:36Z","receivedAt":"2019-07-31T18:39:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Slavica Djukic via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> The idea: for each item that we add, we generate prefixes starting with\n> the first letter, then the first two letters, then three, etc, until we\n> find a prefix that is unique (or until the prefix length would be\n> longer than we want). If we encounter a previously-unique prefix on the\n> way, we adjust that item's prefix to make it unique again (or we mark it\n> as having no unique prefix if we failed to find one). These partial\n> prefixes are stored in a hash map (for quick lookup times).\n\nOK.  I suppose such a machinery that accepts a set of strings and\nthen give us back a set of unique prefix length for each element of\nthe set would be useful in general, even outside the \"and choose\"\npart of \"list and choose\".  Nice design.\n\n> To make sure that this function works as expected, we add a test using a\n> special-purpose test helper that was added for that purpose.\n\nSomehow the above repeatedly says how special purpose it is\nredundantly ;-)\n\n> diff --git a/prefix-map.h b/prefix-map.h\n> new file mode 100644\n> index 0000000000..ce3b8a4a32\n> --- /dev/null\n> +++ b/prefix-map.h\n> @@ -0,0 +1,40 @@\n> +#ifndef PREFIX_MAP_H\n> +#define PREFIX_MAP_H\n> +\n> +#include \"hashmap.h\"\n> +\n> +struct prefix_item {\n> +\tconst char *name;\n> +\tsize_t prefix_length;\n> +};\n> +\n> +struct prefix_map_entry {\n> +\tstruct hashmap_entry e;\n> +\tconst char *name;\n> +\tsize_t prefix_length;\n> +\t/* if item is NULL, the prefix is not unique */\n> +\tstruct prefix_item *item;\n> +};\n> +\n> +struct prefix_map {\n> +\tstruct hashmap map;\n> +\tint min_length, max_length;\n> +};\n> +\n> +/*\n> + * Find unique prefixes in a given list of strings.\n> + *\n> + * Typically, the `struct prefix_item` information will be but a field in the\n> + * actual item struct; For this reason, the `list` parameter is specified as a\n> + * list of pointers to the items.\n> + *\n> + * The `min_length`/`max_length` parameters define what length the unique\n> + * prefixes should have.\n> + *\n> + * If no unique prefix could be found for a given item, its `prefix_length`\n> + * will be set to 0.\n> + */\n> +void find_unique_prefixes(struct prefix_item **list, size_t nr,\n> +\t\t\t  int min_length, int max_length);\n> +\n> +#endif\n\nLooks like a quite sane interface to me.\n\nThanks.\n"},{"id":"379855","messageId":"xmqqpnlnjn3q.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"db70c6475d85dd77385d773274fa390fa7ed08c0.1563289115.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 11/11] built-in add -i: implement the `help` command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-02T21:04:09Z","receivedAt":"2019-08-02T21:04:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> +static int run_help(struct add_i_state *s, const struct pathspec *ps,\n> +\t\t    struct file_list *files, struct list_options *opts)\n> +{\n> +\tconst char *help_color = s->help_color;\n> +\n> +\tcolor_fprintf_ln(stdout, help_color, \"status        - %s\",\n> +\t\t\t _(\"show paths with changes\"));\n> +\tcolor_fprintf_ln(stdout, help_color, \"update        - %s\",\n> +\t\t\t _(\"add working tree state to the staged set of changes\"));\n> +\tcolor_fprintf_ln(stdout, help_color, \"revert        - %s\",\n> +\t\t\t _(\"revert staged set of changes back to the HEAD version\"));\n> +\tcolor_fprintf_ln(stdout, help_color, \"patch         - %s\",\n> +\t\t\t _(\"pick hunks and update selectively\"));\n> +\tcolor_fprintf_ln(stdout, help_color, \"diff          - %s\",\n> +\t\t\t _(\"view diff between HEAD and index\"));\n> +\tcolor_fprintf_ln(stdout, help_color, \"add untracked - %s\",\n> +\t\t\t _(\"add contents of untracked files to the staged set of changes\"));\n\nAs we do not allow the command names to get translated, this makes\nsense.\n\nHave we adopted the convention to name callback parameters that have\nto stay unused (because the callback function must have a function\nsignature that accepts the union of what everybody needs to take)\ndifferently from the parameters that actually get used?  It may make\nsense to use it in a function like this, to prevent readers from\nwasting time by wondering how pathspec is used in this function, for\nexample.\n\n> diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\n> index 69991a3168..cf67756b85 100755\n> --- a/t/t3701-add-interactive.sh\n> +++ b/t/t3701-add-interactive.sh\n> @@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' '\n>  \ttest_write_lines a b a b a a b a b a >expect &&\n>  \ttest_cmp expect a\n>  '\n> +\n> +test_expect_success 'show help from add--helper' '\n> +\tgit reset --hard &&\n> +\tcat >expect <<-EOF &&\n> +\n> +\t<BOLD>*** Commands ***<RESET>\n> +\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n> +\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n> +\t<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>\n> +\t<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>\n> +\t<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>\n> +\t<BOLD;RED>patch         - pick hunks and update selectively<RESET>\n> +\t<BOLD;RED>diff          - view diff between HEAD and index<RESET>\n> +\t<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>\n> +\t<BOLD>*** Commands ***<RESET>\n> +\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n> +\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n> +\t<BOLD;BLUE>What now<RESET>>$SP\n> +\tBye.\n> +\tEOF\n> +\ttest_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&\n> +\ttest_decode_color <actual.colored >actual &&\n> +\ttest_i18ncmp expect actual\n> +'\n\nNicely done.\n\n"},{"id":"379856","messageId":"xmqqlfwbjn0f.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"nycvar.QRO.7.76.6.1907162037430.47@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-02T21:06:08Z","receivedAt":"2019-08-02T21:06:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> As I am a heavy user of git add -p myself and use a patched version for\n>> weeks already (it is so nice to not suffer over one second startup until the\n>> MSYS2 Perl finally shows me anything, instead it feels instantaneous), I\n>> integrated these patch series into Git for Windows' master already, as an\n>> opt-in feature guarded by the config variable add.interactive.useBuiltin\n>> (and Git for Windows' installer is prepared to detect this version and offer\n>> the option in the graphical user interface).\n\nI've sent comments on a few patches in the series; overall it was a\npleasant read.\n\nThanks.\n"},{"id":"379866","messageId":"20190802222657.GA526@sigill.intra.peff.net","threadId":"50913","inReplyTo":"xmqqpnlnjn3q.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 11/11] built-in add -i: implement the `help` command","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2019-08-02T22:26:58Z","receivedAt":"2019-08-02T22:27:00Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 02, 2019 at 02:04:09PM -0700, Junio C Hamano wrote:\n\n> > +static int run_help(struct add_i_state *s, const struct pathspec *ps,\n> > +\t\t    struct file_list *files, struct list_options *opts)\n> [...]\n> \n> As we do not allow the command names to get translated, this makes\n> sense.\n> \n> Have we adopted the convention to name callback parameters that have\n> to stay unused (because the callback function must have a function\n> signature that accepts the union of what everybody needs to take)\n> differently from the parameters that actually get used?  It may make\n> sense to use it in a function like this, to prevent readers from\n> wasting time by wondering how pathspec is used in this function, for\n> example.\n\nI haven't yet[1] polished up the remainder of my patches to make us\n-Wunused-parameter clean, but the pattern there would look like:\n\n  void some_function(const char *foo, void *UNUSED(bar))\n  {\n     ... use foo but not bar ...\n  }\n\nwhich both tells the compiler that \"bar\" may be unused, and renames it\nbehind the scenes to unused_bar so that it cannot be accidentally used\n(or more importantly, so that we can drop the annotation when it does\nget used).\n\nAll of which is to say that I'm fine if you call it \"unused_bar\"\nmanually for now, but I'd switch it to the above in my series. So it may\nnot matter all that much in the meantime.\n\n-Peff\n\n[1] The sticking point is a few more cases I found where it's unclear to\n    me whether they should be marked, or if it's a latent bug.\n"},{"id":"381067","messageId":"20190824123811.GL20404@szeder.dev","threadId":"50913","inReplyTo":"3000d7d08dfb64511b4ebf9d05617897dd7252f7.1563289115.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-08-24T12:38:11Z","receivedAt":"2019-08-24T12:38:20Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Jul 16, 2019 at 07:58:42AM -0700, Slavica Djukic via GitGitGadget wrote:\n> In the `git add -i` command, we show unique prefixes of the commands and\n> files, to give an indication what prefix would select them.\n> \n> Naturally, the C implementation looks a lot different than the Perl\n> implementation: in Perl, a trie is much easier implemented, while we\n> already have a pretty neat hashmap implementation in C that we use for\n> the purpose of storing (not necessarily unique) prefixes.\n> \n> The idea: for each item that we add, we generate prefixes starting with\n> the first letter, then the first two letters, then three, etc, until we\n> find a prefix that is unique (or until the prefix length would be\n> longer than we want). If we encounter a previously-unique prefix on the\n> way, we adjust that item's prefix to make it unique again (or we mark it\n> as having no unique prefix if we failed to find one). These partial\n> prefixes are stored in a hash map (for quick lookup times).\n> \n> To make sure that this function works as expected, we add a test using a\n> special-purpose test helper that was added for that purpose.\n> \n> Note: We expect the list of prefix items to be passed in as a list of\n> pointers rather than as regular list to avoid having to copy information\n> (the actual items will most likely contain more information than just\n> the name and the length of the unique prefix, but passing in `struct\n> prefix_item *` would not allow for that).\n\n> diff --git a/prefix-map.c b/prefix-map.c\n> new file mode 100644\n> index 0000000000..747ddb4ebc\n> --- /dev/null\n> +++ b/prefix-map.c\n> @@ -0,0 +1,109 @@\n> +#include \"cache.h\"\n> +#include \"prefix-map.h\"\n> +\n> +static int map_cmp(const void *unused_cmp_data,\n> +\t\t   const void *entry,\n> +\t\t   const void *entry_or_key,\n> +\t\t   const void *unused_keydata)\n> +{\n> +\tconst struct prefix_map_entry *a = entry;\n> +\tconst struct prefix_map_entry *b = entry_or_key;\n> +\n> +\treturn a->prefix_length != b->prefix_length ||\n> +\t\tstrncmp(a->name, b->name, a->prefix_length);\n> +}\n> +\n> +static void add_prefix_entry(struct hashmap *map, const char *name,\n> +\t\t\t     size_t prefix_length, struct prefix_item *item)\n> +{\n> +\tstruct prefix_map_entry *result = xmalloc(sizeof(*result));\n> +\tresult->name = name;\n> +\tresult->prefix_length = prefix_length;\n> +\tresult->item = item;\n> +\thashmap_entry_init(result, memhash(name, prefix_length));\n> +\thashmap_add(map, result);\n> +}\n> +\n> +static void init_prefix_map(struct prefix_map *prefix_map,\n> +\t\t\t    int min_prefix_length, int max_prefix_length)\n> +{\n> +\thashmap_init(&prefix_map->map, map_cmp, NULL, 0);\n> +\tprefix_map->min_length = min_prefix_length;\n> +\tprefix_map->max_length = max_prefix_length;\n> +}\n> +\n> +static void add_prefix_item(struct prefix_map *prefix_map,\n> +\t\t\t    struct prefix_item *item)\n> +{\n> +\tstruct prefix_map_entry e = { { NULL } }, *e2;\n> +\tint j;\n> +\n> +\te.item = item;\n> +\te.name = item->name;\n> +\n> +\tfor (j = prefix_map->min_length;\n> +\t     j <= prefix_map->max_length && e.name[j]; j++) {\n> +\t\t/* Avoid breaking UTF-8 multi-byte sequences */\n> +\t\tif (!isascii(e.name[j]))\n> +\t\t\tbreak;\n> +\n> +\t\te.prefix_length = j;\n> +\t\thashmap_entry_init(&e, memhash(e.name, j));\n> +\t\te2 = hashmap_get(&prefix_map->map, &e, NULL);\n> +\t\tif (!e2) {\n> +\t\t\t/* prefix is unique at this stage */\n> +\t\t\titem->prefix_length = j;\n> +\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j, item);\n> +\t\t\tbreak;\n> +\t\t}\n> +\n> +\t\tif (!e2->item)\n> +\t\t\tcontinue; /* non-unique prefix */\n> +\n> +\t\tif (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))\n> +\t\t\tBUG(\"unexpected prefix length: %d != %d (%s != %s)\",\n> +\t\t\t    j, (int)e2->item->prefix_length, e.name, e2->name);\n> +\n> +\t\t/* skip common prefix */\n> +\t\tfor (; j < prefix_map->max_length && e.name[j]; j++) {\n> +\t\t\tif (e.item->name[j] != e2->item->name[j])\n> +\t\t\t\tbreak;\n> +\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n> +\t\t\t\t\t NULL);\n> +\t\t}\n> +\n> +\t\t/* e2 no longer refers to a unique prefix */\n> +\t\tif (j < prefix_map->max_length && e2->name[j]) {\n> +\t\t\t/* found a new unique prefix for e2's item */\n> +\t\t\te2->item->prefix_length = j + 1;\n> +\t\t\tadd_prefix_entry(&prefix_map->map, e2->name, j + 1,\n> +\t\t\t\t\t e2->item);\n> +\t\t}\n> +\t\telse\n> +\t\t\te2->item->prefix_length = 0;\n> +\t\te2->item = NULL;\n> +\n> +\t\tif (j < prefix_map->max_length && e.name[j]) {\n> +\t\t\t/* found a unique prefix for the item */\n> +\t\t\te.item->prefix_length = j + 1;\n> +\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n> +\t\t\t\t\t e.item);\n> +\t\t} else\n> +\t\t\t/* item has no (short enough) unique prefix */\n> +\t\t\te.item->prefix_length = 0;\n> +\n> +\t\tbreak;\n> +\t}\n> +}\n> +\n> +void find_unique_prefixes(struct prefix_item **list, size_t nr,\n> +\t\t\t  int min_length, int max_length)\n> +{\n> +\tint i;\n> +\tstruct prefix_map prefix_map;\n> +\n> +\tinit_prefix_map(&prefix_map, min_length, max_length);\n> +\tfor (i = 0; i < nr; i++)\n> +\t\tadd_prefix_item(&prefix_map, list[i]);\n> +\thashmap_free(&prefix_map.map, 1);\n> +}\n\nBetween the commit message, the in-code comment, the names of the new\nfiles, and implementation I was left somewhat confused about what this\nis about and how it works.  TBH, I didn't even try to understand how\nall the above works, in particular the add_prefix_item() function.\n\nHowever, I think it would be much-much simpler to first sort (a copy\nof?) the array of prefix item pointers based on their 'name' field,\nand then look for a unique prefix in each neighboring pair.  Perhaps\nit would even be faster, because it doesn't have to allocate a bunch\nof hashmap items, though I don't think that it matters much in\npractice (i.e. I expect the number of items to be fairly small;\npresumably nobody will run interactive add after a mass refactoring\nmodifying thousands of files).\n\n> diff --git a/prefix-map.h b/prefix-map.h\n> new file mode 100644\n> index 0000000000..ce3b8a4a32\n> --- /dev/null\n> +++ b/prefix-map.h\n> @@ -0,0 +1,40 @@\n> +#ifndef PREFIX_MAP_H\n> +#define PREFIX_MAP_H\n>\n> +#include \"hashmap.h\"\n> +\n> +struct prefix_item {\n> +\tconst char *name;\n> +\tsize_t prefix_length;\n> +};\n\nThis struct is part of find_unique_prefixes()'s signature, good.\n\n> +struct prefix_map_entry {\n> +\tstruct hashmap_entry e;\n> +\tconst char *name;\n> +\tsize_t prefix_length;\n> +\t/* if item is NULL, the prefix is not unique */\n> +\tstruct prefix_item *item;\n> +};\n> +\n> +struct prefix_map {\n> +\tstruct hashmap map;\n> +\tint min_length, max_length;\n> +};\n\nHowever, neither of these two structs nor the hashmap appear in the\nfunction's signature, but are all implementation details.  Therefore,\nthey should not be defined and included here in the header but in the\n.c source file.  (But as mentioned above, I think this could be\nimplemented much simpler without these data structures.)\n\nFurthermore, this is not a map.\nA map, in general, is a container of key-value pairs that allows\nefficient insertion, removal and lookup.  This so-called prefix_map\ndoes none of that, so it should not be called a map.\n\n> +/*\n> + * Find unique prefixes in a given list of strings.\n\n... and stores the length of the unique prefixes in the\n'prefix_length' field of the elements of the given array.\n\n> + *\n> + * Typically, the `struct prefix_item` information will be but a field in the\n\ns/but //, perhaps?\n\n> + * actual item struct; For this reason, the `list` parameter is specified as a\n> + * list of pointers to the items.\n> + *\n> + * The `min_length`/`max_length` parameters define what length the unique\n> + * prefixes should have.\n> + *\n> + * If no unique prefix could be found for a given item, its `prefix_length`\n> + * will be set to 0.\n> + */\n> +void find_unique_prefixes(struct prefix_item **list, size_t nr,\n\nThe first argument is not a list but an array.\n\n> +\t\t\t  int min_length, int max_length);\n\nsize_t, perhaps?  These are closely related to\n'prefix_item.prefix_length', which is already (rightfully) size_t.\n\n> +\n> +#endif\n> diff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c\n> new file mode 100644\n> index 0000000000..3f1c90eaf0\n> --- /dev/null\n> +++ b/t/helper/test-prefix-map.c\n> @@ -0,0 +1,58 @@\n> +#include \"test-tool.h\"\n> +#include \"cache.h\"\n> +#include \"prefix-map.h\"\n> +\n> +static size_t test_count, failed_count;\n> +\n> +static void check(int succeeded, const char *file, size_t line_no,\n> +\t\t  const char *fmt, ...)\n> +{\n> +\tva_list ap;\n> +\n> +\ttest_count++;\n> +\tif (succeeded)\n> +\t\treturn;\n> +\n> +\tva_start(ap, fmt);\n> +\tfprintf(stderr, \"%s:%d: \", file, (int)line_no);\n> +\tvfprintf(stderr, fmt, ap);\n> +\tfputc('\\n', stderr);\n> +\tva_end(ap);\n> +\n> +\tfailed_count++;\n> +}\n> +\n> +#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \\\n> +\tcheck(expect == actual, __FILE__, __LINE__, \\\n> +\t      \"size_t's do not match: %\" \\\n> +\t      PRIdMAX \" != %\" PRIdMAX \" (%s) (%s)\", \\\n> +\t      (intmax_t)expect, (intmax_t)actual, #actual, hint)\n> +\n> +int cmd__prefix_map(int argc, const char **argv)\n> +{\n> +#define NR 5\n> +\tstruct prefix_item items[NR] = {\n\nYou don't have to tell the compiler how many elements this array will\ncontain, it will figure that out on its own.\n\n> +\t\t{ \"unique\" },\n> +\t\t{ \"hell\" },\n> +\t\t{ \"hello\" },\n> +\t\t{ \"wok\" },\n> +\t\t{ \"world\" },\n> +\t};\n> +\tstruct prefix_item *list[NR] = {\n\nLikewise.\n\n> +\t\titems, items + 1, items + 2, items + 3, items + 4\n> +\t};\n> +\n> +\tfind_unique_prefixes(list, NR, 1, 3);\n\nThis could be find_unique_prefixes(list, ARRAY_SIZE(list), 1, 3), and\nthen there is no need for that NR macro anymore.\n\n> +\n> +#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \\\n> +\tEXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \\\n> +\t\t\t     list[index]->name)\n> +\n> +\tEXPECT_PREFIX_LENGTH_EQUALS(1, 0);\n> +\tEXPECT_PREFIX_LENGTH_EQUALS(0, 1);\n> +\tEXPECT_PREFIX_LENGTH_EQUALS(0, 2);\n> +\tEXPECT_PREFIX_LENGTH_EQUALS(3, 3);\n> +\tEXPECT_PREFIX_LENGTH_EQUALS(3, 4);\n> +\n> +\treturn !!failed_count;\n> +}\n"},{"id":"381256","messageId":"nycvar.QRO.7.76.6.1908262317250.46@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"xmqq1ry6qeg2.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-08-26T21:26:22Z","receivedAt":"2019-08-26T21:26:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Wed, 31 Jul 2019, Junio C Hamano wrote:\n\n> \"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\n> writes:\n>\n> > +add.interactive.useBuiltin::\n>\n> I am not sure if three-level name is a good thing to use here.\n>\n> If we have end-user controllable (like branch or remote names)\n> unbounded number of subcommand/submode to \"add\", and \"interactive\"\n> is merely one of it, then three-level name is a perfect fit, but\n> otherwise, not.\n\nWell, my thinking was that `add.useBuiltin` would be misleading (because\nthe non-interactive part of `git add` is _already_ built-in, even `git\nadd -e` is built-in). And `addInteractive.useBuiltin`, to me, would\npretend that `add-interactive` is the name of the command.\n\nBesides, I really hope that this would be only temporary, as I already\nhave a fully-built-in `git add -i` and `git add -p` in Git for Windows,\nas an experimental opt-in, and so far it looks like it could replace the\nscripted version relatively soon, so maybe that particular part is not\nworth all that much worry ;-)\n\n> > @@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,\n> >  {\n> >  \tint status, i;\n> >  \tstruct argv_array argv = ARGV_ARRAY_INIT;\n> > +\tint use_builtin_add_i =\n> > +\t\tgit_env_bool(\"GIT_TEST_ADD_I_USE_BUILTIN\", -1);\n> > +\tif (use_builtin_add_i < 0)\n> > +\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n> > +\t\t\t\t    &use_builtin_add_i);\n> > +\n> > +\tif (use_builtin_add_i == 1 && !patch_mode)\n> > +\t\treturn !!run_add_i(the_repository, pathspec);\n>\n> I am hoping that eventually \"add -p\" will also be routed to the new\n> codepath.  Would it make sense to have \"&& !patch_mode\" here,\n> especially at this step where run_add_i() won't do anything useful\n> anyway yet?\n\nThe `&& !patch_mode` is here to allow for a gradual adoption of the\nbuilt-in parts. I don't want users who opted in to using the built-in\n`git add -i` to be stopped from using `git add -p`, so I don't want to\nprint even a warning, let alone an error message, when the patch mode\nneeds to run under `add.interactive.useBuiltin = true`, even if that\npart is still scripted-only.\n\nOf course, eventually this will be handled. See\nhttps://github.com/gitgitgadget/git/pull/173 for the\nyet-to-be-contributed patch series.\n\nI just don't want to send a multi-dozen patch series. I really don't\nthink there is any effective way to review such a long patch series,\nlet alone an efficient way to develop it incrementally based on feedback\non the mailing list, hance I broke things up into 6 separate patch\nseries (as indicated by the cover letter), and this one is the first of\nthem.\n\n> > @@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb)\n> >  \t\tignore_add_errors = git_config_bool(var, value);\n> >  \t\treturn 0;\n> >  \t}\n> > +\n> >  \treturn git_default_config(var, value, cb);\n> >  }\n>\n> Good addition while at it.\n\n:-)\n\nThis was actually an oversight, sorry... But since you're in favor ;-)\n\nCiao,\nDscho\n"},{"id":"381329","messageId":"nycvar.QRO.7.76.6.1908271121230.46@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"xmqqwofyozka.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 02/11] diff: export diffstat interface","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-08-27T09:22:42Z","receivedAt":"2019-08-27T09:23:10Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Wed, 31 Jul 2019, Junio C Hamano wrote:\n\n> \"Daniel Ferreira via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > @@ -6273,12 +6257,7 @@ void diff_flush(struct diff_options *options)\n> >  \t    dirstat_by_line) {\n> >  \t\tstruct diffstat_t diffstat;\n> >\n> > -\t\tmemset(&diffstat, 0, sizeof(struct diffstat_t));\n> > -\t\tfor (i = 0; i < q->nr; i++) {\n> > -\t\t\tstruct diff_filepair *p = q->queue[i];\n> > -\t\t\tif (check_pair_status(p))\n> > -\t\t\t\tdiff_flush_stat(p, options, &diffstat);\n> > -\t\t}\n> > +\t\tcompute_diffstat(options, &diffstat, q);\n> >  \t\tif (output_format & DIFF_FORMAT_NUMSTAT)\n> >  \t\t\tshow_numstat(&diffstat, options);\n> >  \t\tif (output_format & DIFF_FORMAT_DIFFSTAT)\n> > @@ -6611,6 +6590,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)\n> >  \treturn ignored;\n> >  }\n> >\n> > +void compute_diffstat(struct diff_options *options,\n> > +\t\t      struct diffstat_t *diffstat,\n> > +\t\t      struct diff_queue_struct *q)\n> > +{\n> > +\tint i;\n> > +\n> > +\tmemset(diffstat, 0, sizeof(struct diffstat_t));\n> > +\tfor (i = 0; i < q->nr; i++) {\n> > +\t\tstruct diff_filepair *p = q->queue[i];\n> > +\t\tif (check_pair_status(p))\n> > +\t\t\tdiff_flush_stat(p, options, diffstat);\n> > +\t}\n> > +}\n>\n> Hmm, (1) clearing diffstat struct to initialize, (2) looping over\n> diff_queue to compute stat for each path, (3) using diffstat\n> information and then (4) finally freeing the diffstat info is the\n> bog-standard sequence of the user of this API.  Merging step (1) and\n> (2) may probably be OK (iow, I do not think of a use pattern for\n> future users where being able to do some custom things between steps\n> (1) and (2) would be useful), which is this function is about.  (3)\n> is what the user of this API would do, but shouldn't (4) be exported\n> at the same time, if we are making (1+2) as an external API?\n\nGood point.\n\nIt _also_ hints at the fact that we're not releasing the memory properly\nafter running the diffstat in the built-in `add -i`.\n\nWill fix,\nDscho\n\n>\n> >  void diff_addremove(struct diff_options *options,\n> >  \t\t    int addremove, unsigned mode,\n> >  \t\t    const struct object_id *oid,\n> > diff --git a/diff.h b/diff.h\n> > index b680b377b2..34fc658946 100644\n> > --- a/diff.h\n> > +++ b/diff.h\n> > @@ -244,6 +244,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);\n> >  void diff_emit_submodule_pipethrough(struct diff_options *o,\n> >  \t\t\t\t     const char *line, int len);\n> >\n> > +struct diffstat_t {\n> > +\tint nr;\n> > +\tint alloc;\n> > +\tstruct diffstat_file {\n> > +\t\tchar *from_name;\n> > +\t\tchar *name;\n> > +\t\tchar *print_name;\n> > +\t\tconst char *comments;\n> > +\t\tunsigned is_unmerged:1;\n> > +\t\tunsigned is_binary:1;\n> > +\t\tunsigned is_renamed:1;\n> > +\t\tunsigned is_interesting:1;\n> > +\t\tuintmax_t added, deleted;\n> > +\t} **files;\n> > +};\n> > +\n> >  enum color_diff {\n> >  \tDIFF_RESET = 0,\n> >  \tDIFF_CONTEXT = 1,\n> > @@ -333,6 +349,9 @@ void diff_change(struct diff_options *,\n> >\n> >  struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);\n> >\n> > +void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,\n> > +\t\t      struct diff_queue_struct *q);\n> > +\n> >  #define DIFF_SETUP_REVERSE      \t1\n> >  #define DIFF_SETUP_USE_SIZE_CACHE\t4\n>\n"},{"id":"381335","messageId":"nycvar.QRO.7.76.6.1908271123570.46@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"xmqqsgqmoyz1.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 03/11] built-in add -i: implement the `status` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-08-27T10:04:42Z","receivedAt":"2019-08-27T10:05:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Wed, 31 Jul 2019, Junio C Hamano wrote:\n\n> \"Daniel Ferreira via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > +struct item {\n> > +\tconst char *name;\n> > +};\n> > +\n> > +struct list_options {\n> > +\tconst char *header;\n> > +\tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n> > +\tvoid *print_item_data;\n> > +};\n> > +\n> > +struct adddel {\n> > +\tuintmax_t add, del;\n> > +\tunsigned seen:1, binary:1;\n> > +};\n> > +\n> > +struct file_list {\n> > +\tstruct file_item {\n> > +\t\tstruct item item;\n> > +\t\tstruct adddel index, worktree;\n> > +\t} **file;\n> > +\tsize_t nr, alloc;\n> > +};\n> > +\n> > +struct pathname_entry {\n> > +\tstruct hashmap_entry ent;\n> > +\tsize_t index;\n> > +\tchar pathname[FLEX_ARRAY];\n> > +};\n>\n> All of the above are named too generic but assuming that add-i will\n> stay to be a single file and these names will never leak outside the\n> file to become global, it would be perfectly fine.\n\nYep.\n\n> > +static void populate_wi_changes(struct strbuf *buf,\n> > +\t\t\t\tstruct adddel *ad, const char *no_changes)\n> > +{\n> > +\tif (ad->binary)\n> > +\t\tstrbuf_addstr(buf, _(\"binary\"));\n> > +\telse if (ad->seen)\n> > +\t\tstrbuf_addf(buf, \"+%\"PRIuMAX\"/-%\"PRIuMAX,\n> > +\t\t\t    (uintmax_t)ad->add, (uintmax_t)ad->del);\n> > +\telse\n> > +\t\tstrbuf_addstr(buf, no_changes);\n> > +}\n>\n> I offhand do not see the need for (uintmax_t) casts here...\n\nAh, that's my mistake. I had converted the `add` and `del` fields from\nthe `unsigned long` data type that the 20th century wanted back.\n\n> > +static int run_status(struct repository *r, const struct pathspec *ps,\n> > +\t\t      struct file_list *files, struct list_options *opts)\n> > +{\n> > +\treset_file_list(files);\n> > +\n> > +\tif (get_modified_files(r, files, ps) < 0)\n> > +\t\treturn -1;\n> > +\n> > +\tif (files->nr)\n> > +\t\tlist((struct item **)files->file, files->nr, opts);\n> > +\tputchar('\\n');\n>\n> So, if there is anything to list, we show list() and then add an\n> empty line; if there is nothing to list, we show an empty line\n> anyway?\n>\n> As long as that matches the current scripted \"add -i\", it's\n> perfectly fine.  It's just that the code structure above looked\n> somewhat odd.\n\nYep, this is the Perl version:\n\nsub status_cmd {\n        list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },\n\t\t\tlist_modified());\n\tprint \"\\n\";\n}\n\n> > +static void collect_changes_cb(struct diff_queue_struct *q,\n> > +\t\t\t       struct diff_options *options,\n> > +\t\t\t       void *data)\n> > +{\n> > +\tstruct collection_status *s = data;\n> > +\tstruct diffstat_t stat = { 0 };\n> > +\tint i;\n> > +\n> > +\tif (!q->nr)\n> > +\t\treturn;\n> > +\n> > +\tcompute_diffstat(options, &stat, q);\n> > +\n> > +\tfor (i = 0; i < stat.nr; i++) {\n> > +\t\tconst char *name = stat.files[i]->name;\n> > +\t\tint hash = strhash(name);\n> > +\t\tstruct pathname_entry *entry;\n> > +\t\tsize_t file_index;\n> > +\t\tstruct file_item *file;\n> > +\t\tstruct adddel *adddel;\n> > +\n> > +\t\tentry = hashmap_get_from_hash(&s->file_map, hash, name);\n> > +\t\tif (entry)\n> > +\t\t\tfile_index = entry->index;\n> > +\t\telse {\n> > +\t\t\tFLEX_ALLOC_STR(entry, pathname, name);\n> > +\t\t\thashmap_entry_init(entry, hash);\n> > +\t\t\tentry->index = file_index = s->list->nr;\n> > +\t\t\thashmap_add(&s->file_map, entry);\n> > +\n> > +\t\t\tadd_file_item(s->list, name);\n> > +\t\t}\n> > +\t\tfile = s->list->file[file_index];\n> > +\n> > +\t\tadddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;\n> > +\t\tadddel->seen = 1;\n> > +\t\tadddel->add = stat.files[i]->added;\n> > +\t\tadddel->del = stat.files[i]->deleted;\n> > +\t\tif (stat.files[i]->is_binary)\n> > +\t\t\tadddel->binary = 1;\n> > +\t}\n> > +}\n>\n> Would resources held in the \"stat\" structure leak at the end of this\n> function?\n\nYep, I added a call to the now-public `free_diffstat_info()` function.\n\nThanks,\nDscho\n"},{"id":"381347","messageId":"nycvar.QRO.7.76.6.1908271342140.46@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"20190824123811.GL20404@szeder.dev","subject":"Re: [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-08-27T12:14:06Z","receivedAt":"2019-08-27T12:14:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Gábor,\n\nOn Sat, 24 Aug 2019, SZEDER Gábor wrote:\n\n> On Tue, Jul 16, 2019 at 07:58:42AM -0700, Slavica Djukic via GitGitGadget wrote:\n> > In the `git add -i` command, we show unique prefixes of the commands and\n> > files, to give an indication what prefix would select them.\n> >\n> > Naturally, the C implementation looks a lot different than the Perl\n> > implementation: in Perl, a trie is much easier implemented, while we\n> > already have a pretty neat hashmap implementation in C that we use for\n> > the purpose of storing (not necessarily unique) prefixes.\n> >\n> > The idea: for each item that we add, we generate prefixes starting with\n> > the first letter, then the first two letters, then three, etc, until we\n> > find a prefix that is unique (or until the prefix length would be\n> > longer than we want). If we encounter a previously-unique prefix on the\n> > way, we adjust that item's prefix to make it unique again (or we mark it\n> > as having no unique prefix if we failed to find one). These partial\n> > prefixes are stored in a hash map (for quick lookup times).\n> >\n> > To make sure that this function works as expected, we add a test using a\n> > special-purpose test helper that was added for that purpose.\n> >\n> > Note: We expect the list of prefix items to be passed in as a list of\n> > pointers rather than as regular list to avoid having to copy information\n> > (the actual items will most likely contain more information than just\n> > the name and the length of the unique prefix, but passing in `struct\n> > prefix_item *` would not allow for that).\n>\n> > diff --git a/prefix-map.c b/prefix-map.c\n> > new file mode 100644\n> > index 0000000000..747ddb4ebc\n> > --- /dev/null\n> > +++ b/prefix-map.c\n> > @@ -0,0 +1,109 @@\n> > +#include \"cache.h\"\n> > +#include \"prefix-map.h\"\n> > +\n> > +static int map_cmp(const void *unused_cmp_data,\n> > +\t\t   const void *entry,\n> > +\t\t   const void *entry_or_key,\n> > +\t\t   const void *unused_keydata)\n> > +{\n> > +\tconst struct prefix_map_entry *a = entry;\n> > +\tconst struct prefix_map_entry *b = entry_or_key;\n> > +\n> > +\treturn a->prefix_length != b->prefix_length ||\n> > +\t\tstrncmp(a->name, b->name, a->prefix_length);\n> > +}\n> > +\n> > +static void add_prefix_entry(struct hashmap *map, const char *name,\n> > +\t\t\t     size_t prefix_length, struct prefix_item *item)\n> > +{\n> > +\tstruct prefix_map_entry *result = xmalloc(sizeof(*result));\n> > +\tresult->name = name;\n> > +\tresult->prefix_length = prefix_length;\n> > +\tresult->item = item;\n> > +\thashmap_entry_init(result, memhash(name, prefix_length));\n> > +\thashmap_add(map, result);\n> > +}\n> > +\n> > +static void init_prefix_map(struct prefix_map *prefix_map,\n> > +\t\t\t    int min_prefix_length, int max_prefix_length)\n> > +{\n> > +\thashmap_init(&prefix_map->map, map_cmp, NULL, 0);\n> > +\tprefix_map->min_length = min_prefix_length;\n> > +\tprefix_map->max_length = max_prefix_length;\n> > +}\n> > +\n> > +static void add_prefix_item(struct prefix_map *prefix_map,\n> > +\t\t\t    struct prefix_item *item)\n> > +{\n> > +\tstruct prefix_map_entry e = { { NULL } }, *e2;\n> > +\tint j;\n> > +\n> > +\te.item = item;\n> > +\te.name = item->name;\n> > +\n> > +\tfor (j = prefix_map->min_length;\n> > +\t     j <= prefix_map->max_length && e.name[j]; j++) {\n> > +\t\t/* Avoid breaking UTF-8 multi-byte sequences */\n> > +\t\tif (!isascii(e.name[j]))\n> > +\t\t\tbreak;\n> > +\n> > +\t\te.prefix_length = j;\n> > +\t\thashmap_entry_init(&e, memhash(e.name, j));\n> > +\t\te2 = hashmap_get(&prefix_map->map, &e, NULL);\n> > +\t\tif (!e2) {\n> > +\t\t\t/* prefix is unique at this stage */\n> > +\t\t\titem->prefix_length = j;\n> > +\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j, item);\n> > +\t\t\tbreak;\n> > +\t\t}\n> > +\n> > +\t\tif (!e2->item)\n> > +\t\t\tcontinue; /* non-unique prefix */\n> > +\n> > +\t\tif (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))\n> > +\t\t\tBUG(\"unexpected prefix length: %d != %d (%s != %s)\",\n> > +\t\t\t    j, (int)e2->item->prefix_length, e.name, e2->name);\n> > +\n> > +\t\t/* skip common prefix */\n> > +\t\tfor (; j < prefix_map->max_length && e.name[j]; j++) {\n> > +\t\t\tif (e.item->name[j] != e2->item->name[j])\n> > +\t\t\t\tbreak;\n> > +\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n> > +\t\t\t\t\t NULL);\n> > +\t\t}\n> > +\n> > +\t\t/* e2 no longer refers to a unique prefix */\n> > +\t\tif (j < prefix_map->max_length && e2->name[j]) {\n> > +\t\t\t/* found a new unique prefix for e2's item */\n> > +\t\t\te2->item->prefix_length = j + 1;\n> > +\t\t\tadd_prefix_entry(&prefix_map->map, e2->name, j + 1,\n> > +\t\t\t\t\t e2->item);\n> > +\t\t}\n> > +\t\telse\n> > +\t\t\te2->item->prefix_length = 0;\n> > +\t\te2->item = NULL;\n> > +\n> > +\t\tif (j < prefix_map->max_length && e.name[j]) {\n> > +\t\t\t/* found a unique prefix for the item */\n> > +\t\t\te.item->prefix_length = j + 1;\n> > +\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n> > +\t\t\t\t\t e.item);\n> > +\t\t} else\n> > +\t\t\t/* item has no (short enough) unique prefix */\n> > +\t\t\te.item->prefix_length = 0;\n> > +\n> > +\t\tbreak;\n> > +\t}\n> > +}\n> > +\n> > +void find_unique_prefixes(struct prefix_item **list, size_t nr,\n> > +\t\t\t  int min_length, int max_length)\n> > +{\n> > +\tint i;\n> > +\tstruct prefix_map prefix_map;\n> > +\n> > +\tinit_prefix_map(&prefix_map, min_length, max_length);\n> > +\tfor (i = 0; i < nr; i++)\n> > +\t\tadd_prefix_item(&prefix_map, list[i]);\n> > +\thashmap_free(&prefix_map.map, 1);\n> > +}\n>\n> Between the commit message, the in-code comment, the names of the new\n> files, and implementation I was left somewhat confused about what this\n> is about and how it works.  TBH, I didn't even try to understand how\n> all the above works, in particular the add_prefix_item() function.\n\nLet me try to explain it here, and maybe you can help me by suggesting\nan improved commit message and/or code comments?\n\nThe problem is this: given a set of items with labels (e.g. file names),\nfind, for each item, the unique prefix that identifies it. Example:\ngiven the files `hello.txt`, `heaven.txt` and `hell.txt`, the items'\nunique prefixes would be `hello`, `hea` and `hell.`, respectively.\n\nIn `git add -i`, we actually only want to allow alphanumerical prefixes,\nand we also want at least one, and at most three characters, so only the\nsecond item would have an admissible unique prefix: `hea`.\n\n> However, I think it would be much-much simpler to first sort (a copy\n> of?) the array of prefix item pointers based on their 'name' field,\n> and then look for a unique prefix in each neighboring pair.  Perhaps\n> it would even be faster, because it doesn't have to allocate a bunch\n> of hashmap items, though I don't think that it matters much in\n> practice (i.e. I expect the number of items to be fairly small;\n> presumably nobody will run interactive add after a mass refactoring\n> modifying thousands of files).\n\nThe time complexity of the sorted list would be O(n*log(n)), while the\nhashmap-based complexity would be an amortized O(n).\n\nAnd yes, you would not _want_ to run interactive add after a mass\nrefactoring. But it happens. It happens to me more times than I care to\nadmit. And you know what? I really appreciate that even the Perl version\nis relatively snappy in those circumstances.\n\n> > diff --git a/prefix-map.h b/prefix-map.h\n> > new file mode 100644\n> > index 0000000000..ce3b8a4a32\n> > --- /dev/null\n> > +++ b/prefix-map.h\n> > @@ -0,0 +1,40 @@\n> > +#ifndef PREFIX_MAP_H\n> > +#define PREFIX_MAP_H\n> >\n> > +#include \"hashmap.h\"\n> > +\n> > +struct prefix_item {\n> > +\tconst char *name;\n> > +\tsize_t prefix_length;\n> > +};\n>\n> This struct is part of find_unique_prefixes()'s signature, good.\n>\n> > +struct prefix_map_entry {\n> > +\tstruct hashmap_entry e;\n> > +\tconst char *name;\n> > +\tsize_t prefix_length;\n> > +\t/* if item is NULL, the prefix is not unique */\n> > +\tstruct prefix_item *item;\n> > +};\n> > +\n> > +struct prefix_map {\n> > +\tstruct hashmap map;\n> > +\tint min_length, max_length;\n> > +};\n>\n> However, neither of these two structs nor the hashmap appear in the\n> function's signature, but are all implementation details.  Therefore,\n> they should not be defined and included here in the header but in the\n> .c source file.  (But as mentioned above, I think this could be\n> implemented much simpler without these data structures.)\n\nRight you are!\n\n> Furthermore, this is not a map.\n> A map, in general, is a container of key-value pairs that allows\n> efficient insertion, removal and lookup.  This so-called prefix_map\n> does none of that, so it should not be called a map.\n\nWhat would you call it instead?\n\n(I went with \"map\" because the underlying data structure is a \"hash\nmap\", I know, not the best argument, but I failed to find a better\nname...)\n\nI also have to admit that I thought that I could fix the design where\n`git-add--interactive.perl` creates this trie, but then still performs a\nlinear search when searching by prefix. That seems not to be possible,\nthough, as the unique prefixes are limited to certain character ranges,\nwhile the lookup-by-prefix is not limited in that way.\n\n> > +/*\n> > + * Find unique prefixes in a given list of strings.\n>\n> ... and stores the length of the unique prefixes in the\n> 'prefix_length' field of the elements of the given array.\n\nGood idea. I changed it to also explain what is meant by \"unique\nprefix\":\n\n * Given a list of names, find unique prefixes (i.e. the first <n> characters\n * that uniquely identify the names) and store the lengths of the unique\n * prefixes in the 'prefix_length' field of the elements of the given array..\n\n> > + *\n> > + * Typically, the `struct prefix_item` information will be but a field in the\n>\n> s/but //, perhaps?\n\nSure. I am relatively certain that it is correct grammar, but it is\nprobably a good idea to remove it.\n\n> > + * actual item struct; For this reason, the `list` parameter is specified as a\n> > + * list of pointers to the items.\n> > + *\n> > + * The `min_length`/`max_length` parameters define what length the unique\n> > + * prefixes should have.\n> > + *\n> > + * If no unique prefix could be found for a given item, its `prefix_length`\n> > + * will be set to 0.\n> > + */\n> > +void find_unique_prefixes(struct prefix_item **list, size_t nr,\n>\n> The first argument is not a list but an array.\n\nIndeed.\n\n> > +\t\t\t  int min_length, int max_length);\n>\n> size_t, perhaps?  These are closely related to\n> 'prefix_item.prefix_length', which is already (rightfully) size_t.\n\nTrue.\n\n> > +#endif\n> > diff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c\n> > new file mode 100644\n> > index 0000000000..3f1c90eaf0\n> > --- /dev/null\n> > +++ b/t/helper/test-prefix-map.c\n> > @@ -0,0 +1,58 @@\n> > +#include \"test-tool.h\"\n> > +#include \"cache.h\"\n> > +#include \"prefix-map.h\"\n> > +\n> > +static size_t test_count, failed_count;\n> > +\n> > +static void check(int succeeded, const char *file, size_t line_no,\n> > +\t\t  const char *fmt, ...)\n> > +{\n> > +\tva_list ap;\n> > +\n> > +\ttest_count++;\n> > +\tif (succeeded)\n> > +\t\treturn;\n> > +\n> > +\tva_start(ap, fmt);\n> > +\tfprintf(stderr, \"%s:%d: \", file, (int)line_no);\n> > +\tvfprintf(stderr, fmt, ap);\n> > +\tfputc('\\n', stderr);\n> > +\tva_end(ap);\n> > +\n> > +\tfailed_count++;\n> > +}\n> > +\n> > +#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \\\n> > +\tcheck(expect == actual, __FILE__, __LINE__, \\\n> > +\t      \"size_t's do not match: %\" \\\n> > +\t      PRIdMAX \" != %\" PRIdMAX \" (%s) (%s)\", \\\n> > +\t      (intmax_t)expect, (intmax_t)actual, #actual, hint)\n> > +\n> > +int cmd__prefix_map(int argc, const char **argv)\n> > +{\n> > +#define NR 5\n> > +\tstruct prefix_item items[NR] = {\n>\n> You don't have to tell the compiler how many elements this array will\n> contain, it will figure that out on its own.\n>\n> > +\t\t{ \"unique\" },\n> > +\t\t{ \"hell\" },\n> > +\t\t{ \"hello\" },\n> > +\t\t{ \"wok\" },\n> > +\t\t{ \"world\" },\n> > +\t};\n> > +\tstruct prefix_item *list[NR] = {\n>\n> Likewise.\n\nThat is correct.\n\nWhat the compiler _cannot_ figure out, on its own, however, is that\n`items` and `list` _need_ to contain the same number of items.\n\nHence the need for `NR`.\n\n> > +\t\titems, items + 1, items + 2, items + 3, items + 4\n> > +\t};\n> > +\n> > +\tfind_unique_prefixes(list, NR, 1, 3);\n>\n> This could be find_unique_prefixes(list, ARRAY_SIZE(list), 1, 3), and\n> then there is no need for that NR macro anymore.\n>\n> > +\n> > +#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \\\n> > +\tEXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \\\n> > +\t\t\t     list[index]->name)\n> > +\n> > +\tEXPECT_PREFIX_LENGTH_EQUALS(1, 0);\n> > +\tEXPECT_PREFIX_LENGTH_EQUALS(0, 1);\n> > +\tEXPECT_PREFIX_LENGTH_EQUALS(0, 2);\n> > +\tEXPECT_PREFIX_LENGTH_EQUALS(3, 3);\n> > +\tEXPECT_PREFIX_LENGTH_EQUALS(3, 4);\n> > +\n> > +\treturn !!failed_count;\n> > +}\n\nThank you for your review!\nDscho\n"},{"id":"381348","messageId":"pull.170.v4.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v4 00/11] git add -i: add a rudimentary version in C (supporting only status and help so far)","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-27T12:57:53Z","receivedAt":"2019-08-27T12:57:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is the first leg on the long journey to a fully built-in git add -i \n(next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3\n[https://github.com/gitgitgadget/git/pull/172], 4\n[https://github.com/gitgitgadget/git/pull/173], 5\n[https://github.com/gitgitgadget/git/pull/174], and 6\n[https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are\nnot necessarily up to date, and will be re-targeted to the appropriate\nbranches in https://github.com/gitster/git as soon as Junio picks them up.\n\nThis here patch series reflects the part that was submitted a couple of\ntimes (see https://github.com/gitgitgadget/git/pull/103) during the\nOutreachy project by Slavica Ðukic that continued the journey based on an\ninitial patch series by Daniel Ferreira.\n\nIt only implements the status and the help part, in the interest of making\nthe review remotely more reviewable.\n\nAs I am a heavy user of git add -p myself and use a patched version for\nweeks already (it is so nice to not suffer over one second startup until the\nMSYS2 Perl finally shows me anything, instead it feels instantaneous), I\nintegrated these patch series into Git for Windows' master already, as an\nopt-in feature guarded by the config variable add.interactive.useBuiltin \n(and Git for Windows' installer is prepared to detect this version and offer\nthe option in the graphical user interface).\n\nI had planned on submitting this before v2.22.0-rc0, but there was such a\nbacklog of builds from a big pushout that I had to wait ;-)\n\nChanges since v3:\n\n * Rebased to v2.23.0 to reduce friction.\n * free_diffstat_info() is now made public as well, and used, to avoid a\n   memory leak.\n * Prepared the patches for ew/hashmap (which is strict about the hashmap\n   entries' type in hashmap_entry_init() and friends).\n * The private data types have been moved from prefix-map.h to prefix-map.c.\n * A lot of int types were converted to more appropriate size_t in \n   prefix-map.c.\n * A misleading parameter name list was renamed to the correct array.\n * The code comment above find_unique_prefixes() was (hopefully) improved.\n * The run_help() function's signature now reflects that most of the\n   parameters are actually unused.\n\nChanges since v2:\n\n * Rebased to master to avoid merge conflicts.\n * Renumbered the prefix-map test to avoid conflicts with two patch series\n   that are currently in-flight in pu.\n\nChanges since v1:\n\n * The config machinery was reworked completely, to not use a callback to \n   git_config(), but instead to query the config via the repo_config_get_*() \n   functions. This also prevents a future \"Huh???\" moment: the internal add\n   --interactive API accepts a parameter of type struct repository *r, but\n   the previous configuration did not use that to query the config (and\n   could in the future be a repository other than the_repository).\n   \n   \n * As a consequence, the color sequences are no longer stored in file-local\n   variables, but passed around via a struct.\n   \n   \n * Instead of using the magical constant -2 to quit the main loop, it is now\n   defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is\n   defined as -1 and used where appropriate).\n   \n   \n * Improved the add_prefix_item() function by avoiding buffer overruns, not\n   reusing the struct that is used for lookup also for adding the new item,\n   and by strengthening the bug check.\n\nDaniel Ferreira (2):\n  diff: export diffstat interface\n  built-in add -i: implement the `status` command\n\nJohannes Schindelin (6):\n  Start to implement a built-in version of `git add --interactive`\n  built-in add -i: refresh the index before running `status`\n  built-in add -i: color the header in the `status` command\n  built-in add -i: implement the main loop\n  built-in add -i: support `?` (prompt help)\n  built-in add -i: implement the `help` command\n\nSlavica Djukic (3):\n  Add a function to determine unique prefixes for a list of strings\n  built-in add -i: show unique prefixes of the commands\n  built-in add -i: use color in the main loop\n\n Documentation/config/add.txt |   5 +\n Makefile                     |   3 +\n add-interactive.c            | 558 +++++++++++++++++++++++++++++++++++\n add-interactive.h            |   8 +\n builtin/add.c                |  10 +\n diff.c                       |  39 +--\n diff.h                       |  20 ++\n prefix-map.c                 | 123 ++++++++\n prefix-map.h                 |  29 ++\n repository.c                 |  19 ++\n repository.h                 |   7 +\n t/README                     |   4 +\n t/helper/test-prefix-map.c   |  58 ++++\n t/helper/test-tool.c         |   1 +\n t/helper/test-tool.h         |   1 +\n t/t0018-prefix-map.sh        |  10 +\n t/t3701-add-interactive.sh   |  25 ++\n 17 files changed, 897 insertions(+), 23 deletions(-)\n create mode 100644 add-interactive.c\n create mode 100644 add-interactive.h\n create mode 100644 prefix-map.c\n create mode 100644 prefix-map.h\n create mode 100644 t/helper/test-prefix-map.c\n create mode 100755 t/t0018-prefix-map.sh\n\n\nbase-commit: 5fa0f5238b0cd46cfe7f6fa76c3f526ea98148d9\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/170\n\nRange-diff vs v3:\n\n  1:  0a5ec9345d =  1:  ad8752eca7 Start to implement a built-in version of `git add --interactive`\n  2:  c7a377890d !  2:  38cc04c1d9 diff: export diffstat interface\n     @@ -41,6 +41,15 @@\n       static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,\n       \t\t\t\t\t  const char *name_a,\n       \t\t\t\t\t  const char *name_b)\n     +@@\n     + \tgather_dirstat(options, &dir, changed, \"\", 0);\n     + }\n     + \n     +-static void free_diffstat_info(struct diffstat_t *diffstat)\n     ++void free_diffstat_info(struct diffstat_t *diffstat)\n     + {\n     + \tint i;\n     + \tfor (i = 0; i < diffstat->nr; i++) {\n      @@\n       \t    dirstat_by_line) {\n       \t\tstruct diffstat_t diffstat;\n     @@ -109,6 +118,7 @@\n       \n      +void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,\n      +\t\t      struct diff_queue_struct *q);\n     ++void free_diffstat_info(struct diffstat_t *diffstat);\n      +\n       #define DIFF_SETUP_REVERSE      \t1\n       #define DIFF_SETUP_USE_SIZE_CACHE\t4\n  3:  b93b055ebe !  3:  ee3e40293c built-in add -i: implement the `status` command\n     @@ -158,9 +158,9 @@\n      +\t\t\tfile_index = entry->index;\n      +\t\telse {\n      +\t\t\tFLEX_ALLOC_STR(entry, pathname, name);\n     -+\t\t\thashmap_entry_init(entry, hash);\n     ++\t\t\thashmap_entry_init(&entry->ent, hash);\n      +\t\t\tentry->index = file_index = s->list->nr;\n     -+\t\t\thashmap_add(&s->file_map, entry);\n     ++\t\t\thashmap_add(&s->file_map, &entry->ent);\n      +\n      +\t\t\tadd_file_item(s->list, name);\n      +\t\t}\n     @@ -173,6 +173,7 @@\n      +\t\tif (stat.files[i]->is_binary)\n      +\t\t\tadddel->binary = 1;\n      +\t}\n     ++\tfree_diffstat_info(&stat);\n      +}\n      +\n      +static int get_modified_files(struct repository *r, struct file_list *list,\n  4:  daff24074a =  4:  3c855d9fa5 built-in add -i: refresh the index before running `status`\n  5:  15f18f5b3e =  5:  24737a09f7 built-in add -i: color the header in the `status` command\n  6:  175409aaae =  6:  ac67731cf1 built-in add -i: implement the main loop\n  7:  3000d7d08d !  7:  c5a699b6b2 Add a function to determine unique prefixes for a list of strings\n     @@ -19,7 +19,7 @@\n          prefixes are stored in a hash map (for quick lookup times).\n      \n          To make sure that this function works as expected, we add a test using a\n     -    special-purpose test helper that was added for that purpose.\n     +    special-purpose test helper.\n      \n          Note: We expect the list of prefix items to be passed in as a list of\n          pointers rather than as regular list to avoid having to copy information\n     @@ -58,6 +58,19 @@\n      +#include \"cache.h\"\n      +#include \"prefix-map.h\"\n      +\n     ++struct prefix_map_entry {\n     ++\tstruct hashmap_entry e;\n     ++\tconst char *name;\n     ++\tsize_t prefix_length;\n     ++\t/* if item is NULL, the prefix is not unique */\n     ++\tstruct prefix_item *item;\n     ++};\n     ++\n     ++struct prefix_map {\n     ++\tstruct hashmap map;\n     ++\tsize_t min_length, max_length;\n     ++};\n     ++\n      +static int map_cmp(const void *unused_cmp_data,\n      +\t\t   const void *entry,\n      +\t\t   const void *entry_or_key,\n     @@ -77,12 +90,12 @@\n      +\tresult->name = name;\n      +\tresult->prefix_length = prefix_length;\n      +\tresult->item = item;\n     -+\thashmap_entry_init(result, memhash(name, prefix_length));\n     -+\thashmap_add(map, result);\n     ++\thashmap_entry_init(&result->e, memhash(name, prefix_length));\n     ++\thashmap_add(map, &result->e);\n      +}\n      +\n      +static void init_prefix_map(struct prefix_map *prefix_map,\n     -+\t\t\t    int min_prefix_length, int max_prefix_length)\n     ++\t\t\t    size_t min_prefix_length, size_t max_prefix_length)\n      +{\n      +\thashmap_init(&prefix_map->map, map_cmp, NULL, 0);\n      +\tprefix_map->min_length = min_prefix_length;\n     @@ -93,7 +106,7 @@\n      +\t\t\t    struct prefix_item *item)\n      +{\n      +\tstruct prefix_map_entry e = { { NULL } }, *e2;\n     -+\tint j;\n     ++\tsize_t j;\n      +\n      +\te.item = item;\n      +\te.name = item->name;\n     @@ -105,8 +118,8 @@\n      +\t\t\tbreak;\n      +\n      +\t\te.prefix_length = j;\n     -+\t\thashmap_entry_init(&e, memhash(e.name, j));\n     -+\t\te2 = hashmap_get(&prefix_map->map, &e, NULL);\n     ++\t\thashmap_entry_init(&e.e, memhash(e.name, j));\n     ++\t\te2 = hashmap_get(&prefix_map->map, &e.e, NULL);\n      +\t\tif (!e2) {\n      +\t\t\t/* prefix is unique at this stage */\n      +\t\t\titem->prefix_length = j;\n     @@ -119,7 +132,8 @@\n      +\n      +\t\tif (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))\n      +\t\t\tBUG(\"unexpected prefix length: %d != %d (%s != %s)\",\n     -+\t\t\t    j, (int)e2->item->prefix_length, e.name, e2->name);\n     ++\t\t\t    (int)j, (int)e2->item->prefix_length,\n     ++\t\t\t    e.name, e2->name);\n      +\n      +\t\t/* skip common prefix */\n      +\t\tfor (; j < prefix_map->max_length && e.name[j]; j++) {\n     @@ -153,15 +167,15 @@\n      +\t}\n      +}\n      +\n     -+void find_unique_prefixes(struct prefix_item **list, size_t nr,\n     -+\t\t\t  int min_length, int max_length)\n     ++void find_unique_prefixes(struct prefix_item **array, size_t nr,\n     ++\t\t\t  size_t min_length, size_t max_length)\n      +{\n     -+\tint i;\n     ++\tsize_t i;\n      +\tstruct prefix_map prefix_map;\n      +\n      +\tinit_prefix_map(&prefix_map, min_length, max_length);\n      +\tfor (i = 0; i < nr; i++)\n     -+\t\tadd_prefix_item(&prefix_map, list[i]);\n     ++\t\tadd_prefix_item(&prefix_map, array[i]);\n      +\thashmap_free(&prefix_map.map, 1);\n      +}\n      \n     @@ -180,25 +194,14 @@\n      +\tsize_t prefix_length;\n      +};\n      +\n     -+struct prefix_map_entry {\n     -+\tstruct hashmap_entry e;\n     -+\tconst char *name;\n     -+\tsize_t prefix_length;\n     -+\t/* if item is NULL, the prefix is not unique */\n     -+\tstruct prefix_item *item;\n     -+};\n     -+\n     -+struct prefix_map {\n     -+\tstruct hashmap map;\n     -+\tint min_length, max_length;\n     -+};\n     -+\n      +/*\n     -+ * Find unique prefixes in a given list of strings.\n     ++ * Given an array of names, find unique prefixes (i.e. the first <n> characters\n     ++ * that uniquely identify the names) and store the lengths of the unique\n     ++ * prefixes in the 'prefix_length' field of the elements of the given array..\n      + *\n     -+ * Typically, the `struct prefix_item` information will be but a field in the\n     -+ * actual item struct; For this reason, the `list` parameter is specified as a\n     -+ * list of pointers to the items.\n     ++ * Typically, the `struct prefix_item` information is a field in the actual\n     ++ * item struct; For this reason, the `array` parameter is specified as an array\n     ++ * of pointers to the items.\n      + *\n      + * The `min_length`/`max_length` parameters define what length the unique\n      + * prefixes should have.\n     @@ -206,8 +209,8 @@\n      + * If no unique prefix could be found for a given item, its `prefix_length`\n      + * will be set to 0.\n      + */\n     -+void find_unique_prefixes(struct prefix_item **list, size_t nr,\n     -+\t\t\t  int min_length, int max_length);\n     ++void find_unique_prefixes(struct prefix_item **array, size_t nr,\n     ++\t\t\t  size_t min_length, size_t max_length);\n      +\n      +#endif\n      \n  8:  e23ddebfbf =  8:  bc7a74f697 built-in add -i: show unique prefixes of the commands\n  9:  d8c012fce8 =  9:  74f73e26b4 built-in add -i: support `?` (prompt help)\n 10:  8121a3ca1b = 10:  88001009bc built-in add -i: use color in the main loop\n 11:  db70c6475d ! 11:  b27fbe289f built-in add -i: implement the `help` command\n     @@ -18,22 +18,21 @@\n       \treturn 0;\n       }\n       \n     -+static int run_help(struct add_i_state *s, const struct pathspec *ps,\n     -+\t\t    struct file_list *files, struct list_options *opts)\n     ++static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,\n     ++\t\t    struct file_list *unused_files,\n     ++\t\t    struct list_options *unused_opts)\n      +{\n     -+\tconst char *help_color = s->help_color;\n     -+\n     -+\tcolor_fprintf_ln(stdout, help_color, \"status        - %s\",\n     ++\tcolor_fprintf_ln(stdout, s->help_color, \"status        - %s\",\n      +\t\t\t _(\"show paths with changes\"));\n     -+\tcolor_fprintf_ln(stdout, help_color, \"update        - %s\",\n     ++\tcolor_fprintf_ln(stdout, s->help_color, \"update        - %s\",\n      +\t\t\t _(\"add working tree state to the staged set of changes\"));\n     -+\tcolor_fprintf_ln(stdout, help_color, \"revert        - %s\",\n     ++\tcolor_fprintf_ln(stdout, s->help_color, \"revert        - %s\",\n      +\t\t\t _(\"revert staged set of changes back to the HEAD version\"));\n     -+\tcolor_fprintf_ln(stdout, help_color, \"patch         - %s\",\n     ++\tcolor_fprintf_ln(stdout, s->help_color, \"patch         - %s\",\n      +\t\t\t _(\"pick hunks and update selectively\"));\n     -+\tcolor_fprintf_ln(stdout, help_color, \"diff          - %s\",\n     ++\tcolor_fprintf_ln(stdout, s->help_color, \"diff          - %s\",\n      +\t\t\t _(\"view diff between HEAD and index\"));\n     -+\tcolor_fprintf_ln(stdout, help_color, \"add untracked - %s\",\n     ++\tcolor_fprintf_ln(stdout, s->help_color, \"add untracked - %s\",\n      +\t\t\t _(\"add contents of untracked files to the staged set of changes\"));\n      +\n      +\treturn 0;\n\n-- \ngitgitgadget\n"},{"id":"381349","messageId":"ad8752eca717218a2bb667273550ac07ce0760d8.1566910672.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 01/11] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-27T12:57:54Z","receivedAt":"2019-08-27T12:57:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis is hardly the first conversion of a Git command that is implemented\nas a script to a built-in. So far, the most successful strategy for such\nconversions has been to add a built-in helper and call that for more and\nmore functionality from the script, as more and more parts are\nconverted.\n\nWith the interactive add, we choose a different strategy. The sole\nreason for this is that on Windows (where such a conversion has the most\nbenefits in terms of speed and robustness) we face the very specific\nproblem that a `system()` call in Perl seems to close `stdin` in the\nparent process when the spawned process consumes even one character from\n`stdin`. And that just does not work for us here, as it would stop the\nmain loop as soon as any interactive command was performed by the\nhelper. Which is almost all of the commands in `git add -i`.\n\nIt is almost as if Perl told us once again that it does not want us to\nuse it on Windows.\n\nInstead, we follow the opposite route where we start with a bare-bones\nversion of the built-in interactive add, guarded by the new\n`add.interactive.useBuiltin` config variable, and then add more and more\nfunctionality to it, until it is feature complete.\n\nAt this point, the built-in version of `git add -i` only states that it\ncannot do anything yet ;-)\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config/add.txt |  5 +++++\n Makefile                     |  1 +\n add-interactive.c            |  7 +++++++\n add-interactive.h            |  8 ++++++++\n builtin/add.c                | 10 ++++++++++\n t/README                     |  4 ++++\n 6 files changed, 35 insertions(+)\n create mode 100644 add-interactive.c\n create mode 100644 add-interactive.h\n\ndiff --git a/Documentation/config/add.txt b/Documentation/config/add.txt\nindex 4d753f006e..c9f748f81c 100644\n--- a/Documentation/config/add.txt\n+++ b/Documentation/config/add.txt\n@@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::\n \toption of linkgit:git-add[1].  `add.ignore-errors` is deprecated,\n \tas it does not follow the usual naming convention for configuration\n \tvariables.\n+\n+add.interactive.useBuiltin::\n+\t[EXPERIMENTAL] Set to `true` to use the experimental built-in\n+\timplementation of the interactive version of linkgit:git-add[1]\n+\tinstead of the Perl script version. Is `false` by default.\ndiff --git a/Makefile b/Makefile\nindex f9255344ae..d04daf9fd8 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -826,6 +826,7 @@ LIB_H := $(sort $(shell git ls-files '*.h' ':!t/' ':!Documentation/' 2>/dev/null\n \t-name '*.h' -print))\n \n LIB_OBJS += abspath.o\n+LIB_OBJS += add-interactive.o\n LIB_OBJS += advice.o\n LIB_OBJS += alias.o\n LIB_OBJS += alloc.o\ndiff --git a/add-interactive.c b/add-interactive.c\nnew file mode 100644\nindex 0000000000..482e458dc6\n--- /dev/null\n+++ b/add-interactive.c\n@@ -0,0 +1,7 @@\n+#include \"cache.h\"\n+#include \"add-interactive.h\"\n+\n+int run_add_i(struct repository *r, const struct pathspec *ps)\n+{\n+\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n+}\ndiff --git a/add-interactive.h b/add-interactive.h\nnew file mode 100644\nindex 0000000000..7043b8741d\n--- /dev/null\n+++ b/add-interactive.h\n@@ -0,0 +1,8 @@\n+#ifndef ADD_INTERACTIVE_H\n+#define ADD_INTERACTIVE_H\n+\n+struct repository;\n+struct pathspec;\n+int run_add_i(struct repository *r, const struct pathspec *ps);\n+\n+#endif\ndiff --git a/builtin/add.c b/builtin/add.c\nindex dd18e5c9b6..4f625691b5 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -20,6 +20,7 @@\n #include \"bulk-checkin.h\"\n #include \"argv-array.h\"\n #include \"submodule.h\"\n+#include \"add-interactive.h\"\n \n static const char * const builtin_add_usage[] = {\n \tN_(\"git add [<options>] [--] <pathspec>...\"),\n@@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,\n {\n \tint status, i;\n \tstruct argv_array argv = ARGV_ARRAY_INIT;\n+\tint use_builtin_add_i =\n+\t\tgit_env_bool(\"GIT_TEST_ADD_I_USE_BUILTIN\", -1);\n+\tif (use_builtin_add_i < 0)\n+\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n+\t\t\t\t    &use_builtin_add_i);\n+\n+\tif (use_builtin_add_i == 1 && !patch_mode)\n+\t\treturn !!run_add_i(the_repository, pathspec);\n \n \targv_array_push(&argv, \"add--interactive\");\n \tif (patch_mode)\n@@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb)\n \t\tignore_add_errors = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \ndiff --git a/t/README b/t/README\nindex 60d5b77bcc..bda93fe603 100644\n--- a/t/README\n+++ b/t/README\n@@ -397,6 +397,10 @@ GIT_TEST_STASH_USE_BUILTIN=<boolean>, when false, disables the\n built-in version of git-stash. See 'stash.useBuiltin' in\n git-config(1).\n \n+GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the\n+builtin version of git add -i. See 'add.interactive.useBuiltin' in\n+git-config(1).\n+\n GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading\n of the index for the whole test suite by bypassing the default number of\n cache entries and thread minimums. Setting this to 1 will make the\n-- \ngitgitgadget\n\n"},{"id":"381351","messageId":"bc7a74f697741c723ca670b543392b3f8fc0e35b.1566910672.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 08/11] built-in add -i: show unique prefixes of the commands","fromName":"Slavica Djukic via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-27T12:57:59Z","receivedAt":"2019-08-27T12:58:03Z","isPatch":true,"sender":{"key":"slavicadj.ip2018@gmail.com","avatar":null},"body":"From: Slavica Djukic <slawica92@hotmail.com>\n\nJust like in the Perl script `git-add--interactive.perl`, for each\ncommand a unique prefix is determined (if there exists any within the\ngiven parameters), and shown in the list, and accepted as a shortcut for\nthe command.\n\nWe use the prefix map implementation that we just added in the previous\ncommit for that purpose.\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n---\n add-interactive.c | 69 ++++++++++++++++++++++++++++++++++++++---------\n 1 file changed, 56 insertions(+), 13 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex bbab69d4bc..12a4c2adb8 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -5,6 +5,7 @@\n #include \"diffcore.h\"\n #include \"revision.h\"\n #include \"refs.h\"\n+#include \"prefix-map.h\"\n \n struct add_i_state {\n \tstruct repository *r;\n@@ -46,18 +47,32 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)\n \treturn 0;\n }\n \n-struct item {\n-\tconst char *name;\n-};\n+static ssize_t find_unique(const char *string,\n+\t\t\t   struct prefix_item **list, size_t nr)\n+{\n+\tssize_t found = -1, i;\n+\n+\tfor (i = 0; i < nr; i++) {\n+\t\tstruct prefix_item *item = list[i];\n+\t\tif (!starts_with(item->name, string))\n+\t\t\tcontinue;\n+\t\tif (found >= 0)\n+\t\t\treturn -1;\n+\t\tfound = i;\n+\t}\n+\n+\treturn found;\n+}\n \n struct list_options {\n \tint columns;\n \tconst char *header;\n-\tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n+\tvoid (*print_item)(int i, struct prefix_item *item,\n+\t\t\t   void *print_item_data);\n \tvoid *print_item_data;\n };\n \n-static void list(struct item **list, size_t nr,\n+static void list(struct prefix_item **list, size_t nr,\n \t\t struct add_i_state *s, struct list_options *opts)\n {\n \tint i, last_lf = 0;\n@@ -100,13 +115,15 @@ struct list_and_choose_options {\n  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n  * `LIST_AND_CHOOSE_QUIT` is returned.\n  */\n-static ssize_t list_and_choose(struct item **items, size_t nr,\n+static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \t\t\t       struct add_i_state *s,\n \t\t\t       struct list_and_choose_options *opts)\n {\n \tstruct strbuf input = STRBUF_INIT;\n \tssize_t res = LIST_AND_CHOOSE_ERROR;\n \n+\tfind_unique_prefixes(items, nr, 1, 4);\n+\n \tfor (;;) {\n \t\tchar *p, *endp;\n \n@@ -146,6 +163,9 @@ static ssize_t list_and_choose(struct item **items, size_t nr,\n \t\t\t}\n \n \t\t\tp[sep] = '\\0';\n+\t\t\tif (index < 0)\n+\t\t\t\tindex = find_unique(p, items, nr);\n+\n \t\t\tif (index < 0 || index >= nr)\n \t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n \t\t\telse {\n@@ -171,7 +191,7 @@ struct adddel {\n \n struct file_list {\n \tstruct file_item {\n-\t\tstruct item item;\n+\t\tstruct prefix_item item;\n \t\tstruct adddel index, worktree;\n \t} **file;\n \tsize_t nr, alloc;\n@@ -338,12 +358,29 @@ static void populate_wi_changes(struct strbuf *buf,\n \t\tstrbuf_addstr(buf, no_changes);\n }\n \n+/* filters out prefixes which have special meaning to list_and_choose() */\n+static int is_valid_prefix(const char *prefix, size_t prefix_len)\n+{\n+\treturn prefix_len && prefix &&\n+\t\t/*\n+\t\t * We expect `prefix` to be NUL terminated, therefore this\n+\t\t * `strcspn()` call is okay, even if it might do much more\n+\t\t * work than strictly necessary.\n+\t\t */\n+\t\tstrcspn(prefix, \" \\t\\r\\n,\") >= prefix_len &&\t/* separators */\n+\t\t*prefix != '-' &&\t\t\t\t/* deselection */\n+\t\t!isdigit(*prefix) &&\t\t\t\t/* selection */\n+\t\t(prefix_len != 1 ||\n+\t\t (*prefix != '*' &&\t\t\t\t/* \"all\" wildcard */\n+\t\t  *prefix != '?'));\t\t\t\t/* prompt help */\n+}\n+\n struct print_file_item_data {\n \tconst char *modified_fmt;\n \tstruct strbuf buf, index, worktree;\n };\n \n-static void print_file_item(int i, struct item *item,\n+static void print_file_item(int i, struct prefix_item *item,\n \t\t\t    void *print_file_item_data)\n {\n \tstruct file_item *c = (struct file_item *)item;\n@@ -370,20 +407,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \t\treturn -1;\n \n \tif (files->nr)\n-\t\tlist((struct item **)files->file, files->nr, s, opts);\n+\t\tlist((struct prefix_item **)files->file, files->nr, s, opts);\n \tputchar('\\n');\n \n \treturn 0;\n }\n \n-static void print_command_item(int i, struct item *item,\n+static void print_command_item(int i, struct prefix_item *item,\n \t\t\t       void *print_command_item_data)\n {\n-\tprintf(\" %2d: %s\", i + 1, item->name);\n+\tif (!item->prefix_length ||\n+\t    !is_valid_prefix(item->name, item->prefix_length))\n+\t\tprintf(\" %2d: %s\", i + 1, item->name);\n+\telse\n+\t\tprintf(\" %3d: [%.*s]%s\", i + 1,\n+\t\t       (int)item->prefix_length, item->name,\n+\t\t       item->name + item->prefix_length);\n }\n \n struct command_item {\n-\tstruct item item;\n+\tstruct prefix_item item;\n \tint (*command)(struct add_i_state *s, const struct pathspec *ps,\n \t\t       struct file_list *files, struct list_options *opts);\n };\n@@ -425,7 +468,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\tres = -1;\n \n \tfor (;;) {\n-\t\ti = list_and_choose((struct item **)commands,\n+\t\ti = list_and_choose((struct prefix_item **)commands,\n \t\t\t\t    ARRAY_SIZE(commands), &s, &main_loop_opts);\n \t\tif (i == LIST_AND_CHOOSE_QUIT) {\n \t\t\tprintf(_(\"Bye.\\n\"));\n-- \ngitgitgadget\n\n"},{"id":"381352","messageId":"ee3e40293c517f8321011a513fb719a6e613cba2.1566910672.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 03/11] built-in add -i: implement the `status` command","fromName":"Daniel Ferreira via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-27T12:57:56Z","receivedAt":"2019-08-27T12:58:03Z","isPatch":true,"sender":{"key":"bnmvco@gmail.com","avatar":"https://avatars.githubusercontent.com/u/735397?v=4"},"body":"From: Daniel Ferreira <bnmvco@gmail.com>\n\nThis implements the `status` command of `git add -i`. The data\nstructures introduced in this commit will be extended as needed later.\n\nAt this point, we re-implement only part of the `list_and_choose()`\nfunction of the Perl script `git-add--interactive.perl` and call it\n`list()`. It does not yet color anything, or do columns, or allow user\ninput.\n\nOver the course of the next commits, we will introduce a\n`list_and_choose()` function that uses `list()` to display the list of\noptions and let the user choose one or more of the displayed items. This\nwill be used to implement the main loop of the built-in `git add -i`, at\nwhich point the new `status` command can actually be used.\n\nNote that we pass the list of items as a `struct item **` as opposed to\na `struct item *`, to allow for the actual items to contain much more\ninformation than merely the name.\n\nSigned-off-by: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 266 +++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 265 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 482e458dc6..d64206ba1c 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -1,7 +1,271 @@\n #include \"cache.h\"\n #include \"add-interactive.h\"\n+#include \"diffcore.h\"\n+#include \"revision.h\"\n+#include \"refs.h\"\n+\n+struct item {\n+\tconst char *name;\n+};\n+\n+struct list_options {\n+\tconst char *header;\n+\tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n+\tvoid *print_item_data;\n+};\n+\n+static void list(struct item **list, size_t nr, struct list_options *opts)\n+{\n+\tint i;\n+\n+\tif (!nr)\n+\t\treturn;\n+\n+\tif (opts->header)\n+\t\tprintf(\"%s\\n\", opts->header);\n+\n+\tfor (i = 0; i < nr; i++) {\n+\t\topts->print_item(i, list[i], opts->print_item_data);\n+\t\tputchar('\\n');\n+\t}\n+}\n+\n+struct adddel {\n+\tuintmax_t add, del;\n+\tunsigned seen:1, binary:1;\n+};\n+\n+struct file_list {\n+\tstruct file_item {\n+\t\tstruct item item;\n+\t\tstruct adddel index, worktree;\n+\t} **file;\n+\tsize_t nr, alloc;\n+};\n+\n+static void add_file_item(struct file_list *list, const char *name)\n+{\n+\tstruct file_item *item;\n+\n+\tFLEXPTR_ALLOC_STR(item, item.name, name);\n+\n+\tALLOC_GROW(list->file, list->nr + 1, list->alloc);\n+\tlist->file[list->nr++] = item;\n+}\n+\n+static void reset_file_list(struct file_list *list)\n+{\n+\tsize_t i;\n+\n+\tfor (i = 0; i < list->nr; i++)\n+\t\tfree(list->file[i]);\n+\tlist->nr = 0;\n+}\n+\n+static void release_file_list(struct file_list *list)\n+{\n+\treset_file_list(list);\n+\tFREE_AND_NULL(list->file);\n+\tlist->alloc = 0;\n+}\n+\n+static int file_item_cmp(const void *a, const void *b)\n+{\n+\tconst struct file_item * const *f1 = a;\n+\tconst struct file_item * const *f2 = b;\n+\n+\treturn strcmp((*f1)->item.name, (*f2)->item.name);\n+}\n+\n+struct pathname_entry {\n+\tstruct hashmap_entry ent;\n+\tsize_t index;\n+\tchar pathname[FLEX_ARRAY];\n+};\n+\n+static int pathname_entry_cmp(const void *unused_cmp_data,\n+\t\t\t      const void *entry, const void *entry_or_key,\n+\t\t\t      const void *pathname)\n+{\n+\tconst struct pathname_entry *e1 = entry, *e2 = entry_or_key;\n+\n+\treturn strcmp(e1->pathname,\n+\t\t      pathname ? (const char *)pathname : e2->pathname);\n+}\n+\n+struct collection_status {\n+\tenum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase;\n+\n+\tconst char *reference;\n+\n+\tstruct file_list *list;\n+\tstruct hashmap file_map;\n+};\n+\n+static void collect_changes_cb(struct diff_queue_struct *q,\n+\t\t\t       struct diff_options *options,\n+\t\t\t       void *data)\n+{\n+\tstruct collection_status *s = data;\n+\tstruct diffstat_t stat = { 0 };\n+\tint i;\n+\n+\tif (!q->nr)\n+\t\treturn;\n+\n+\tcompute_diffstat(options, &stat, q);\n+\n+\tfor (i = 0; i < stat.nr; i++) {\n+\t\tconst char *name = stat.files[i]->name;\n+\t\tint hash = strhash(name);\n+\t\tstruct pathname_entry *entry;\n+\t\tsize_t file_index;\n+\t\tstruct file_item *file;\n+\t\tstruct adddel *adddel;\n+\n+\t\tentry = hashmap_get_from_hash(&s->file_map, hash, name);\n+\t\tif (entry)\n+\t\t\tfile_index = entry->index;\n+\t\telse {\n+\t\t\tFLEX_ALLOC_STR(entry, pathname, name);\n+\t\t\thashmap_entry_init(&entry->ent, hash);\n+\t\t\tentry->index = file_index = s->list->nr;\n+\t\t\thashmap_add(&s->file_map, &entry->ent);\n+\n+\t\t\tadd_file_item(s->list, name);\n+\t\t}\n+\t\tfile = s->list->file[file_index];\n+\n+\t\tadddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;\n+\t\tadddel->seen = 1;\n+\t\tadddel->add = stat.files[i]->added;\n+\t\tadddel->del = stat.files[i]->deleted;\n+\t\tif (stat.files[i]->is_binary)\n+\t\t\tadddel->binary = 1;\n+\t}\n+\tfree_diffstat_info(&stat);\n+}\n+\n+static int get_modified_files(struct repository *r, struct file_list *list,\n+\t\t\t      const struct pathspec *ps)\n+{\n+\tstruct object_id head_oid;\n+\tint is_initial = !resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING,\n+\t\t\t\t\t     &head_oid, NULL);\n+\tstruct collection_status s = { FROM_WORKTREE };\n+\n+\tif (repo_read_index_preload(r, ps, 0) < 0)\n+\t\treturn error(_(\"could not read index\"));\n+\n+\ts.list = list;\n+\thashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);\n+\n+\tfor (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {\n+\t\tstruct rev_info rev;\n+\t\tstruct setup_revision_opt opt = { 0 };\n+\n+\t\topt.def = is_initial ?\n+\t\t\tempty_tree_oid_hex() : oid_to_hex(&head_oid);\n+\n+\t\tinit_revisions(&rev, NULL);\n+\t\tsetup_revisions(0, NULL, &rev, &opt);\n+\n+\t\trev.diffopt.output_format = DIFF_FORMAT_CALLBACK;\n+\t\trev.diffopt.format_callback = collect_changes_cb;\n+\t\trev.diffopt.format_callback_data = &s;\n+\n+\t\tif (ps)\n+\t\t\tcopy_pathspec(&rev.prune_data, ps);\n+\n+\t\tif (s.phase == FROM_INDEX)\n+\t\t\trun_diff_index(&rev, 1);\n+\t\telse {\n+\t\t\trev.diffopt.flags.ignore_dirty_submodules = 1;\n+\t\t\trun_diff_files(&rev, 0);\n+\t\t}\n+\t}\n+\thashmap_free(&s.file_map, 1);\n+\n+\t/* While the diffs are ordered already, we ran *two* diffs... */\n+\tQSORT(list->file, list->nr, file_item_cmp);\n+\n+\treturn 0;\n+}\n+\n+static void populate_wi_changes(struct strbuf *buf,\n+\t\t\t\tstruct adddel *ad, const char *no_changes)\n+{\n+\tif (ad->binary)\n+\t\tstrbuf_addstr(buf, _(\"binary\"));\n+\telse if (ad->seen)\n+\t\tstrbuf_addf(buf, \"+%\"PRIuMAX\"/-%\"PRIuMAX,\n+\t\t\t    (uintmax_t)ad->add, (uintmax_t)ad->del);\n+\telse\n+\t\tstrbuf_addstr(buf, no_changes);\n+}\n+\n+struct print_file_item_data {\n+\tconst char *modified_fmt;\n+\tstruct strbuf buf, index, worktree;\n+};\n+\n+static void print_file_item(int i, struct item *item,\n+\t\t\t    void *print_file_item_data)\n+{\n+\tstruct file_item *c = (struct file_item *)item;\n+\tstruct print_file_item_data *d = print_file_item_data;\n+\n+\tstrbuf_reset(&d->index);\n+\tstrbuf_reset(&d->worktree);\n+\tstrbuf_reset(&d->buf);\n+\n+\tpopulate_wi_changes(&d->worktree, &c->worktree, _(\"nothing\"));\n+\tpopulate_wi_changes(&d->index, &c->index, _(\"unchanged\"));\n+\tstrbuf_addf(&d->buf, d->modified_fmt,\n+\t\t    d->index.buf, d->worktree.buf, item->name);\n+\n+\tprintf(\" %2d: %s\", i + 1, d->buf.buf);\n+}\n+\n+static int run_status(struct repository *r, const struct pathspec *ps,\n+\t\t      struct file_list *files, struct list_options *opts)\n+{\n+\treset_file_list(files);\n+\n+\tif (get_modified_files(r, files, ps) < 0)\n+\t\treturn -1;\n+\n+\tif (files->nr)\n+\t\tlist((struct item **)files->file, files->nr, opts);\n+\tputchar('\\n');\n+\n+\treturn 0;\n+}\n \n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n-\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n+\tstruct print_file_item_data print_file_item_data = {\n+\t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n+\t};\n+\tstruct list_options opts = {\n+\t\tNULL, print_file_item, &print_file_item_data\n+\t};\n+\tstruct strbuf header = STRBUF_INIT;\n+\tstruct file_list files = { NULL };\n+\tint res = 0;\n+\n+\tstrbuf_addstr(&header, \"      \");\n+\tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n+\t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n+\topts.header = header.buf;\n+\n+\tres = run_status(r, ps, &files, &opts);\n+\n+\trelease_file_list(&files);\n+\tstrbuf_release(&print_file_item_data.buf);\n+\tstrbuf_release(&print_file_item_data.index);\n+\tstrbuf_release(&print_file_item_data.worktree);\n+\tstrbuf_release(&header);\n+\n+\treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"381350","messageId":"ac67731cf11810e24de13ec3312ec71d2115b5f7.1566910672.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 06/11] built-in add -i: implement the main loop","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-27T12:57:58Z","receivedAt":"2019-08-27T12:58:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe reason why we did not start with the main loop to begin with is that\nit is the first user of `list_and_choose()`, which uses the `list()`\nfunction that we conveniently introduced for use by the `status`\ncommand.\n\nApart from the \"and choose\" part, there are more differences between the\nway the `status` command calls the `list_and_choose()` function in the\nPerl version of `git add -i` compared to the other callers of said\nfunction. The most important ones:\n\n- The list is not only shown, but the user is also asked to make a\n  choice, possibly selecting multiple entries.\n\n- The list of items is prefixed with a marker indicating what items have\n  been selected, if multi-selection is allowed.\n\n- Initially, for each item a unique prefix (if there exists any within\n  the given parameters) is determined, and shown in the list, and\n  accepted as a shortcut for the selection.\n\nThese features will be implemented later, except the part where the user\ncan choose a command. At this stage, though, the built-in `git add -i`\nstill only supports the `status` command, with the remaining commands to\nfollow over the course of the next commits.\n\nIn addition, we also modify `list()` to support displaying the commands\nin columns, even if there is currently only one.\n\nThe Perl script `git-add--interactive.perl` mixed the purposes of the\n\"list\" and the \"and choose\" part into the same function. In the C\nversion, we will keep them separate instead, calling the `list()`\nfunction from the `list_and_choose()` function.\n\nNote that we only have a prompt ending in a single \">\" at this stage;\nlater commits will add commands that display a double \">>\" to indicate\nthat the user is in a different loop than the main one.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 129 +++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 127 insertions(+), 2 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex f5577a80d3..bbab69d4bc 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -51,6 +51,7 @@ struct item {\n };\n \n struct list_options {\n+\tint columns;\n \tconst char *header;\n \tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n \tvoid *print_item_data;\n@@ -59,7 +60,7 @@ struct list_options {\n static void list(struct item **list, size_t nr,\n \t\t struct add_i_state *s, struct list_options *opts)\n {\n-\tint i;\n+\tint i, last_lf = 0;\n \n \tif (!nr)\n \t\treturn;\n@@ -70,8 +71,97 @@ static void list(struct item **list, size_t nr,\n \n \tfor (i = 0; i < nr; i++) {\n \t\topts->print_item(i, list[i], opts->print_item_data);\n+\n+\t\tif ((opts->columns) && ((i + 1) % (opts->columns))) {\n+\t\t\tputchar('\\t');\n+\t\t\tlast_lf = 0;\n+\t\t}\n+\t\telse {\n+\t\t\tputchar('\\n');\n+\t\t\tlast_lf = 1;\n+\t\t}\n+\t}\n+\n+\tif (!last_lf)\n \t\tputchar('\\n');\n+}\n+struct list_and_choose_options {\n+\tstruct list_options list_opts;\n+\n+\tconst char *prompt;\n+};\n+\n+#define LIST_AND_CHOOSE_ERROR (-1)\n+#define LIST_AND_CHOOSE_QUIT  (-2)\n+\n+/*\n+ * Returns the selected index.\n+ *\n+ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n+ * `LIST_AND_CHOOSE_QUIT` is returned.\n+ */\n+static ssize_t list_and_choose(struct item **items, size_t nr,\n+\t\t\t       struct add_i_state *s,\n+\t\t\t       struct list_and_choose_options *opts)\n+{\n+\tstruct strbuf input = STRBUF_INIT;\n+\tssize_t res = LIST_AND_CHOOSE_ERROR;\n+\n+\tfor (;;) {\n+\t\tchar *p, *endp;\n+\n+\t\tstrbuf_reset(&input);\n+\n+\t\tlist(items, nr, s, &opts->list_opts);\n+\n+\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n+\t\tfflush(stdout);\n+\n+\t\tif (strbuf_getline(&input, stdin) == EOF) {\n+\t\t\tputchar('\\n');\n+\t\t\tres = LIST_AND_CHOOSE_QUIT;\n+\t\t\tbreak;\n+\t\t}\n+\t\tstrbuf_trim(&input);\n+\n+\t\tif (!input.len)\n+\t\t\tbreak;\n+\n+\t\tp = input.buf;\n+\t\tfor (;;) {\n+\t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n+\t\t\tssize_t index = -1;\n+\n+\t\t\tif (!sep) {\n+\t\t\t\tif (!*p)\n+\t\t\t\t\tbreak;\n+\t\t\t\tp++;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (isdigit(*p)) {\n+\t\t\t\tindex = strtoul(p, &endp, 10) - 1;\n+\t\t\t\tif (endp != p + sep)\n+\t\t\t\t\tindex = -1;\n+\t\t\t}\n+\n+\t\t\tp[sep] = '\\0';\n+\t\t\tif (index < 0 || index >= nr)\n+\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n+\t\t\telse {\n+\t\t\t\tres = index;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tp += sep + 1;\n+\t\t}\n+\n+\t\tif (res != LIST_AND_CHOOSE_ERROR)\n+\t\t\tbreak;\n \t}\n+\n+\tstrbuf_release(&input);\n+\treturn res;\n }\n \n struct adddel {\n@@ -286,17 +376,40 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \treturn 0;\n }\n \n+static void print_command_item(int i, struct item *item,\n+\t\t\t       void *print_command_item_data)\n+{\n+\tprintf(\" %2d: %s\", i + 1, item->name);\n+}\n+\n+struct command_item {\n+\tstruct item item;\n+\tint (*command)(struct add_i_state *s, const struct pathspec *ps,\n+\t\t       struct file_list *files, struct list_options *opts);\n+};\n+\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n+\tstruct list_and_choose_options main_loop_opts = {\n+\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n+\t\tN_(\"What now\")\n+\t};\n+\tstruct command_item\n+\t\tstatus = { { \"status\" }, run_status };\n+\tstruct command_item *commands[] = {\n+\t\t&status\n+\t};\n+\n \tstruct print_file_item_data print_file_item_data = {\n \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n \t};\n \tstruct list_options opts = {\n-\t\tNULL, print_file_item, &print_file_item_data\n+\t\t0, NULL, print_file_item, &print_file_item_data\n \t};\n \tstruct strbuf header = STRBUF_INIT;\n \tstruct file_list files = { NULL };\n+\tssize_t i;\n \tint res = 0;\n \n \tif (init_add_i_state(r, &s))\n@@ -311,6 +424,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tif (run_status(&s, ps, &files, &opts) < 0)\n \t\tres = -1;\n \n+\tfor (;;) {\n+\t\ti = list_and_choose((struct item **)commands,\n+\t\t\t\t    ARRAY_SIZE(commands), &s, &main_loop_opts);\n+\t\tif (i == LIST_AND_CHOOSE_QUIT) {\n+\t\t\tprintf(_(\"Bye.\\n\"));\n+\t\t\tres = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (i != LIST_AND_CHOOSE_ERROR)\n+\t\t\tres = commands[i]->command(&s, ps, &files, &opts);\n+\t}\n+\n \trelease_file_list(&files);\n \tstrbuf_release(&print_file_item_data.buf);\n \tstrbuf_release(&print_file_item_data.index);\n-- \ngitgitgadget\n\n"},{"id":"381354","messageId":"3c855d9fa563a288d7934e3cca29295fc929257a.1566910672.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 04/11] built-in add -i: refresh the index before running `status`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-27T12:57:56Z","receivedAt":"2019-08-27T12:58:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis is what the Perl version does, and therefore it is what the\nbuilt-in version should do, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c |  4 +++-\n repository.c      | 19 +++++++++++++++++++\n repository.h      |  7 +++++++\n 3 files changed, 29 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex d64206ba1c..427abe505e 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -259,7 +259,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n \topts.header = header.buf;\n \n-\tres = run_status(r, ps, &files, &opts);\n+\trepo_refresh_and_write_index(r, REFRESH_QUIET, 1);\n+\tif (run_status(r, ps, &files, &opts) < 0)\n+\t\tres = -1;\n \n \trelease_file_list(&files);\n \tstrbuf_release(&print_file_item_data.buf);\ndiff --git a/repository.c b/repository.c\nindex 682c239fe3..def35c40fc 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -275,3 +275,22 @@ int repo_hold_locked_index(struct repository *repo,\n \t\tBUG(\"the repo hasn't been setup\");\n \treturn hold_lock_file_for_update(lf, repo->index_file, flags);\n }\n+\n+int repo_refresh_and_write_index(struct repository *r,\n+\t\t\t\t unsigned int flags, int gentle)\n+{\n+\tstruct lock_file lock_file = LOCK_INIT;\n+\tint fd;\n+\n+\tif (repo_read_index_preload(r, NULL, 0) < 0)\n+\t\treturn error(_(\"could not read index\"));\n+\tfd = repo_hold_locked_index(r, &lock_file, 0);\n+\tif (!gentle && fd < 0)\n+\t\treturn error(_(\"could not lock index for writing\"));\n+\trefresh_index(r->index, flags, NULL, NULL, NULL);\n+\tif (0 <= fd)\n+\t\trepo_update_index_if_able(r, &lock_file);\n+\trollback_lock_file(&lock_file);\n+\n+\treturn 0;\n+}\ndiff --git a/repository.h b/repository.h\nindex 4fb6a5885f..cf5d5bab48 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -157,5 +157,12 @@ int repo_read_index_unmerged(struct repository *);\n  */\n void repo_update_index_if_able(struct repository *, struct lock_file *);\n \n+/*\n+ * Refresh the index and write it out. If the index file could not be\n+ * locked, error out, except in gentle mode. The flags will be passed\n+ * through to refresh_index().\n+ */\n+int repo_refresh_and_write_index(struct repository *r,\n+\t\t\t\t unsigned int flags, int gentle);\n \n #endif /* REPOSITORY_H */\n-- \ngitgitgadget\n\n"},{"id":"381353","messageId":"74f73e26b4de4bb7873079811d1251a938c58522.1566910672.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 09/11] built-in add -i: support `?` (prompt help)","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-27T12:58:00Z","receivedAt":"2019-08-27T12:58:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWith this change, we print out the same colored help text that the\nPerl-based `git add -i` prints in the main loop when question mark is\nentered.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 22 +++++++++++++++++++++-\n 1 file changed, 21 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 12a4c2adb8..710317e599 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -11,6 +11,7 @@ struct add_i_state {\n \tstruct repository *r;\n \tint use_color;\n \tchar header_color[COLOR_MAXLEN];\n+\tchar help_color[COLOR_MAXLEN];\n };\n \n static void init_color(struct repository *r, struct add_i_state *s,\n@@ -43,6 +44,7 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)\n \ts->use_color = want_color(s->use_color);\n \n \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n+\tinit_color(r, s, \"help\", s->help_color, GIT_COLOR_BOLD_RED);\n \n \treturn 0;\n }\n@@ -104,6 +106,7 @@ struct list_and_choose_options {\n \tstruct list_options list_opts;\n \n \tconst char *prompt;\n+\tvoid (*print_help)(struct add_i_state *s);\n };\n \n #define LIST_AND_CHOOSE_ERROR (-1)\n@@ -144,6 +147,11 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \t\tif (!input.len)\n \t\t\tbreak;\n \n+\t\tif (!strcmp(input.buf, \"?\")) {\n+\t\t\topts->print_help(s);\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tp = input.buf;\n \t\tfor (;;) {\n \t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n@@ -431,12 +439,24 @@ struct command_item {\n \t\t       struct file_list *files, struct list_options *opts);\n };\n \n+static void command_prompt_help(struct add_i_state *s)\n+{\n+\tconst char *help_color = s->help_color;\n+\tcolor_fprintf_ln(stdout, help_color, \"%s\", _(\"Prompt help:\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"1          - %s\",\n+\t\t\t _(\"select a numbered item\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"foo        - %s\",\n+\t\t\t _(\"select item based on unique prefix\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"           - %s\",\n+\t\t\t _(\"(empty) select nothing\"));\n+}\n+\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n \tstruct list_and_choose_options main_loop_opts = {\n \t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n-\t\tN_(\"What now\")\n+\t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct command_item\n \t\tstatus = { { \"status\" }, run_status };\n-- \ngitgitgadget\n\n"},{"id":"381355","messageId":"b27fbe289f18201466ce996e29b2788e22b2b17a.1566910672.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 11/11] built-in add -i: implement the `help` command","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-27T12:58:01Z","receivedAt":"2019-08-27T12:58:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis imitates the code to show the help text from the Perl script\n`git-add--interactive.perl` in the built-in version.\n\nTo make sure that it renders exactly like the Perl version of `git add\n-i`, we also add a test case for that to `t3701-add-interactive.sh`.\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c          | 26 ++++++++++++++++++++++++--\n t/t3701-add-interactive.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 49 insertions(+), 2 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex a343195a67..765455a3fc 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -429,6 +429,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \treturn 0;\n }\n \n+static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,\n+\t\t    struct file_list *unused_files,\n+\t\t    struct list_options *unused_opts)\n+{\n+\tcolor_fprintf_ln(stdout, s->help_color, \"status        - %s\",\n+\t\t\t _(\"show paths with changes\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"update        - %s\",\n+\t\t\t _(\"add working tree state to the staged set of changes\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"revert        - %s\",\n+\t\t\t _(\"revert staged set of changes back to the HEAD version\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"patch         - %s\",\n+\t\t\t _(\"pick hunks and update selectively\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"diff          - %s\",\n+\t\t\t _(\"view diff between HEAD and index\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"add untracked - %s\",\n+\t\t\t _(\"add contents of untracked files to the staged set of changes\"));\n+\n+\treturn 0;\n+}\n+\n struct print_command_item_data {\n \tconst char *color, *reset;\n };\n@@ -474,9 +494,11 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct command_item\n-\t\tstatus = { { \"status\" }, run_status };\n+\t\tstatus = { { \"status\" }, run_status },\n+\t\thelp = { { \"help\" }, run_help };\n \tstruct command_item *commands[] = {\n-\t\t&status\n+\t\t&status,\n+\t\t&help\n \t};\n \n \tstruct print_file_item_data print_file_item_data = {\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex 69991a3168..cf67756b85 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' '\n \ttest_write_lines a b a b a a b a b a >expect &&\n \ttest_cmp expect a\n '\n+\n+test_expect_success 'show help from add--helper' '\n+\tgit reset --hard &&\n+\tcat >expect <<-EOF &&\n+\n+\t<BOLD>*** Commands ***<RESET>\n+\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>\n+\t<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>\n+\t<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>\n+\t<BOLD;RED>patch         - pick hunks and update selectively<RESET>\n+\t<BOLD;RED>diff          - view diff between HEAD and index<RESET>\n+\t<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>\n+\t<BOLD>*** Commands ***<RESET>\n+\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t<BOLD;BLUE>What now<RESET>>$SP\n+\tBye.\n+\tEOF\n+\ttest_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&\n+\ttest_decode_color <actual.colored >actual &&\n+\ttest_i18ncmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"381356","messageId":"88001009bc00b10856141cb689d31400a68b7df3.1566910672.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 10/11] built-in add -i: use color in the main loop","fromName":"Slavica Djukic via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-27T12:58:01Z","receivedAt":"2019-08-27T12:58:08Z","isPatch":true,"sender":{"key":"slavicadj.ip2018@gmail.com","avatar":null},"body":"From: Slavica Djukic <slawica92@hotmail.com>\n\nThe error messages as well as the unique prefixes are colored in `git\nadd -i` by default; We need to do the same in the built-in version.\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 37 ++++++++++++++++++++++++++++++++-----\n 1 file changed, 32 insertions(+), 5 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 710317e599..a343195a67 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -12,6 +12,9 @@ struct add_i_state {\n \tint use_color;\n \tchar header_color[COLOR_MAXLEN];\n \tchar help_color[COLOR_MAXLEN];\n+\tchar prompt_color[COLOR_MAXLEN];\n+\tchar error_color[COLOR_MAXLEN];\n+\tchar reset_color[COLOR_MAXLEN];\n };\n \n static void init_color(struct repository *r, struct add_i_state *s,\n@@ -45,6 +48,9 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)\n \n \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n \tinit_color(r, s, \"help\", s->help_color, GIT_COLOR_BOLD_RED);\n+\tinit_color(r, s, \"prompt\", s->prompt_color, GIT_COLOR_BOLD_BLUE);\n+\tinit_color(r, s, \"error\", s->error_color, GIT_COLOR_BOLD_RED);\n+\tinit_color(r, s, \"reset\", s->reset_color, GIT_COLOR_RESET);\n \n \treturn 0;\n }\n@@ -134,7 +140,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \n \t\tlist(items, nr, s, &opts->list_opts);\n \n-\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n+\t\tcolor_fprintf(stdout, s->prompt_color, \"%s\", opts->prompt);\n+\t\tfputs(\"> \", stdout);\n \t\tfflush(stdout);\n \n \t\tif (strbuf_getline(&input, stdin) == EOF) {\n@@ -175,7 +182,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n \t\t\t\tindex = find_unique(p, items, nr);\n \n \t\t\tif (index < 0 || index >= nr)\n-\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n+\t\t\t\tcolor_fprintf_ln(stdout, s->error_color,\n+\t\t\t\t\t\t _(\"Huh (%s)?\"), p);\n \t\t\telse {\n \t\t\t\tres = index;\n \t\t\t\tbreak;\n@@ -421,15 +429,21 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \treturn 0;\n }\n \n+struct print_command_item_data {\n+\tconst char *color, *reset;\n+};\n+\n static void print_command_item(int i, struct prefix_item *item,\n \t\t\t       void *print_command_item_data)\n {\n+\tstruct print_command_item_data *d = print_command_item_data;\n+\n \tif (!item->prefix_length ||\n \t    !is_valid_prefix(item->name, item->prefix_length))\n \t\tprintf(\" %2d: %s\", i + 1, item->name);\n \telse\n-\t\tprintf(\" %3d: [%.*s]%s\", i + 1,\n-\t\t       (int)item->prefix_length, item->name,\n+\t\tprintf(\" %2d: %s%.*s%s%s\", i + 1,\n+\t\t       d->color, (int)item->prefix_length, item->name, d->reset,\n \t\t       item->name + item->prefix_length);\n }\n \n@@ -454,8 +468,9 @@ static void command_prompt_help(struct add_i_state *s)\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n+\tstruct print_command_item_data data;\n \tstruct list_and_choose_options main_loop_opts = {\n-\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n+\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, &data },\n \t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct command_item\n@@ -478,6 +493,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tif (init_add_i_state(r, &s))\n \t\treturn error(\"could not parse `add -i` config\");\n \n+\t/*\n+\t * When color was asked for, use the prompt color for\n+\t * highlighting, otherwise use square brackets.\n+\t */\n+\tif (s.use_color) {\n+\t\tdata.color = s.prompt_color;\n+\t\tdata.reset = s.reset_color;\n+\t} else {\n+\t\tdata.color = \"[\";\n+\t\tdata.reset = \"]\";\n+\t}\n+\n \tstrbuf_addstr(&header, \"      \");\n \tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n-- \ngitgitgadget\n\n"},{"id":"381357","messageId":"38cc04c1d9816bc3bad960df0d5edea3020a9173.1566910672.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 02/11] diff: export diffstat interface","fromName":"Daniel Ferreira via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-27T12:57:55Z","receivedAt":"2019-08-27T12:58:10Z","isPatch":true,"sender":{"key":"bnmvco@gmail.com","avatar":"https://avatars.githubusercontent.com/u/735397?v=4"},"body":"From: Daniel Ferreira <bnmvco@gmail.com>\n\nMake the diffstat interface (namely, the diffstat_t struct and\ncompute_diffstat) no longer be internal to diff.c and allow it to be used\nby other parts of git.\n\nThis is helpful for code that may want to easily extract information\nfrom files using the diff machinery, while flushing it differently from\nhow the show_* functions used by diff_flush() do it. One example is the\nbuiltin implementation of git-add--interactive's status.\n\nSigned-off-by: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 39 ++++++++++++++++-----------------------\n diff.h | 20 ++++++++++++++++++++\n 2 files changed, 36 insertions(+), 23 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex efe42b341a..6829a74065 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -2492,22 +2492,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)\n \t}\n }\n \n-struct diffstat_t {\n-\tint nr;\n-\tint alloc;\n-\tstruct diffstat_file {\n-\t\tchar *from_name;\n-\t\tchar *name;\n-\t\tchar *print_name;\n-\t\tconst char *comments;\n-\t\tunsigned is_unmerged:1;\n-\t\tunsigned is_binary:1;\n-\t\tunsigned is_renamed:1;\n-\t\tunsigned is_interesting:1;\n-\t\tuintmax_t added, deleted;\n-\t} **files;\n-};\n-\n static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,\n \t\t\t\t\t  const char *name_a,\n \t\t\t\t\t  const char *name_b)\n@@ -3154,7 +3138,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o\n \tgather_dirstat(options, &dir, changed, \"\", 0);\n }\n \n-static void free_diffstat_info(struct diffstat_t *diffstat)\n+void free_diffstat_info(struct diffstat_t *diffstat)\n {\n \tint i;\n \tfor (i = 0; i < diffstat->nr; i++) {\n@@ -6278,12 +6262,7 @@ void diff_flush(struct diff_options *options)\n \t    dirstat_by_line) {\n \t\tstruct diffstat_t diffstat;\n \n-\t\tmemset(&diffstat, 0, sizeof(struct diffstat_t));\n-\t\tfor (i = 0; i < q->nr; i++) {\n-\t\t\tstruct diff_filepair *p = q->queue[i];\n-\t\t\tif (check_pair_status(p))\n-\t\t\t\tdiff_flush_stat(p, options, &diffstat);\n-\t\t}\n+\t\tcompute_diffstat(options, &diffstat, q);\n \t\tif (output_format & DIFF_FORMAT_NUMSTAT)\n \t\t\tshow_numstat(&diffstat, options);\n \t\tif (output_format & DIFF_FORMAT_DIFFSTAT)\n@@ -6616,6 +6595,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)\n \treturn ignored;\n }\n \n+void compute_diffstat(struct diff_options *options,\n+\t\t      struct diffstat_t *diffstat,\n+\t\t      struct diff_queue_struct *q)\n+{\n+\tint i;\n+\n+\tmemset(diffstat, 0, sizeof(struct diffstat_t));\n+\tfor (i = 0; i < q->nr; i++) {\n+\t\tstruct diff_filepair *p = q->queue[i];\n+\t\tif (check_pair_status(p))\n+\t\t\tdiff_flush_stat(p, options, diffstat);\n+\t}\n+}\n+\n void diff_addremove(struct diff_options *options,\n \t\t    int addremove, unsigned mode,\n \t\t    const struct object_id *oid,\ndiff --git a/diff.h b/diff.h\nindex c2c3056810..c791ecc972 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -245,6 +245,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);\n void diff_emit_submodule_pipethrough(struct diff_options *o,\n \t\t\t\t     const char *line, int len);\n \n+struct diffstat_t {\n+\tint nr;\n+\tint alloc;\n+\tstruct diffstat_file {\n+\t\tchar *from_name;\n+\t\tchar *name;\n+\t\tchar *print_name;\n+\t\tconst char *comments;\n+\t\tunsigned is_unmerged:1;\n+\t\tunsigned is_binary:1;\n+\t\tunsigned is_renamed:1;\n+\t\tunsigned is_interesting:1;\n+\t\tuintmax_t added, deleted;\n+\t} **files;\n+};\n+\n enum color_diff {\n \tDIFF_RESET = 0,\n \tDIFF_CONTEXT = 1,\n@@ -334,6 +350,10 @@ void diff_change(struct diff_options *,\n \n struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);\n \n+void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,\n+\t\t      struct diff_queue_struct *q);\n+void free_diffstat_info(struct diffstat_t *diffstat);\n+\n #define DIFF_SETUP_REVERSE      \t1\n #define DIFF_SETUP_USE_SIZE_CACHE\t4\n \n-- \ngitgitgadget\n\n"},{"id":"381358","messageId":"c5a699b6b27e04cf341a20258702bb6d3df917e5.1566910672.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 07/11] Add a function to determine unique prefixes for a list of strings","fromName":"Slavica Djukic via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-27T12:57:58Z","receivedAt":"2019-08-27T12:58:11Z","isPatch":true,"sender":{"key":"slavicadj.ip2018@gmail.com","avatar":null},"body":"From: Slavica Djukic <slawica92@hotmail.com>\n\nIn the `git add -i` command, we show unique prefixes of the commands and\nfiles, to give an indication what prefix would select them.\n\nNaturally, the C implementation looks a lot different than the Perl\nimplementation: in Perl, a trie is much easier implemented, while we\nalready have a pretty neat hashmap implementation in C that we use for\nthe purpose of storing (not necessarily unique) prefixes.\n\nThe idea: for each item that we add, we generate prefixes starting with\nthe first letter, then the first two letters, then three, etc, until we\nfind a prefix that is unique (or until the prefix length would be\nlonger than we want). If we encounter a previously-unique prefix on the\nway, we adjust that item's prefix to make it unique again (or we mark it\nas having no unique prefix if we failed to find one). These partial\nprefixes are stored in a hash map (for quick lookup times).\n\nTo make sure that this function works as expected, we add a test using a\nspecial-purpose test helper.\n\nNote: We expect the list of prefix items to be passed in as a list of\npointers rather than as regular list to avoid having to copy information\n(the actual items will most likely contain more information than just\nthe name and the length of the unique prefix, but passing in `struct\nprefix_item *` would not allow for that).\n\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Makefile                   |   2 +\n prefix-map.c               | 123 +++++++++++++++++++++++++++++++++++++\n prefix-map.h               |  29 +++++++++\n t/helper/test-prefix-map.c |  58 +++++++++++++++++\n t/helper/test-tool.c       |   1 +\n t/helper/test-tool.h       |   1 +\n t/t0018-prefix-map.sh      |  10 +++\n 7 files changed, 224 insertions(+)\n create mode 100644 prefix-map.c\n create mode 100644 prefix-map.h\n create mode 100644 t/helper/test-prefix-map.c\n create mode 100755 t/t0018-prefix-map.sh\n\ndiff --git a/Makefile b/Makefile\nindex d04daf9fd8..5c141c4609 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -727,6 +727,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o\n TEST_BUILTINS_OBJS += test-parse-options.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n+TEST_BUILTINS_OBJS += test-prefix-map.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\n@@ -945,6 +946,7 @@ LIB_OBJS += patch-ids.o\n LIB_OBJS += path.o\n LIB_OBJS += pathspec.o\n LIB_OBJS += pkt-line.o\n+LIB_OBJS += prefix-map.o\n LIB_OBJS += preload-index.o\n LIB_OBJS += pretty.o\n LIB_OBJS += prio-queue.o\ndiff --git a/prefix-map.c b/prefix-map.c\nnew file mode 100644\nindex 0000000000..ff4284bbe3\n--- /dev/null\n+++ b/prefix-map.c\n@@ -0,0 +1,123 @@\n+#include \"cache.h\"\n+#include \"prefix-map.h\"\n+\n+struct prefix_map_entry {\n+\tstruct hashmap_entry e;\n+\tconst char *name;\n+\tsize_t prefix_length;\n+\t/* if item is NULL, the prefix is not unique */\n+\tstruct prefix_item *item;\n+};\n+\n+struct prefix_map {\n+\tstruct hashmap map;\n+\tsize_t min_length, max_length;\n+};\n+\n+static int map_cmp(const void *unused_cmp_data,\n+\t\t   const void *entry,\n+\t\t   const void *entry_or_key,\n+\t\t   const void *unused_keydata)\n+{\n+\tconst struct prefix_map_entry *a = entry;\n+\tconst struct prefix_map_entry *b = entry_or_key;\n+\n+\treturn a->prefix_length != b->prefix_length ||\n+\t\tstrncmp(a->name, b->name, a->prefix_length);\n+}\n+\n+static void add_prefix_entry(struct hashmap *map, const char *name,\n+\t\t\t     size_t prefix_length, struct prefix_item *item)\n+{\n+\tstruct prefix_map_entry *result = xmalloc(sizeof(*result));\n+\tresult->name = name;\n+\tresult->prefix_length = prefix_length;\n+\tresult->item = item;\n+\thashmap_entry_init(&result->e, memhash(name, prefix_length));\n+\thashmap_add(map, &result->e);\n+}\n+\n+static void init_prefix_map(struct prefix_map *prefix_map,\n+\t\t\t    size_t min_prefix_length, size_t max_prefix_length)\n+{\n+\thashmap_init(&prefix_map->map, map_cmp, NULL, 0);\n+\tprefix_map->min_length = min_prefix_length;\n+\tprefix_map->max_length = max_prefix_length;\n+}\n+\n+static void add_prefix_item(struct prefix_map *prefix_map,\n+\t\t\t    struct prefix_item *item)\n+{\n+\tstruct prefix_map_entry e = { { NULL } }, *e2;\n+\tsize_t j;\n+\n+\te.item = item;\n+\te.name = item->name;\n+\n+\tfor (j = prefix_map->min_length;\n+\t     j <= prefix_map->max_length && e.name[j]; j++) {\n+\t\t/* Avoid breaking UTF-8 multi-byte sequences */\n+\t\tif (!isascii(e.name[j]))\n+\t\t\tbreak;\n+\n+\t\te.prefix_length = j;\n+\t\thashmap_entry_init(&e.e, memhash(e.name, j));\n+\t\te2 = hashmap_get(&prefix_map->map, &e.e, NULL);\n+\t\tif (!e2) {\n+\t\t\t/* prefix is unique at this stage */\n+\t\t\titem->prefix_length = j;\n+\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j, item);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (!e2->item)\n+\t\t\tcontinue; /* non-unique prefix */\n+\n+\t\tif (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))\n+\t\t\tBUG(\"unexpected prefix length: %d != %d (%s != %s)\",\n+\t\t\t    (int)j, (int)e2->item->prefix_length,\n+\t\t\t    e.name, e2->name);\n+\n+\t\t/* skip common prefix */\n+\t\tfor (; j < prefix_map->max_length && e.name[j]; j++) {\n+\t\t\tif (e.item->name[j] != e2->item->name[j])\n+\t\t\t\tbreak;\n+\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n+\t\t\t\t\t NULL);\n+\t\t}\n+\n+\t\t/* e2 no longer refers to a unique prefix */\n+\t\tif (j < prefix_map->max_length && e2->name[j]) {\n+\t\t\t/* found a new unique prefix for e2's item */\n+\t\t\te2->item->prefix_length = j + 1;\n+\t\t\tadd_prefix_entry(&prefix_map->map, e2->name, j + 1,\n+\t\t\t\t\t e2->item);\n+\t\t}\n+\t\telse\n+\t\t\te2->item->prefix_length = 0;\n+\t\te2->item = NULL;\n+\n+\t\tif (j < prefix_map->max_length && e.name[j]) {\n+\t\t\t/* found a unique prefix for the item */\n+\t\t\te.item->prefix_length = j + 1;\n+\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n+\t\t\t\t\t e.item);\n+\t\t} else\n+\t\t\t/* item has no (short enough) unique prefix */\n+\t\t\te.item->prefix_length = 0;\n+\n+\t\tbreak;\n+\t}\n+}\n+\n+void find_unique_prefixes(struct prefix_item **array, size_t nr,\n+\t\t\t  size_t min_length, size_t max_length)\n+{\n+\tsize_t i;\n+\tstruct prefix_map prefix_map;\n+\n+\tinit_prefix_map(&prefix_map, min_length, max_length);\n+\tfor (i = 0; i < nr; i++)\n+\t\tadd_prefix_item(&prefix_map, array[i]);\n+\thashmap_free(&prefix_map.map, 1);\n+}\ndiff --git a/prefix-map.h b/prefix-map.h\nnew file mode 100644\nindex 0000000000..0c55ee0b2b\n--- /dev/null\n+++ b/prefix-map.h\n@@ -0,0 +1,29 @@\n+#ifndef PREFIX_MAP_H\n+#define PREFIX_MAP_H\n+\n+#include \"hashmap.h\"\n+\n+struct prefix_item {\n+\tconst char *name;\n+\tsize_t prefix_length;\n+};\n+\n+/*\n+ * Given an array of names, find unique prefixes (i.e. the first <n> characters\n+ * that uniquely identify the names) and store the lengths of the unique\n+ * prefixes in the 'prefix_length' field of the elements of the given array..\n+ *\n+ * Typically, the `struct prefix_item` information is a field in the actual\n+ * item struct; For this reason, the `array` parameter is specified as an array\n+ * of pointers to the items.\n+ *\n+ * The `min_length`/`max_length` parameters define what length the unique\n+ * prefixes should have.\n+ *\n+ * If no unique prefix could be found for a given item, its `prefix_length`\n+ * will be set to 0.\n+ */\n+void find_unique_prefixes(struct prefix_item **array, size_t nr,\n+\t\t\t  size_t min_length, size_t max_length);\n+\n+#endif\ndiff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c\nnew file mode 100644\nindex 0000000000..3f1c90eaf0\n--- /dev/null\n+++ b/t/helper/test-prefix-map.c\n@@ -0,0 +1,58 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"prefix-map.h\"\n+\n+static size_t test_count, failed_count;\n+\n+static void check(int succeeded, const char *file, size_t line_no,\n+\t\t  const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\ttest_count++;\n+\tif (succeeded)\n+\t\treturn;\n+\n+\tva_start(ap, fmt);\n+\tfprintf(stderr, \"%s:%d: \", file, (int)line_no);\n+\tvfprintf(stderr, fmt, ap);\n+\tfputc('\\n', stderr);\n+\tva_end(ap);\n+\n+\tfailed_count++;\n+}\n+\n+#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \\\n+\tcheck(expect == actual, __FILE__, __LINE__, \\\n+\t      \"size_t's do not match: %\" \\\n+\t      PRIdMAX \" != %\" PRIdMAX \" (%s) (%s)\", \\\n+\t      (intmax_t)expect, (intmax_t)actual, #actual, hint)\n+\n+int cmd__prefix_map(int argc, const char **argv)\n+{\n+#define NR 5\n+\tstruct prefix_item items[NR] = {\n+\t\t{ \"unique\" },\n+\t\t{ \"hell\" },\n+\t\t{ \"hello\" },\n+\t\t{ \"wok\" },\n+\t\t{ \"world\" },\n+\t};\n+\tstruct prefix_item *list[NR] = {\n+\t\titems, items + 1, items + 2, items + 3, items + 4\n+\t};\n+\n+\tfind_unique_prefixes(list, NR, 1, 3);\n+\n+#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \\\n+\tEXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \\\n+\t\t\t     list[index]->name)\n+\n+\tEXPECT_PREFIX_LENGTH_EQUALS(1, 0);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(0, 1);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(0, 2);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(3, 3);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(3, 4);\n+\n+\treturn !!failed_count;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex ce7e89028c..34ddf3f8b7 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -41,6 +41,7 @@ static struct test_cmd cmds[] = {\n \t{ \"parse-options\", cmd__parse_options },\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n+\t{ \"prefix-map\", cmd__prefix_map },\n \t{ \"prio-queue\", cmd__prio_queue },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex f805bb39ae..400854e60d 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -31,6 +31,7 @@ int cmd__online_cpus(int argc, const char **argv);\n int cmd__parse_options(int argc, const char **argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n+int cmd__prefix_map(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t0018-prefix-map.sh b/t/t0018-prefix-map.sh\nnew file mode 100755\nindex 0000000000..187fa92aec\n--- /dev/null\n+++ b/t/t0018-prefix-map.sh\n@@ -0,0 +1,10 @@\n+#!/bin/sh\n+\n+test_description='basic tests for prefix map'\n+. ./test-lib.sh\n+\n+test_expect_success 'prefix map' '\n+\ttest-tool prefix-map\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"381359","messageId":"24737a09f7d2f82a6d19bdfff846af32c4fb0aa4.1566910672.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 05/11] built-in add -i: color the header in the `status` command","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-27T12:57:57Z","receivedAt":"2019-08-27T12:58:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nFor simplicity, we only implemented the `status` command without colors.\nThis patch starts adding color, matching what the Perl script\n`git-add--interactive.perl` does.\n\nOriginal-Patch-By: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Djukic <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 60 ++++++++++++++++++++++++++++++++++++++++++-----\n 1 file changed, 54 insertions(+), 6 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 427abe505e..f5577a80d3 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -1,9 +1,51 @@\n #include \"cache.h\"\n #include \"add-interactive.h\"\n+#include \"color.h\"\n+#include \"config.h\"\n #include \"diffcore.h\"\n #include \"revision.h\"\n #include \"refs.h\"\n \n+struct add_i_state {\n+\tstruct repository *r;\n+\tint use_color;\n+\tchar header_color[COLOR_MAXLEN];\n+};\n+\n+static void init_color(struct repository *r, struct add_i_state *s,\n+\t\t       const char *slot_name, char *dst,\n+\t\t       const char *default_color)\n+{\n+\tchar *key = xstrfmt(\"color.interactive.%s\", slot_name);\n+\tconst char *value;\n+\n+\tif (!s->use_color)\n+\t\tdst[0] = '\\0';\n+\telse if (repo_config_get_value(r, key, &value) ||\n+\t\t color_parse(value, dst))\n+\t\tstrlcpy(dst, default_color, COLOR_MAXLEN);\n+\n+\tfree(key);\n+}\n+\n+static int init_add_i_state(struct repository *r, struct add_i_state *s)\n+{\n+\tconst char *value;\n+\n+\ts->r = r;\n+\n+\tif (repo_config_get_value(r, \"color.interactive\", &value))\n+\t\ts->use_color = -1;\n+\telse\n+\t\ts->use_color =\n+\t\t\tgit_config_colorbool(\"color.interactive\", value);\n+\ts->use_color = want_color(s->use_color);\n+\n+\tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n+\n+\treturn 0;\n+}\n+\n struct item {\n \tconst char *name;\n };\n@@ -14,7 +56,8 @@ struct list_options {\n \tvoid *print_item_data;\n };\n \n-static void list(struct item **list, size_t nr, struct list_options *opts)\n+static void list(struct item **list, size_t nr,\n+\t\t struct add_i_state *s, struct list_options *opts)\n {\n \tint i;\n \n@@ -22,7 +65,8 @@ static void list(struct item **list, size_t nr, struct list_options *opts)\n \t\treturn;\n \n \tif (opts->header)\n-\t\tprintf(\"%s\\n\", opts->header);\n+\t\tcolor_fprintf_ln(stdout, s->header_color,\n+\t\t\t\t \"%s\", opts->header);\n \n \tfor (i = 0; i < nr; i++) {\n \t\topts->print_item(i, list[i], opts->print_item_data);\n@@ -227,16 +271,16 @@ static void print_file_item(int i, struct item *item,\n \tprintf(\" %2d: %s\", i + 1, d->buf.buf);\n }\n \n-static int run_status(struct repository *r, const struct pathspec *ps,\n+static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \t\t      struct file_list *files, struct list_options *opts)\n {\n \treset_file_list(files);\n \n-\tif (get_modified_files(r, files, ps) < 0)\n+\tif (get_modified_files(s->r, files, ps) < 0)\n \t\treturn -1;\n \n \tif (files->nr)\n-\t\tlist((struct item **)files->file, files->nr, opts);\n+\t\tlist((struct item **)files->file, files->nr, s, opts);\n \tputchar('\\n');\n \n \treturn 0;\n@@ -244,6 +288,7 @@ static int run_status(struct repository *r, const struct pathspec *ps,\n \n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n+\tstruct add_i_state s = { NULL };\n \tstruct print_file_item_data print_file_item_data = {\n \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n \t};\n@@ -254,13 +299,16 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tstruct file_list files = { NULL };\n \tint res = 0;\n \n+\tif (init_add_i_state(r, &s))\n+\t\treturn error(\"could not parse `add -i` config\");\n+\n \tstrbuf_addstr(&header, \"      \");\n \tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n \topts.header = header.buf;\n \n \trepo_refresh_and_write_index(r, REFRESH_QUIET, 1);\n-\tif (run_status(r, ps, &files, &opts) < 0)\n+\tif (run_status(&s, ps, &files, &opts) < 0)\n \t\tres = -1;\n \n \trelease_file_list(&files);\n-- \ngitgitgadget\n\n"},{"id":"381402","messageId":"xmqq5zmi2qkp.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"nycvar.QRO.7.76.6.1908262317250.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-27T22:25:58Z","receivedAt":"2019-08-27T22:26:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Besides, I really hope that this would be only temporary,...\n\nOh, no question about it.  This should be temporary knob.\n\nI still do worry about giving a bad example for others to copy.\nPeople tend to copy & paste without thinking.  Either we come up\nwith and use a two-level name, or we add a comment to explain to\ndevelopers (not users---as this is merely a temporary thing) why\nthey should never follow suit using three-level names for things\nlike this one written in big red letters, or something, then perhaps\nwe won't have to worry about too much?  I dunno.\n\n>> > +\tif (use_builtin_add_i == 1 && !patch_mode)\n>> > +\t\treturn !!run_add_i(the_repository, pathspec);\n>>\n>> I am hoping that eventually \"add -p\" will also be routed to the new\n>> codepath.  Would it make sense to have \"&& !patch_mode\" here,\n>> especially at this step where run_add_i() won't do anything useful\n>> anyway yet?\n>\n> The `&& !patch_mode` is here to allow for a gradual adoption of the\n> built-in parts. ...\n\nAh, so \"add.usebuiltin = interactive patch\" can (eventually) choose\nto use the C code for both while \"add.usebuiltin = interactive\"\nwould not use it for the patch mode, or something?  Or even\n\n\tadd.interactive.usebuiltin = yes\n\tadd.patch.usebuiltin = no\n\nperhaps?\n\n> Of course, eventually this will be handled.\n\nYup, again, the knob is merely temporary.\n"},{"id":"381446","messageId":"nycvar.QRO.7.76.6.1908281705230.46@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"xmqq5zmi2qkp.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-08-28T15:06:36Z","receivedAt":"2019-08-28T15:06:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Tue, 27 Aug 2019, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>\n> > Besides, I really hope that this would be only temporary,...\n>\n> Oh, no question about it.  This should be temporary knob.\n>\n> I still do worry about giving a bad example for others to copy.\n> People tend to copy & paste without thinking.  Either we come up\n> with and use a two-level name, or we add a comment to explain to\n> developers (not users---as this is merely a temporary thing) why\n> they should never follow suit using three-level names for things\n> like this one written in big red letters, or something, then perhaps\n> we won't have to worry about too much?  I dunno.\n>\n> >> > +\tif (use_builtin_add_i == 1 && !patch_mode)\n> >> > +\t\treturn !!run_add_i(the_repository, pathspec);\n> >>\n> >> I am hoping that eventually \"add -p\" will also be routed to the new\n> >> codepath.  Would it make sense to have \"&& !patch_mode\" here,\n> >> especially at this step where run_add_i() won't do anything useful\n> >> anyway yet?\n> >\n> > The `&& !patch_mode` is here to allow for a gradual adoption of the\n> > built-in parts. ...\n>\n> Ah, so \"add.usebuiltin = interactive patch\" can (eventually) choose\n> to use the C code for both while \"add.usebuiltin = interactive\"\n> would not use it for the patch mode, or something?  Or even\n>\n> \tadd.interactive.usebuiltin = yes\n\nThis is what I had in mind.\n\n> \tadd.patch.usebuiltin = no\n\nAnd this is what I should have done ;-)\n\nBut maybe I should do\n\n\tadd.useBuiltin = wheneverPossible\n\n?\n\nCiao,\nDscho\n\n>\n> perhaps?\n>\n> > Of course, eventually this will be handled.\n>\n> Yup, again, the knob is merely temporary.\n\n"},{"id":"381451","messageId":"xmqqd0gp1etv.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"nycvar.QRO.7.76.6.1908281705230.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-28T15:37:16Z","receivedAt":"2019-08-28T15:37:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> Ah, so \"add.usebuiltin = interactive patch\" can (eventually) choose\n>> to use the C code for both while \"add.usebuiltin = interactive\"\n>> would not use it for the patch mode, or something?  Or even\n>>\n>> \tadd.interactive.usebuiltin = yes\n>\n> This is what I had in mind.\n>\n>> \tadd.patch.usebuiltin = no\n>\n> And this is what I should have done ;-)\n>\n> But maybe I should do\n>\n> \tadd.useBuiltin = wheneverPossible\n\nAh, either is fine, I think, but because this is meant to be\ntemporary and not advertised to end-users for purposes other than\npurely an escape hatch, I would not spend too much engineering\neffort (iow, do the easiest to add and then easiest to later yank\nout).\n\nThanks.\n\n"},{"id":"381458","messageId":"20190828163045.GF8571@szeder.dev","threadId":"50913","inReplyTo":"nycvar.QRO.7.76.6.1908271342140.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-08-28T16:30:45Z","receivedAt":"2019-08-28T16:30:54Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Aug 27, 2019 at 02:14:06PM +0200, Johannes Schindelin wrote:\n> Hi Gábor,\n> \n> On Sat, 24 Aug 2019, SZEDER Gábor wrote:\n> \n> > On Tue, Jul 16, 2019 at 07:58:42AM -0700, Slavica Djukic via GitGitGadget wrote:\n> > > In the `git add -i` command, we show unique prefixes of the commands and\n> > > files, to give an indication what prefix would select them.\n> > >\n> > > Naturally, the C implementation looks a lot different than the Perl\n> > > implementation: in Perl, a trie is much easier implemented, while we\n> > > already have a pretty neat hashmap implementation in C that we use for\n> > > the purpose of storing (not necessarily unique) prefixes.\n> > >\n> > > The idea: for each item that we add, we generate prefixes starting with\n> > > the first letter, then the first two letters, then three, etc, until we\n> > > find a prefix that is unique (or until the prefix length would be\n> > > longer than we want). If we encounter a previously-unique prefix on the\n> > > way, we adjust that item's prefix to make it unique again (or we mark it\n> > > as having no unique prefix if we failed to find one). These partial\n> > > prefixes are stored in a hash map (for quick lookup times).\n> > >\n> > > To make sure that this function works as expected, we add a test using a\n> > > special-purpose test helper that was added for that purpose.\n> > >\n> > > Note: We expect the list of prefix items to be passed in as a list of\n> > > pointers rather than as regular list to avoid having to copy information\n> > > (the actual items will most likely contain more information than just\n> > > the name and the length of the unique prefix, but passing in `struct\n> > > prefix_item *` would not allow for that).\n> >\n> > > diff --git a/prefix-map.c b/prefix-map.c\n> > > new file mode 100644\n> > > index 0000000000..747ddb4ebc\n> > > --- /dev/null\n> > > +++ b/prefix-map.c\n> > > @@ -0,0 +1,109 @@\n> > > +#include \"cache.h\"\n> > > +#include \"prefix-map.h\"\n> > > +\n> > > +static int map_cmp(const void *unused_cmp_data,\n> > > +\t\t   const void *entry,\n> > > +\t\t   const void *entry_or_key,\n> > > +\t\t   const void *unused_keydata)\n> > > +{\n> > > +\tconst struct prefix_map_entry *a = entry;\n> > > +\tconst struct prefix_map_entry *b = entry_or_key;\n> > > +\n> > > +\treturn a->prefix_length != b->prefix_length ||\n> > > +\t\tstrncmp(a->name, b->name, a->prefix_length);\n> > > +}\n> > > +\n> > > +static void add_prefix_entry(struct hashmap *map, const char *name,\n> > > +\t\t\t     size_t prefix_length, struct prefix_item *item)\n> > > +{\n> > > +\tstruct prefix_map_entry *result = xmalloc(sizeof(*result));\n> > > +\tresult->name = name;\n> > > +\tresult->prefix_length = prefix_length;\n> > > +\tresult->item = item;\n> > > +\thashmap_entry_init(result, memhash(name, prefix_length));\n> > > +\thashmap_add(map, result);\n> > > +}\n> > > +\n> > > +static void init_prefix_map(struct prefix_map *prefix_map,\n> > > +\t\t\t    int min_prefix_length, int max_prefix_length)\n> > > +{\n> > > +\thashmap_init(&prefix_map->map, map_cmp, NULL, 0);\n> > > +\tprefix_map->min_length = min_prefix_length;\n> > > +\tprefix_map->max_length = max_prefix_length;\n> > > +}\n> > > +\n> > > +static void add_prefix_item(struct prefix_map *prefix_map,\n> > > +\t\t\t    struct prefix_item *item)\n> > > +{\n> > > +\tstruct prefix_map_entry e = { { NULL } }, *e2;\n> > > +\tint j;\n> > > +\n> > > +\te.item = item;\n> > > +\te.name = item->name;\n> > > +\n> > > +\tfor (j = prefix_map->min_length;\n> > > +\t     j <= prefix_map->max_length && e.name[j]; j++) {\n> > > +\t\t/* Avoid breaking UTF-8 multi-byte sequences */\n\nOk, that's a good idea, but...\n\n> > > +\t\tif (!isascii(e.name[j]))\n> > > +\t\t\tbreak;\n\nThis doesn't seem right.  It just breaks the loop, potentially leaving\n'item->prefix_length' set from the previous iteration.  Try the two\nstrings \"123\" and \"12ő\", in this order: they both get prefix_length =\n3, i.e. the prefix ends between the two bytes of the multi-byte\ncharacter.\n\nFurthermore, neither the docstring of find_unique_prefixes() nor the\ncommit message mention what is supposed to happen with paths with\nmulti-byte characters.  I think at least one of them should.\n\n> > > +\n> > > +\t\te.prefix_length = j;\n> > > +\t\thashmap_entry_init(&e, memhash(e.name, j));\n> > > +\t\te2 = hashmap_get(&prefix_map->map, &e, NULL);\n> > > +\t\tif (!e2) {\n> > > +\t\t\t/* prefix is unique at this stage */\n> > > +\t\t\titem->prefix_length = j;\n> > > +\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j, item);\n> > > +\t\t\tbreak;\n> > > +\t\t}\n> > > +\n> > > +\t\tif (!e2->item)\n> > > +\t\t\tcontinue; /* non-unique prefix */\n> > > +\n> > > +\t\tif (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))\n> > > +\t\t\tBUG(\"unexpected prefix length: %d != %d (%s != %s)\",\n> > > +\t\t\t    j, (int)e2->item->prefix_length, e.name, e2->name);\n> > > +\n> > > +\t\t/* skip common prefix */\n> > > +\t\tfor (; j < prefix_map->max_length && e.name[j]; j++) {\n> > > +\t\t\tif (e.item->name[j] != e2->item->name[j])\n> > > +\t\t\t\tbreak;\n> > > +\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n> > > +\t\t\t\t\t NULL);\n> > > +\t\t}\n> > > +\n> > > +\t\t/* e2 no longer refers to a unique prefix */\n> > > +\t\tif (j < prefix_map->max_length && e2->name[j]) {\n> > > +\t\t\t/* found a new unique prefix for e2's item */\n> > > +\t\t\te2->item->prefix_length = j + 1;\n> > > +\t\t\tadd_prefix_entry(&prefix_map->map, e2->name, j + 1,\n> > > +\t\t\t\t\t e2->item);\n> > > +\t\t}\n> > > +\t\telse\n> > > +\t\t\te2->item->prefix_length = 0;\n> > > +\t\te2->item = NULL;\n> > > +\n> > > +\t\tif (j < prefix_map->max_length && e.name[j]) {\n> > > +\t\t\t/* found a unique prefix for the item */\n> > > +\t\t\te.item->prefix_length = j + 1;\n> > > +\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n> > > +\t\t\t\t\t e.item);\n> > > +\t\t} else\n> > > +\t\t\t/* item has no (short enough) unique prefix */\n> > > +\t\t\te.item->prefix_length = 0;\n> > > +\n> > > +\t\tbreak;\n> > > +\t}\n> > > +}\n> > > +\n> > > +void find_unique_prefixes(struct prefix_item **list, size_t nr,\n> > > +\t\t\t  int min_length, int max_length)\n> > > +{\n> > > +\tint i;\n> > > +\tstruct prefix_map prefix_map;\n> > > +\n> > > +\tinit_prefix_map(&prefix_map, min_length, max_length);\n> > > +\tfor (i = 0; i < nr; i++)\n> > > +\t\tadd_prefix_item(&prefix_map, list[i]);\n> > > +\thashmap_free(&prefix_map.map, 1);\n> > > +}\n> >\n> > Between the commit message, the in-code comment, the names of the new\n> > files, and implementation I was left somewhat confused about what this\n> > is about and how it works.  TBH, I didn't even try to understand how\n> > all the above works, in particular the add_prefix_item() function.\n> \n> Let me try to explain it here, and maybe you can help me by suggesting\n> an improved commit message and/or code comments?\n> \n> The problem is this: given a set of items with labels (e.g. file names),\n> find, for each item, the unique prefix that identifies it. Example:\n> given the files `hello.txt`, `heaven.txt` and `hell.txt`, the items'\n> unique prefixes would be `hello`, `hea` and `hell.`, respectively.\n> \n> In `git add -i`, we actually only want to allow alphanumerical prefixes,\n> and we also want at least one, and at most three characters, so only the\n> second item would have an admissible unique prefix: `hea`.\n\nYou say \"at most three characters\", but I notive that the call added\nto 'add-interactive.c' in a later commit specifies 4.\n\nI wonder what's the reason for the minimum prefix length, though.  I\nmean, you can't really have a unique prefix shorter than one\ncharacter...\n\n> > However, I think it would be much-much simpler to first sort (a copy\n> > of?) the array of prefix item pointers based on their 'name' field,\n> > and then look for a unique prefix in each neighboring pair.  Perhaps\n> > it would even be faster, because it doesn't have to allocate a bunch\n> > of hashmap items, though I don't think that it matters much in\n> > practice (i.e. I expect the number of items to be fairly small;\n> > presumably nobody will run interactive add after a mass refactoring\n> > modifying thousands of files).\n> \n> The time complexity of the sorted list would be O(n*log(n)), while the\n> hashmap-based complexity would be an amortized O(n).\n\nOh, so you don't know how/why it works, either!? ;)\n\nThis hashmap-based implementation calls hashmap_get() approx.\nn * (max_length - min_length) times.\n\nFurthermore, depending on the strings and 'max_length', it can call\nadd_prefix_entry() more than n times.  E.g. the six strings \"a1\",\n\"a2\", \"ab1\", \"ab2\", \"abc1\", \"abc2\", in this order, and max_length=4\nresult in 9 add_prefix_entry() calls.  And each add_prefix_entry()\ncall involves a memory allocation.\n\nOk, that's theory, let's see practice.\n\nFor a (very) quick'n'dirty performance test I replaced those five\nitems in 't/helper/test-prefix-map.c' with the output of:\n\n  git -C ..../webkit.git/ ls-tree -r --name-only HEAD | sed -e 's/^/{ \"/; s/$/\" },/'\n\nThat's 280734 real paths.  Then a simple 'time test-tool prefix-map'\nwith the hashmap-based implementation ran for 0.055s, while using my\nPoC sort-based algorithm it ran for 0.063s.  So while the\nhashmap-based implementation is indeed faster, they are both well\nbelow 0.1s, so in the context of 'git add -i' the performance\ndifference between the two approaches doesn't really matter.  \n\nIf we look beyond 'git add -i's requirements, and specify larger\nmax_length values, the hashmap-based implementation gets progressively\nslower.  Already at max_length=5 it's a tad slower than the sort-based\nimplementation, and at 9 it takes twice as long.\n\nThe current hashmap-based implementation is ~50% more lines than my\nsort-based PoC.  I'll send it out in a minute.\n\n> And yes, you would not _want_ to run interactive add after a mass\n> refactoring. But it happens. It happens to me more times than I care to\n> admit. And you know what? I really appreciate that even the Perl version\n> is relatively snappy in those circumstances.\n> \n> > > diff --git a/prefix-map.h b/prefix-map.h\n> > > new file mode 100644\n> > > index 0000000000..ce3b8a4a32\n> > > --- /dev/null\n> > > +++ b/prefix-map.h\n> > > @@ -0,0 +1,40 @@\n> > > +#ifndef PREFIX_MAP_H\n> > > +#define PREFIX_MAP_H\n> > >\n> > > +#include \"hashmap.h\"\n> > > +\n> > > +struct prefix_item {\n> > > +\tconst char *name;\n> > > +\tsize_t prefix_length;\n> > > +};\n> >\n> > This struct is part of find_unique_prefixes()'s signature, good.\n> >\n> > > +struct prefix_map_entry {\n> > > +\tstruct hashmap_entry e;\n> > > +\tconst char *name;\n> > > +\tsize_t prefix_length;\n> > > +\t/* if item is NULL, the prefix is not unique */\n> > > +\tstruct prefix_item *item;\n> > > +};\n> > > +\n> > > +struct prefix_map {\n> > > +\tstruct hashmap map;\n> > > +\tint min_length, max_length;\n> > > +};\n> >\n> > However, neither of these two structs nor the hashmap appear in the\n> > function's signature, but are all implementation details.  Therefore,\n> > they should not be defined and included here in the header but in the\n> > .c source file.  (But as mentioned above, I think this could be\n> > implemented much simpler without these data structures.)\n> \n> Right you are!\n> \n> > Furthermore, this is not a map.\n> > A map, in general, is a container of key-value pairs that allows\n> > efficient insertion, removal and lookup.  This so-called prefix_map\n> > does none of that, so it should not be called a map.\n> \n> What would you call it instead?\n\nWell, I would rather get rid of it in the first place :)\n\nIn my PoC I called the files 'unique-prefix.{c,h}'.\n\n> > > +/*\n> > > + * Find unique prefixes in a given list of strings.\n> >\n> > ... and stores the length of the unique prefixes in the\n> > 'prefix_length' field of the elements of the given array.\n> \n> Good idea. I changed it to also explain what is meant by \"unique\n> prefix\":\n> \n>  * Given a list of names, find unique prefixes (i.e. the first <n> characters\n>  * that uniquely identify the names) and store the lengths of the unique\n>  * prefixes in the 'prefix_length' field of the elements of the given array..\n\nSounds good.\n\n> > > + *\n> > > + * Typically, the `struct prefix_item` information will be but a field in the\n\n> > > + * actual item struct; For this reason, the `list` parameter is specified as a\n> > > + * list of pointers to the items.\n> > > + *\n> > > + * The `min_length`/`max_length` parameters define what length the unique\n> > > + * prefixes should have.\n> > > + *\n> > > + * If no unique prefix could be found for a given item, its `prefix_length`\n> > > + * will be set to 0.\n\nI ran into cases where the hashmap-based implementation didn't set\n'prefix_length' to 0 when it, in my opinion, should have, but left it\nuntouched.  Just try any string shorter than 'min_length'.\n\nMay or may not be related: try \"ab\", \"ab\", \"abc\", in this order: both\n\"ab\" get their 'prefix_length' set to 0, as they should, but the\n'prefix_length' of \"abc\" is left untouched, although it should be set\nto 3.\n\nHmm, now that I've mentioned \"in this order\" three times already:\nanother benefit of the sort-based approach is that the order of\nelements doesn't matter.\n\n> > > + */\n> > > +void find_unique_prefixes(struct prefix_item **list, size_t nr,\n> >\n> > The first argument is not a list but an array.\n> \n> Indeed.\n> \n> > > +\t\t\t  int min_length, int max_length);\n> >\n> > size_t, perhaps?  These are closely related to\n> > 'prefix_item.prefix_length', which is already (rightfully) size_t.\n> \n> True.\n> \n> > > +#endif\n> > > diff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c\n> > > new file mode 100644\n> > > index 0000000000..3f1c90eaf0\n> > > --- /dev/null\n> > > +++ b/t/helper/test-prefix-map.c\n> > > @@ -0,0 +1,58 @@\n> > > +#include \"test-tool.h\"\n> > > +#include \"cache.h\"\n> > > +#include \"prefix-map.h\"\n> > > +\n> > > +static size_t test_count, failed_count;\n> > > +\n> > > +static void check(int succeeded, const char *file, size_t line_no,\n> > > +\t\t  const char *fmt, ...)\n> > > +{\n> > > +\tva_list ap;\n> > > +\n> > > +\ttest_count++;\n> > > +\tif (succeeded)\n> > > +\t\treturn;\n> > > +\n> > > +\tva_start(ap, fmt);\n> > > +\tfprintf(stderr, \"%s:%d: \", file, (int)line_no);\n> > > +\tvfprintf(stderr, fmt, ap);\n> > > +\tfputc('\\n', stderr);\n> > > +\tva_end(ap);\n> > > +\n> > > +\tfailed_count++;\n> > > +}\n> > > +\n> > > +#define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \\\n> > > +\tcheck(expect == actual, __FILE__, __LINE__, \\\n> > > +\t      \"size_t's do not match: %\" \\\n> > > +\t      PRIdMAX \" != %\" PRIdMAX \" (%s) (%s)\", \\\n> > > +\t      (intmax_t)expect, (intmax_t)actual, #actual, hint)\n> > > +\n> > > +int cmd__prefix_map(int argc, const char **argv)\n> > > +{\n> > > +#define NR 5\n> > > +\tstruct prefix_item items[NR] = {\n> >\n> > You don't have to tell the compiler how many elements this array will\n> > contain, it will figure that out on its own.\n> >\n> > > +\t\t{ \"unique\" },\n> > > +\t\t{ \"hell\" },\n> > > +\t\t{ \"hello\" },\n> > > +\t\t{ \"wok\" },\n> > > +\t\t{ \"world\" },\n> > > +\t};\n> > > +\tstruct prefix_item *list[NR] = {\n> >\n> > Likewise.\n> \n> That is correct.\n> \n> What the compiler _cannot_ figure out, on its own, however, is that\n> `items` and `list` _need_ to contain the same number of items.\n> \n> Hence the need for `NR`.\n\nOK, that's a good point.  I'd still like to get rid of that NR with\nsomething like this instead:\n\n  struct prefix_item items[] = { .... };\n  struct prefix_item *list[ARRAY_SIZE(items)];\n  for (i = 0; i < ARRAY_SIZE(items); i++) {\n      list[i] = &items[i];\n      list[i]->prefix_length = 12345; /* dummy */\n  }\n  find_unique_prefixes(list, ARRAY_SIZE(list), 0, 3);\n\nNote the dummy value, to catch cases when 'prefix_length' is left\nuntouched.\n\nThis made this test tool more convenient to use, because I didn't have\nto fiddle with NR and, worse, with those 'items, items+1, items+N'.\n\nIt's still a bit PITA (having to re-build each time I change the test\nvector), though, compared to driving it from stdin and verifying it\nthrough its stdout.\n\n> > > +\t\titems, items + 1, items + 2, items + 3, items + 4\n> > > +\t};\n> > > +\n> > > +\tfind_unique_prefixes(list, NR, 1, 3);\n> >\n> > This could be find_unique_prefixes(list, ARRAY_SIZE(list), 1, 3), and\n> > then there is no need for that NR macro anymore.\n> >\n> > > +\n> > > +#define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \\\n> > > +\tEXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \\\n> > > +\t\t\t     list[index]->name)\n> > > +\n> > > +\tEXPECT_PREFIX_LENGTH_EQUALS(1, 0);\n> > > +\tEXPECT_PREFIX_LENGTH_EQUALS(0, 1);\n> > > +\tEXPECT_PREFIX_LENGTH_EQUALS(0, 2);\n> > > +\tEXPECT_PREFIX_LENGTH_EQUALS(3, 3);\n> > > +\tEXPECT_PREFIX_LENGTH_EQUALS(3, 4);\n> > > +\n> > > +\treturn !!failed_count;\n> > > +}\n> \n> Thank you for your review!\n> Dscho\n\n"},{"id":"381459","messageId":"20190828163419.30620-1-szeder.dev@gmail.com","threadId":"50913","inReplyTo":"20190828163045.GF8571@szeder.dev","subject":"[PATCH] [PoC] A simpler find_unique_prefixes() implementation","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-08-28T16:34:19Z","receivedAt":"2019-08-28T16:35:21Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"Implement find_unique_prefixes() by sorting the prefix items\nlexicographically and then looking for unique prefixes in adjacent\npairs.  It's definitely shorter than the current hashmap-based\nimplementation (101 vs 149 lines), subjectively easier to follow, and\nhopefully less buggy (some of the new elements in the test trigger\nfailure with the hashmap).\n\nTODO: deal multi-byte caracter sequences, make the test tool more\n      developer-friendly\n---\n Makefile                   |   2 +-\n add-interactive.c          |   2 +-\n prefix-map.c               | 109 -------------------------------------\n prefix-map.h               |  40 --------------\n t/helper/test-prefix-map.c |  31 +++++++----\n unique-prefix.c            |  73 +++++++++++++++++++++++++\n unique-prefix.h            |  28 ++++++++++\n 7 files changed, 124 insertions(+), 161 deletions(-)\n delete mode 100644 prefix-map.c\n delete mode 100644 prefix-map.h\n create mode 100644 unique-prefix.c\n create mode 100644 unique-prefix.h\n\ndiff --git a/Makefile b/Makefile\nindex 545a0e8743..6773041f0d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -944,7 +944,6 @@ LIB_OBJS += patch-ids.o\n LIB_OBJS += path.o\n LIB_OBJS += pathspec.o\n LIB_OBJS += pkt-line.o\n-LIB_OBJS += prefix-map.o\n LIB_OBJS += preload-index.o\n LIB_OBJS += pretty.o\n LIB_OBJS += prio-queue.o\n@@ -1013,6 +1012,7 @@ LIB_OBJS += transport-helper.o\n LIB_OBJS += tree-diff.o\n LIB_OBJS += tree.o\n LIB_OBJS += tree-walk.o\n+LIB_OBJS += unique-prefix.o\n LIB_OBJS += unpack-trees.o\n LIB_OBJS += upload-pack.o\n LIB_OBJS += url.o\ndiff --git a/add-interactive.c b/add-interactive.c\nindex c431c72e3f..cf22749b81 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -5,7 +5,7 @@\n #include \"diffcore.h\"\n #include \"revision.h\"\n #include \"refs.h\"\n-#include \"prefix-map.h\"\n+#include \"unique-prefix.h\"\n \n struct add_i_state {\n \tstruct repository *r;\ndiff --git a/prefix-map.c b/prefix-map.c\ndeleted file mode 100644\nindex 747ddb4ebc..0000000000\n--- a/prefix-map.c\n+++ /dev/null\n@@ -1,109 +0,0 @@\n-#include \"cache.h\"\n-#include \"prefix-map.h\"\n-\n-static int map_cmp(const void *unused_cmp_data,\n-\t\t   const void *entry,\n-\t\t   const void *entry_or_key,\n-\t\t   const void *unused_keydata)\n-{\n-\tconst struct prefix_map_entry *a = entry;\n-\tconst struct prefix_map_entry *b = entry_or_key;\n-\n-\treturn a->prefix_length != b->prefix_length ||\n-\t\tstrncmp(a->name, b->name, a->prefix_length);\n-}\n-\n-static void add_prefix_entry(struct hashmap *map, const char *name,\n-\t\t\t     size_t prefix_length, struct prefix_item *item)\n-{\n-\tstruct prefix_map_entry *result = xmalloc(sizeof(*result));\n-\tresult->name = name;\n-\tresult->prefix_length = prefix_length;\n-\tresult->item = item;\n-\thashmap_entry_init(result, memhash(name, prefix_length));\n-\thashmap_add(map, result);\n-}\n-\n-static void init_prefix_map(struct prefix_map *prefix_map,\n-\t\t\t    int min_prefix_length, int max_prefix_length)\n-{\n-\thashmap_init(&prefix_map->map, map_cmp, NULL, 0);\n-\tprefix_map->min_length = min_prefix_length;\n-\tprefix_map->max_length = max_prefix_length;\n-}\n-\n-static void add_prefix_item(struct prefix_map *prefix_map,\n-\t\t\t    struct prefix_item *item)\n-{\n-\tstruct prefix_map_entry e = { { NULL } }, *e2;\n-\tint j;\n-\n-\te.item = item;\n-\te.name = item->name;\n-\n-\tfor (j = prefix_map->min_length;\n-\t     j <= prefix_map->max_length && e.name[j]; j++) {\n-\t\t/* Avoid breaking UTF-8 multi-byte sequences */\n-\t\tif (!isascii(e.name[j]))\n-\t\t\tbreak;\n-\n-\t\te.prefix_length = j;\n-\t\thashmap_entry_init(&e, memhash(e.name, j));\n-\t\te2 = hashmap_get(&prefix_map->map, &e, NULL);\n-\t\tif (!e2) {\n-\t\t\t/* prefix is unique at this stage */\n-\t\t\titem->prefix_length = j;\n-\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j, item);\n-\t\t\tbreak;\n-\t\t}\n-\n-\t\tif (!e2->item)\n-\t\t\tcontinue; /* non-unique prefix */\n-\n-\t\tif (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))\n-\t\t\tBUG(\"unexpected prefix length: %d != %d (%s != %s)\",\n-\t\t\t    j, (int)e2->item->prefix_length, e.name, e2->name);\n-\n-\t\t/* skip common prefix */\n-\t\tfor (; j < prefix_map->max_length && e.name[j]; j++) {\n-\t\t\tif (e.item->name[j] != e2->item->name[j])\n-\t\t\t\tbreak;\n-\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n-\t\t\t\t\t NULL);\n-\t\t}\n-\n-\t\t/* e2 no longer refers to a unique prefix */\n-\t\tif (j < prefix_map->max_length && e2->name[j]) {\n-\t\t\t/* found a new unique prefix for e2's item */\n-\t\t\te2->item->prefix_length = j + 1;\n-\t\t\tadd_prefix_entry(&prefix_map->map, e2->name, j + 1,\n-\t\t\t\t\t e2->item);\n-\t\t}\n-\t\telse\n-\t\t\te2->item->prefix_length = 0;\n-\t\te2->item = NULL;\n-\n-\t\tif (j < prefix_map->max_length && e.name[j]) {\n-\t\t\t/* found a unique prefix for the item */\n-\t\t\te.item->prefix_length = j + 1;\n-\t\t\tadd_prefix_entry(&prefix_map->map, e.name, j + 1,\n-\t\t\t\t\t e.item);\n-\t\t} else\n-\t\t\t/* item has no (short enough) unique prefix */\n-\t\t\te.item->prefix_length = 0;\n-\n-\t\tbreak;\n-\t}\n-}\n-\n-void find_unique_prefixes(struct prefix_item **list, size_t nr,\n-\t\t\t  int min_length, int max_length)\n-{\n-\tint i;\n-\tstruct prefix_map prefix_map;\n-\n-\tinit_prefix_map(&prefix_map, min_length, max_length);\n-\tfor (i = 0; i < nr; i++)\n-\t\tadd_prefix_item(&prefix_map, list[i]);\n-\thashmap_free(&prefix_map.map, 1);\n-}\ndiff --git a/prefix-map.h b/prefix-map.h\ndeleted file mode 100644\nindex ce3b8a4a32..0000000000\n--- a/prefix-map.h\n+++ /dev/null\n@@ -1,40 +0,0 @@\n-#ifndef PREFIX_MAP_H\n-#define PREFIX_MAP_H\n-\n-#include \"hashmap.h\"\n-\n-struct prefix_item {\n-\tconst char *name;\n-\tsize_t prefix_length;\n-};\n-\n-struct prefix_map_entry {\n-\tstruct hashmap_entry e;\n-\tconst char *name;\n-\tsize_t prefix_length;\n-\t/* if item is NULL, the prefix is not unique */\n-\tstruct prefix_item *item;\n-};\n-\n-struct prefix_map {\n-\tstruct hashmap map;\n-\tint min_length, max_length;\n-};\n-\n-/*\n- * Find unique prefixes in a given list of strings.\n- *\n- * Typically, the `struct prefix_item` information will be but a field in the\n- * actual item struct; For this reason, the `list` parameter is specified as a\n- * list of pointers to the items.\n- *\n- * The `min_length`/`max_length` parameters define what length the unique\n- * prefixes should have.\n- *\n- * If no unique prefix could be found for a given item, its `prefix_length`\n- * will be set to 0.\n- */\n-void find_unique_prefixes(struct prefix_item **list, size_t nr,\n-\t\t\t  int min_length, int max_length);\n-\n-#endif\ndiff --git a/t/helper/test-prefix-map.c b/t/helper/test-prefix-map.c\nindex 3f1c90eaf0..d472ff5dc6 100644\n--- a/t/helper/test-prefix-map.c\n+++ b/t/helper/test-prefix-map.c\n@@ -1,6 +1,5 @@\n #include \"test-tool.h\"\n-#include \"cache.h\"\n-#include \"prefix-map.h\"\n+#include \"unique-prefix.h\"\n \n static size_t test_count, failed_count;\n \n@@ -24,25 +23,32 @@ static void check(int succeeded, const char *file, size_t line_no,\n \n #define EXPECT_SIZE_T_EQUALS(expect, actual, hint) \\\n \tcheck(expect == actual, __FILE__, __LINE__, \\\n-\t      \"size_t's do not match: %\" \\\n-\t      PRIdMAX \" != %\" PRIdMAX \" (%s) (%s)\", \\\n+\t      \"size_t's do not match: expected: %\" \\\n+\t      PRIdMAX \" got: %\" PRIdMAX \" (%s) (%s)\", \\\n \t      (intmax_t)expect, (intmax_t)actual, #actual, hint)\n \n int cmd__prefix_map(int argc, const char **argv)\n {\n-#define NR 5\n-\tstruct prefix_item items[NR] = {\n+\tsize_t i;\n+\tstruct prefix_item items[] = {\n \t\t{ \"unique\" },\n \t\t{ \"hell\" },\n \t\t{ \"hello\" },\n \t\t{ \"wok\" },\n \t\t{ \"world\" },\n+\t\t{ \"a\" },\n+\t\t{ \"\" },\n+\t\t{ \"pq\" },\n+\t\t{ \"pq\" },\n+\t\t{ \"pqr\" },\n \t};\n-\tstruct prefix_item *list[NR] = {\n-\t\titems, items + 1, items + 2, items + 3, items + 4\n-\t};\n+\tstruct prefix_item *list[ARRAY_SIZE(items)];\n+\tfor (i = 0; i < ARRAY_SIZE(items); i++) {\n+\t\tlist[i] = &items[i];\n+\t\tlist[i]->prefix_length = 12345; /* dummy */\n+\t}\n \n-\tfind_unique_prefixes(list, NR, 1, 3);\n+\tfind_unique_prefixes(list, ARRAY_SIZE(list), 1, 3);\n \n #define EXPECT_PREFIX_LENGTH_EQUALS(expect, index) \\\n \tEXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \\\n@@ -53,6 +59,11 @@ int cmd__prefix_map(int argc, const char **argv)\n \tEXPECT_PREFIX_LENGTH_EQUALS(0, 2);\n \tEXPECT_PREFIX_LENGTH_EQUALS(3, 3);\n \tEXPECT_PREFIX_LENGTH_EQUALS(3, 4);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(1, 5);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(0, 6);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(0, 7);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(0, 8);\n+\tEXPECT_PREFIX_LENGTH_EQUALS(3, 9);\n \n \treturn !!failed_count;\n }\ndiff --git a/unique-prefix.c b/unique-prefix.c\nnew file mode 100644\nindex 0000000000..327e8fa32c\n--- /dev/null\n+++ b/unique-prefix.c\n@@ -0,0 +1,73 @@\n+#include \"git-compat-util.h\"\n+#include \"unique-prefix.h\"\n+\n+static int prefix_item_ptr_cmp(const void *item1, const void *item2)\n+{\n+\tconst struct prefix_item *pi1 = *((struct prefix_item**) item1);\n+\tconst struct prefix_item *pi2 = *((struct prefix_item**) item2);\n+\treturn strcmp(pi1->name, pi2->name);\n+}\n+\n+static int str_is_shorter_than(const char *s, size_t len)\n+{\n+\tfor (; *s && len; s++, len--);\n+\treturn !!len;\n+}\n+\n+static size_t str_common_prefix_len(const char *s1, const char *s2, size_t limit)\n+{\n+\tsize_t i;\n+\tfor (i = 0; i < limit; i++)\n+\t\tif (s1[i] != s2[i] || s1[i] == '\\0')\n+\t\t\treturn i;\n+\treturn limit + 1;\n+}\n+\n+void find_unique_prefixes(struct prefix_item **items, size_t nr,\n+\t\t\t  int min_length, int max_length)\n+{\n+\tsize_t i;\n+\tstruct prefix_item **sorted_items;\n+\n+\tALLOC_ARRAY(sorted_items, nr);\n+\tCOPY_ARRAY(sorted_items, items, nr);\n+\tQSORT(sorted_items, nr, prefix_item_ptr_cmp);\n+\n+\tif (str_is_shorter_than(sorted_items[0]->name, min_length))\n+\t\tsorted_items[0]->prefix_length = 0;\n+\telse\n+\t\tsorted_items[0]->prefix_length = min_length;\n+\tfor (i = 1; i < nr; i++) {\n+\t\tsize_t common_len = str_common_prefix_len(\n+\t\t\tsorted_items[i - 1]->name, sorted_items[i]->name,\n+\t\t\tmax_length);\n+\n+\t\tif (!sorted_items[i - 1]->prefix_length)\n+\t\t\t/*\n+\t\t\t * The previous iteration already determined that\n+\t\t\t * it doesn't have a unique prefix.\n+\t\t\t */\n+\t\t\t;\n+\t\telse if (max_length < common_len)\n+\t\t\tsorted_items[i - 1]->prefix_length = 0;\n+\t\telse if (sorted_items[i - 1]->name[common_len] == '\\0')\n+\t\t\t/* Either prefix of or equal to the next string. */\n+\t\t\tsorted_items[i - 1]->prefix_length = 0;\n+\t\telse if (sorted_items[i - 1]->prefix_length <= common_len)\n+\t\t\tsorted_items[i - 1]->prefix_length = common_len + 1;\n+\n+\t\tif (max_length < common_len)\n+\t\t\tsorted_items[i]->prefix_length = 0;\n+\t\telse if (sorted_items[i]->name[common_len] == '\\0')\n+\t\t\t/* the two strings are the same */\n+\t\t\tsorted_items[i]->prefix_length = 0;\n+\t\telse if (min_length <= common_len)\n+\t\t\tsorted_items[i]->prefix_length = common_len + 1;\n+\t\telse if (str_is_shorter_than(sorted_items[i]->name, min_length))\n+\t\t\tsorted_items[i]->prefix_length = 0;\n+\t\telse\n+\t\t\tsorted_items[i]->prefix_length = min_length;\n+\t}\n+\n+\tfree(sorted_items);\n+}\ndiff --git a/unique-prefix.h b/unique-prefix.h\nnew file mode 100644\nindex 0000000000..cbddc29a9a\n--- /dev/null\n+++ b/unique-prefix.h\n@@ -0,0 +1,28 @@\n+#ifndef UNIQUE_PREFIX_H\n+#define UNIQUE_PREFIX_H\n+\n+struct prefix_item {\n+\tconst char *name;\n+\tsize_t prefix_length;\n+};\n+\n+/*\n+ * Find unique prefixes in a given array of strings.\n+ * Given a list of names, find unique prefixes (i.e. the first <n> characters\n+ * that uniquely identify the names) and store the lengths of the unique\n+ * prefixes in the 'prefix_length' field of the elements of the given array.\n+ *\n+ * Typically, the `struct prefix_item` information will be a field in the\n+ * actual item struct; For this reason, the `items` parameter is specified\n+ * as an array of pointers to the items.\n+ *\n+ * The `min_length`/`max_length` parameters define what length the unique\n+ * prefixes should have.\n+ *\n+ * If no unique prefix could be found for a given item, its `prefix_length`\n+ * will be set to 0.\n+ */\n+void find_unique_prefixes(struct prefix_item **items, size_t nr,\n+\t\t\t  int min_length, int max_length);\n+\n+#endif\n-- \n2.23.0.331.g4e51dcdf11\n\n"},{"id":"381611","messageId":"nycvar.QRO.7.76.6.1908302209220.46@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"20190828163419.30620-1-szeder.dev@gmail.com","subject":"Re: [PATCH] [PoC] A simpler find_unique_prefixes() implementation","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-08-30T20:12:41Z","receivedAt":"2019-08-30T20:12:57Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Gábor,\n\nOn Wed, 28 Aug 2019, SZEDER Gábor wrote:\n\n> Implement find_unique_prefixes() by sorting the prefix items\n> lexicographically and then looking for unique prefixes in adjacent\n> pairs.  It's definitely shorter than the current hashmap-based\n> implementation (101 vs 149 lines), subjectively easier to follow, and\n> hopefully less buggy (some of the new elements in the test trigger\n> failure with the hashmap).\n>\n> TODO: deal multi-byte caracter sequences, make the test tool more\n>       developer-friendly\n\nSorry for my late response, I am still trying to catch up with an\never-growing inbox after trying to pretend to be on vacation.\n\nYour argument is a strong one, and I will try to re-roll, folding the\nfunctionality to find the unique prefixes into `add-interactive.c` while\ntrying to reuse the already-sorted list for the lookup by prefix.\n\nAs to the multi-byte UTF-8 characters: you're right, that code has a\nbug. The intention was to require only single-byte characters in the\nprefix, for simplicity.\n\nThanks,\nDscho\n"},{"id":"385379","messageId":"ff59d2d0b3b8b591a806ef71b4bcfd350000b06e.1572869729.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v5.git.1572869729.gitgitgadget@gmail.com","subject":"[PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-04T12:15:21Z","receivedAt":"2019-11-04T12:15:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis is hardly the first conversion of a Git command that is implemented\nas a script to a built-in. So far, the most successful strategy for such\nconversions has been to add a built-in helper and call that for more and\nmore functionality from the script, as more and more parts are\nconverted.\n\nWith the interactive add, we choose a different strategy. The sole\nreason for this is that on Windows (where such a conversion has the most\nbenefits in terms of speed and robustness) we face the very specific\nproblem that a `system()` call in Perl seems to close `stdin` in the\nparent process when the spawned process consumes even one character from\n`stdin`. And that just does not work for us here, as it would stop the\nmain loop as soon as any interactive command was performed by the\nhelper. Which is almost all of the commands in `git add -i`.\n\nIt is almost as if Perl told us once again that it does not want us to\nuse it on Windows.\n\nInstead, we follow the opposite route where we start with a bare-bones\nversion of the built-in interactive add, guarded by the new\n`add.interactive.useBuiltin` config variable, and then add more and more\nfunctionality to it, until it is feature complete.\n\nAt this point, the built-in version of `git add -i` only states that it\ncannot do anything yet ;-)\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config/add.txt |  5 +++++\n Makefile                     |  1 +\n add-interactive.c            |  7 +++++++\n add-interactive.h            |  8 ++++++++\n builtin/add.c                | 10 ++++++++++\n t/README                     |  4 ++++\n 6 files changed, 35 insertions(+)\n create mode 100644 add-interactive.c\n create mode 100644 add-interactive.h\n\ndiff --git a/Documentation/config/add.txt b/Documentation/config/add.txt\nindex 4d753f006e..c9f748f81c 100644\n--- a/Documentation/config/add.txt\n+++ b/Documentation/config/add.txt\n@@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::\n \toption of linkgit:git-add[1].  `add.ignore-errors` is deprecated,\n \tas it does not follow the usual naming convention for configuration\n \tvariables.\n+\n+add.interactive.useBuiltin::\n+\t[EXPERIMENTAL] Set to `true` to use the experimental built-in\n+\timplementation of the interactive version of linkgit:git-add[1]\n+\tinstead of the Perl script version. Is `false` by default.\ndiff --git a/Makefile b/Makefile\nindex 58b92af54b..6c4a1e0ee5 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -823,6 +823,7 @@ LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentat\n \t-name '*.h' -print)))\n \n LIB_OBJS += abspath.o\n+LIB_OBJS += add-interactive.o\n LIB_OBJS += advice.o\n LIB_OBJS += alias.o\n LIB_OBJS += alloc.o\ndiff --git a/add-interactive.c b/add-interactive.c\nnew file mode 100644\nindex 0000000000..482e458dc6\n--- /dev/null\n+++ b/add-interactive.c\n@@ -0,0 +1,7 @@\n+#include \"cache.h\"\n+#include \"add-interactive.h\"\n+\n+int run_add_i(struct repository *r, const struct pathspec *ps)\n+{\n+\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n+}\ndiff --git a/add-interactive.h b/add-interactive.h\nnew file mode 100644\nindex 0000000000..7043b8741d\n--- /dev/null\n+++ b/add-interactive.h\n@@ -0,0 +1,8 @@\n+#ifndef ADD_INTERACTIVE_H\n+#define ADD_INTERACTIVE_H\n+\n+struct repository;\n+struct pathspec;\n+int run_add_i(struct repository *r, const struct pathspec *ps);\n+\n+#endif\ndiff --git a/builtin/add.c b/builtin/add.c\nindex dd18e5c9b6..4f625691b5 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -20,6 +20,7 @@\n #include \"bulk-checkin.h\"\n #include \"argv-array.h\"\n #include \"submodule.h\"\n+#include \"add-interactive.h\"\n \n static const char * const builtin_add_usage[] = {\n \tN_(\"git add [<options>] [--] <pathspec>...\"),\n@@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,\n {\n \tint status, i;\n \tstruct argv_array argv = ARGV_ARRAY_INIT;\n+\tint use_builtin_add_i =\n+\t\tgit_env_bool(\"GIT_TEST_ADD_I_USE_BUILTIN\", -1);\n+\tif (use_builtin_add_i < 0)\n+\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n+\t\t\t\t    &use_builtin_add_i);\n+\n+\tif (use_builtin_add_i == 1 && !patch_mode)\n+\t\treturn !!run_add_i(the_repository, pathspec);\n \n \targv_array_push(&argv, \"add--interactive\");\n \tif (patch_mode)\n@@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb)\n \t\tignore_add_errors = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \ndiff --git a/t/README b/t/README\nindex 60d5b77bcc..5132ec83f8 100644\n--- a/t/README\n+++ b/t/README\n@@ -397,6 +397,10 @@ GIT_TEST_STASH_USE_BUILTIN=<boolean>, when false, disables the\n built-in version of git-stash. See 'stash.useBuiltin' in\n git-config(1).\n \n+GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the\n+built-in version of git add -i. See 'add.interactive.useBuiltin' in\n+git-config(1).\n+\n GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading\n of the index for the whole test suite by bypassing the default number of\n cache entries and thread minimums. Setting this to 1 will make the\n-- \ngitgitgadget\n\n"},{"id":"385380","messageId":"e405f071100e1f538207819233565b5845e70708.1572869730.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v5.git.1572869729.gitgitgadget@gmail.com","subject":"[PATCH v5 4/9] built-in add -i: color the header in the `status` command","fromName":"Slavica Đukić via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-04T12:15:24Z","receivedAt":"2019-11-04T12:15:38Z","isPatch":true,"sender":{"key":"name:Slavica Đukić","avatar":null},"body":"From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>\n\nFor simplicity, we only implemented the `status` command without colors.\nThis patch starts adding color, matching what the Perl script\n`git-add--interactive.perl` does.\n\nOriginal-Patch-By: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Đukić <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 41 +++++++++++++++++++++++++++++++++++++----\n 1 file changed, 37 insertions(+), 4 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex aa35184d87..174e07ce83 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -1,5 +1,7 @@\n #include \"cache.h\"\n #include \"add-interactive.h\"\n+#include \"color.h\"\n+#include \"config.h\"\n #include \"diffcore.h\"\n #include \"revision.h\"\n #include \"refs.h\"\n@@ -7,11 +9,40 @@\n \n struct add_i_state {\n \tstruct repository *r;\n+\tint use_color;\n+\tchar header_color[COLOR_MAXLEN];\n };\n \n+static void init_color(struct repository *r, struct add_i_state *s,\n+\t\t       const char *slot_name, char *dst,\n+\t\t       const char *default_color)\n+{\n+\tchar *key = xstrfmt(\"color.interactive.%s\", slot_name);\n+\tconst char *value;\n+\n+\tif (!s->use_color)\n+\t\tdst[0] = '\\0';\n+\telse if (repo_config_get_value(r, key, &value) ||\n+\t\t color_parse(value, dst))\n+\t\tstrlcpy(dst, default_color, COLOR_MAXLEN);\n+\n+\tfree(key);\n+}\n+\n static void init_add_i_state(struct add_i_state *s, struct repository *r)\n {\n-       s->r = r;\n+\tconst char *value;\n+\n+\ts->r = r;\n+\n+\tif (repo_config_get_value(r, \"color.interactive\", &value))\n+\t\ts->use_color = -1;\n+\telse\n+\t\ts->use_color =\n+\t\t\tgit_config_colorbool(\"color.interactive\", value);\n+\ts->use_color = want_color(s->use_color);\n+\n+\tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n }\n \n struct list_options {\n@@ -20,7 +51,8 @@ struct list_options {\n \tvoid *print_item_data;\n };\n \n-static void list(struct string_list *list, struct list_options *opts)\n+static void list(struct add_i_state *s, struct string_list *list,\n+\t\t struct list_options *opts)\n {\n \tint i;\n \n@@ -28,7 +60,8 @@ static void list(struct string_list *list, struct list_options *opts)\n \t\treturn;\n \n \tif (opts->header)\n-\t\tprintf(\"%s\\n\", opts->header);\n+\t\tcolor_fprintf_ln(stdout, s->header_color,\n+\t\t\t\t \"%s\", opts->header);\n \n \tfor (i = 0; i < list->nr; i++) {\n \t\topts->print_item(i, list->items + i, opts->print_item_data);\n@@ -213,7 +246,7 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \tif (get_modified_files(s->r, files, ps) < 0)\n \t\treturn -1;\n \n-\tlist(files, opts);\n+\tlist(s, files, opts);\n \tputchar('\\n');\n \n \treturn 0;\n-- \ngitgitgadget\n\n"},{"id":"385381","messageId":"6aaa0de4f4564b1d8da0691851dac0d22f87e565.1572869730.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v5.git.1572869729.gitgitgadget@gmail.com","subject":"[PATCH v5 3/9] built-in add -i: implement the `status` command","fromName":"Daniel Ferreira via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-04T12:15:23Z","receivedAt":"2019-11-04T12:15:38Z","isPatch":true,"sender":{"key":"bnmvco@gmail.com","avatar":"https://avatars.githubusercontent.com/u/735397?v=4"},"body":"From: Daniel Ferreira <bnmvco@gmail.com>\n\nThis implements the `status` command of `git add -i`. The data\nstructures introduced in this commit will be extended later, as needed.\n\nAt this point, we re-implement only part of the `list_and_choose()`\nfunction of the Perl script `git-add--interactive.perl` and call it\n`list()`. It does not yet color anything, or do columns, or allow user\ninput.\n\nOver the course of the next commits, we will introduce a\n`list_and_choose()` function that uses `list()` to display the list of\noptions and let the user choose one or more of the displayed items. This\nwill be used to implement the main loop of the built-in `git add -i`, at\nwhich point the new `status` command can actually be used.\n\nSigned-off-by: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Đukić <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 251 +++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 250 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 482e458dc6..aa35184d87 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -1,7 +1,256 @@\n #include \"cache.h\"\n #include \"add-interactive.h\"\n+#include \"diffcore.h\"\n+#include \"revision.h\"\n+#include \"refs.h\"\n+#include \"string-list.h\"\n+\n+struct add_i_state {\n+\tstruct repository *r;\n+};\n+\n+static void init_add_i_state(struct add_i_state *s, struct repository *r)\n+{\n+       s->r = r;\n+}\n+\n+struct list_options {\n+\tconst char *header;\n+\tvoid (*print_item)(int i, struct string_list_item *item, void *print_item_data);\n+\tvoid *print_item_data;\n+};\n+\n+static void list(struct string_list *list, struct list_options *opts)\n+{\n+\tint i;\n+\n+\tif (!list->nr)\n+\t\treturn;\n+\n+\tif (opts->header)\n+\t\tprintf(\"%s\\n\", opts->header);\n+\n+\tfor (i = 0; i < list->nr; i++) {\n+\t\topts->print_item(i, list->items + i, opts->print_item_data);\n+\t\tputchar('\\n');\n+\t}\n+}\n+\n+struct adddel {\n+\tuintmax_t add, del;\n+\tunsigned seen:1, binary:1;\n+};\n+\n+struct file_item {\n+\tstruct adddel index, worktree;\n+};\n+\n+static void add_file_item(struct string_list *files, const char *name)\n+{\n+\tstruct file_item *item = xcalloc(sizeof(*item), 1);\n+\n+\tstring_list_append(files, name)->util = item;\n+}\n+\n+struct pathname_entry {\n+\tstruct hashmap_entry ent;\n+\tconst char *name;\n+\tstruct file_item *item;\n+};\n+\n+static int pathname_entry_cmp(const void *unused_cmp_data,\n+\t\t\t      const struct hashmap_entry *he1,\n+\t\t\t      const struct hashmap_entry *he2,\n+\t\t\t      const void *name)\n+{\n+\tconst struct pathname_entry *e1 =\n+\t\tcontainer_of(he1, const struct pathname_entry, ent);\n+\tconst struct pathname_entry *e2 =\n+\t\tcontainer_of(he2, const struct pathname_entry, ent);\n+\n+\treturn strcmp(e1->name, name ? (const char *)name : e2->name);\n+}\n+\n+struct collection_status {\n+\tenum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase;\n+\n+\tconst char *reference;\n+\n+\tstruct string_list *files;\n+\tstruct hashmap file_map;\n+};\n+\n+static void collect_changes_cb(struct diff_queue_struct *q,\n+\t\t\t       struct diff_options *options,\n+\t\t\t       void *data)\n+{\n+\tstruct collection_status *s = data;\n+\tstruct diffstat_t stat = { 0 };\n+\tint i;\n+\n+\tif (!q->nr)\n+\t\treturn;\n+\n+\tcompute_diffstat(options, &stat, q);\n+\n+\tfor (i = 0; i < stat.nr; i++) {\n+\t\tconst char *name = stat.files[i]->name;\n+\t\tint hash = strhash(name);\n+\t\tstruct pathname_entry *entry;\n+\t\tstruct file_item *file_item;\n+\t\tstruct adddel *adddel;\n+\n+\t\tentry = hashmap_get_entry_from_hash(&s->file_map, hash, name,\n+\t\t\t\t\t\t    struct pathname_entry, ent);\n+\t\tif (!entry) {\n+\t\t\tadd_file_item(s->files, name);\n+\n+\t\t\tentry = xcalloc(sizeof(*entry), 1);\n+\t\t\thashmap_entry_init(&entry->ent, hash);\n+\t\t\tentry->name = s->files->items[s->files->nr - 1].string;\n+\t\t\tentry->item = s->files->items[s->files->nr - 1].util;\n+\t\t\thashmap_add(&s->file_map, &entry->ent);\n+\t\t}\n+\n+\t\tfile_item = entry->item;\n+\t\tadddel = s->phase == FROM_INDEX ?\n+\t\t\t&file_item->index : &file_item->worktree;\n+\t\tadddel->seen = 1;\n+\t\tadddel->add = stat.files[i]->added;\n+\t\tadddel->del = stat.files[i]->deleted;\n+\t\tif (stat.files[i]->is_binary)\n+\t\t\tadddel->binary = 1;\n+\t}\n+\tfree_diffstat_info(&stat);\n+}\n+\n+static int get_modified_files(struct repository *r, struct string_list *files,\n+\t\t\t      const struct pathspec *ps)\n+{\n+\tstruct object_id head_oid;\n+\tint is_initial = !resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING,\n+\t\t\t\t\t     &head_oid, NULL);\n+\tstruct collection_status s = { FROM_WORKTREE };\n+\n+\tif (discard_index(r->index) < 0 ||\n+\t    repo_read_index_preload(r, ps, 0) < 0)\n+\t\treturn error(_(\"could not read index\"));\n+\n+\tstring_list_clear(files, 1);\n+\ts.files = files;\n+\thashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);\n+\n+\tfor (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {\n+\t\tstruct rev_info rev;\n+\t\tstruct setup_revision_opt opt = { 0 };\n+\n+\t\topt.def = is_initial ?\n+\t\t\tempty_tree_oid_hex() : oid_to_hex(&head_oid);\n+\n+\t\tinit_revisions(&rev, NULL);\n+\t\tsetup_revisions(0, NULL, &rev, &opt);\n+\n+\t\trev.diffopt.output_format = DIFF_FORMAT_CALLBACK;\n+\t\trev.diffopt.format_callback = collect_changes_cb;\n+\t\trev.diffopt.format_callback_data = &s;\n+\n+\t\tif (ps)\n+\t\t\tcopy_pathspec(&rev.prune_data, ps);\n+\n+\t\tif (s.phase == FROM_INDEX)\n+\t\t\trun_diff_index(&rev, 1);\n+\t\telse {\n+\t\t\trev.diffopt.flags.ignore_dirty_submodules = 1;\n+\t\t\trun_diff_files(&rev, 0);\n+\t\t}\n+\t}\n+\thashmap_free_entries(&s.file_map, struct pathname_entry, ent);\n+\n+\t/* While the diffs are ordered already, we ran *two* diffs... */\n+\tstring_list_sort(files);\n+\n+\treturn 0;\n+}\n+\n+static void render_adddel(struct strbuf *buf,\n+\t\t\t\tstruct adddel *ad, const char *no_changes)\n+{\n+\tif (ad->binary)\n+\t\tstrbuf_addstr(buf, _(\"binary\"));\n+\telse if (ad->seen)\n+\t\tstrbuf_addf(buf, \"+%\"PRIuMAX\"/-%\"PRIuMAX,\n+\t\t\t    (uintmax_t)ad->add, (uintmax_t)ad->del);\n+\telse\n+\t\tstrbuf_addstr(buf, no_changes);\n+}\n+\n+struct print_file_item_data {\n+\tconst char *modified_fmt;\n+\tstruct strbuf buf, index, worktree;\n+};\n+\n+static void print_file_item(int i, struct string_list_item *item,\n+\t\t\t    void *print_file_item_data)\n+{\n+\tstruct file_item *c = item->util;\n+\tstruct print_file_item_data *d = print_file_item_data;\n+\n+\tstrbuf_reset(&d->index);\n+\tstrbuf_reset(&d->worktree);\n+\tstrbuf_reset(&d->buf);\n+\n+\trender_adddel(&d->worktree, &c->worktree, _(\"nothing\"));\n+\trender_adddel(&d->index, &c->index, _(\"unchanged\"));\n+\tstrbuf_addf(&d->buf, d->modified_fmt,\n+\t\t    d->index.buf, d->worktree.buf, item->string);\n+\n+\tprintf(\" %2d: %s\", i + 1, d->buf.buf);\n+}\n+\n+static int run_status(struct add_i_state *s, const struct pathspec *ps,\n+\t\t      struct string_list *files, struct list_options *opts)\n+{\n+\tif (get_modified_files(s->r, files, ps) < 0)\n+\t\treturn -1;\n+\n+\tlist(files, opts);\n+\tputchar('\\n');\n+\n+\treturn 0;\n+}\n \n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n-\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n+\tstruct add_i_state s = { NULL };\n+\tstruct print_file_item_data print_file_item_data = {\n+\t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n+\t};\n+\tstruct list_options opts = {\n+\t\tNULL, print_file_item, &print_file_item_data\n+\t};\n+\tstruct strbuf header = STRBUF_INIT;\n+\tstruct string_list files = STRING_LIST_INIT_DUP;\n+\tint res = 0;\n+\n+\tinit_add_i_state(&s, r);\n+\tstrbuf_addstr(&header, \"      \");\n+\tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n+\t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n+\topts.header = header.buf;\n+\n+\tif (discard_index(r->index) < 0 ||\n+\t    repo_read_index(r) < 0 ||\n+\t    repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,\n+\t\t\t\t\t NULL, NULL, NULL) < 0)\n+\t\twarning(_(\"could not refresh index\"));\n+\n+\tres = run_status(&s, ps, &files, &opts);\n+\n+\tstring_list_clear(&files, 1);\n+\tstrbuf_release(&print_file_item_data.buf);\n+\tstrbuf_release(&print_file_item_data.index);\n+\tstrbuf_release(&print_file_item_data.worktree);\n+\tstrbuf_release(&header);\n+\n+\treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"385382","messageId":"2fc8cc354690841cd8496d9a7660faf572580aef.1572869730.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v5.git.1572869729.gitgitgadget@gmail.com","subject":"[PATCH v5 2/9] diff: export diffstat interface","fromName":"Daniel Ferreira via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-04T12:15:22Z","receivedAt":"2019-11-04T12:15:39Z","isPatch":true,"sender":{"key":"bnmvco@gmail.com","avatar":"https://avatars.githubusercontent.com/u/735397?v=4"},"body":"From: Daniel Ferreira <bnmvco@gmail.com>\n\nMake the diffstat interface (namely, the diffstat_t struct and\ncompute_diffstat) no longer be internal to diff.c and allow it to be used\nby other parts of git.\n\nThis is helpful for code that may want to easily extract information\nfrom files using the diff machinery, while flushing it differently from\nhow the show_* functions used by diff_flush() do it. One example is the\nbuiltin implementation of git-add--interactive's status.\n\nSigned-off-by: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Đukić <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 39 ++++++++++++++++-----------------------\n diff.h | 20 ++++++++++++++++++++\n 2 files changed, 36 insertions(+), 23 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex afe4400a60..5703a9b78f 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -2495,22 +2495,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)\n \t}\n }\n \n-struct diffstat_t {\n-\tint nr;\n-\tint alloc;\n-\tstruct diffstat_file {\n-\t\tchar *from_name;\n-\t\tchar *name;\n-\t\tchar *print_name;\n-\t\tconst char *comments;\n-\t\tunsigned is_unmerged:1;\n-\t\tunsigned is_binary:1;\n-\t\tunsigned is_renamed:1;\n-\t\tunsigned is_interesting:1;\n-\t\tuintmax_t added, deleted;\n-\t} **files;\n-};\n-\n static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,\n \t\t\t\t\t  const char *name_a,\n \t\t\t\t\t  const char *name_b)\n@@ -3157,7 +3141,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o\n \tgather_dirstat(options, &dir, changed, \"\", 0);\n }\n \n-static void free_diffstat_info(struct diffstat_t *diffstat)\n+void free_diffstat_info(struct diffstat_t *diffstat)\n {\n \tint i;\n \tfor (i = 0; i < diffstat->nr; i++) {\n@@ -6283,12 +6267,7 @@ void diff_flush(struct diff_options *options)\n \t    dirstat_by_line) {\n \t\tstruct diffstat_t diffstat;\n \n-\t\tmemset(&diffstat, 0, sizeof(struct diffstat_t));\n-\t\tfor (i = 0; i < q->nr; i++) {\n-\t\t\tstruct diff_filepair *p = q->queue[i];\n-\t\t\tif (check_pair_status(p))\n-\t\t\t\tdiff_flush_stat(p, options, &diffstat);\n-\t\t}\n+\t\tcompute_diffstat(options, &diffstat, q);\n \t\tif (output_format & DIFF_FORMAT_NUMSTAT)\n \t\t\tshow_numstat(&diffstat, options);\n \t\tif (output_format & DIFF_FORMAT_DIFFSTAT)\n@@ -6621,6 +6600,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)\n \treturn ignored;\n }\n \n+void compute_diffstat(struct diff_options *options,\n+\t\t      struct diffstat_t *diffstat,\n+\t\t      struct diff_queue_struct *q)\n+{\n+\tint i;\n+\n+\tmemset(diffstat, 0, sizeof(struct diffstat_t));\n+\tfor (i = 0; i < q->nr; i++) {\n+\t\tstruct diff_filepair *p = q->queue[i];\n+\t\tif (check_pair_status(p))\n+\t\t\tdiff_flush_stat(p, options, diffstat);\n+\t}\n+}\n+\n void diff_addremove(struct diff_options *options,\n \t\t    int addremove, unsigned mode,\n \t\t    const struct object_id *oid,\ndiff --git a/diff.h b/diff.h\nindex 7f8f024feb..d986ddc3b5 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -245,6 +245,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);\n void diff_emit_submodule_pipethrough(struct diff_options *o,\n \t\t\t\t     const char *line, int len);\n \n+struct diffstat_t {\n+\tint nr;\n+\tint alloc;\n+\tstruct diffstat_file {\n+\t\tchar *from_name;\n+\t\tchar *name;\n+\t\tchar *print_name;\n+\t\tconst char *comments;\n+\t\tunsigned is_unmerged:1;\n+\t\tunsigned is_binary:1;\n+\t\tunsigned is_renamed:1;\n+\t\tunsigned is_interesting:1;\n+\t\tuintmax_t added, deleted;\n+\t} **files;\n+};\n+\n enum color_diff {\n \tDIFF_RESET = 0,\n \tDIFF_CONTEXT = 1,\n@@ -334,6 +350,10 @@ void diff_change(struct diff_options *,\n \n struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);\n \n+void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,\n+\t\t      struct diff_queue_struct *q);\n+void free_diffstat_info(struct diffstat_t *diffstat);\n+\n #define DIFF_SETUP_REVERSE      \t1\n #define DIFF_SETUP_USE_SIZE_CACHE\t4\n \n-- \ngitgitgadget\n\n"},{"id":"385383","messageId":"pull.170.v5.git.1572869729.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v5 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far)","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-04T12:15:20Z","receivedAt":"2019-11-04T12:15:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is the first leg on the long journey to a fully built-in git add -i \n(next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3\n[https://github.com/gitgitgadget/git/pull/172], 4\n[https://github.com/gitgitgadget/git/pull/173], 5\n[https://github.com/gitgitgadget/git/pull/174], and 6\n[https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are\nnot necessarily up to date, and will be re-targeted to the appropriate\nbranches in https://github.com/gitster/git as soon as Junio picks them up.\n\nThis here patch series reflects the part that was submitted a couple of\ntimes (see https://github.com/gitgitgadget/git/pull/103) during the\nOutreachy project by Slavica Ðukic that continued the journey based on an\ninitial patch series by Daniel Ferreira.\n\nIt only implements the status and the help part, in the interest of making\nthe review remotely more reviewable.\n\nAs I am a heavy user of git add -p myself and use a patched version for\nseveral months already (it is so nice to not suffer over one second startup\nuntil the MSYS2 Perl finally shows me anything, instead it feels\ninstantaneous), I integrated these patch series into Git for Windows\nalready, as an opt-in feature guarded by the config variable \nadd.interactive.useBuiltin (and Git for Windows' installer knows to detect\nthis version and offer the option in the graphical user interface).\n\nChanges since v4:\n\n * Rebased onto current master to make use of Thomas Gummerer's \n   repo_refresh_and_write_index() as well as to avoid merge conflicts with\n   Eric Wong's work on struct hashmap.\n * Instead of rolling a dedicated data struct to simulate a Trie, we now use \n   string-list extensively (an unsorted copy and a sorted one, the latter to\n   determine unique prefixes). This had massive ramifications on the rest of\n   the patches... For example, the struct command_item structure no longer\n   contains the name field, but is intended to be a util in a string_list.\n * Changed the commit messages and author lines to reflect Slavica's name\n   correctly.\n * Touched up a couple commit messages.\n\nChanges since v3:\n\n * Rebased to v2.23.0 to reduce friction.\n * free_diffstat_info() is now made public as well, and used, to avoid a\n   memory leak.\n * Prepared the patches for ew/hashmap (which is strict about the hashmap\n   entries' type in hashmap_entry_init() and friends).\n * The private data types have been moved from prefix-map.h to prefix-map.c.\n * A lot of int types were converted to more appropriate size_t in \n   prefix-map.c.\n * A misleading parameter name list was renamed to the correct array.\n * The code comment above find_unique_prefixes() was (hopefully) improved.\n * The run_help() function's signature now reflects that most of the\n   parameters are actually unused.\n\nChanges since v2:\n\n * Rebased to master to avoid merge conflicts.\n * Renumbered the prefix-map test to avoid conflicts with two patch series\n   that are currently in-flight in pu.\n\nChanges since v1:\n\n * The config machinery was reworked completely, to not use a callback to \n   git_config(), but instead to query the config via the repo_config_get_*() \n   functions. This also prevents a future \"Huh???\" moment: the internal add\n   --interactive API accepts a parameter of type struct repository *r, but\n   the previous configuration did not use that to query the config (and\n   could in the future be a repository other than the_repository).\n   \n   \n * As a consequence, the color sequences are no longer stored in file-local\n   variables, but passed around via a struct.\n   \n   \n * Instead of using the magical constant -2 to quit the main loop, it is now\n   defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is\n   defined as -1 and used where appropriate).\n   \n   \n * Improved the add_prefix_item() function by avoiding buffer overruns, not\n   reusing the struct that is used for lookup also for adding the new item,\n   and by strengthening the bug check.\n\nDaniel Ferreira (2):\n  diff: export diffstat interface\n  built-in add -i: implement the `status` command\n\nJohannes Schindelin (4):\n  Start to implement a built-in version of `git add --interactive`\n  built-in add -i: implement the main loop\n  built-in add -i: show unique prefixes of the commands\n  built-in add -i: support `?` (prompt help)\n\nSlavica Đukić (3):\n  built-in add -i: color the header in the `status` command\n  built-in add -i: use color in the main loop\n  built-in add -i: implement the `help` command\n\n Documentation/config/add.txt |   5 +\n Makefile                     |   1 +\n add-interactive.c            | 650 +++++++++++++++++++++++++++++++++++\n add-interactive.h            |   8 +\n builtin/add.c                |  10 +\n diff.c                       |  39 +--\n diff.h                       |  20 ++\n t/README                     |   4 +\n t/t3701-add-interactive.sh   |  25 ++\n 9 files changed, 739 insertions(+), 23 deletions(-)\n create mode 100644 add-interactive.c\n create mode 100644 add-interactive.h\n\n\nbase-commit: 566a1439f6f56c2171b8853ddbca0ad3f5098770\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/170\n\nRange-diff vs v4:\n\n  1:  ad8752eca7 !  1:  ff59d2d0b3 Start to implement a built-in version of `git add --interactive`\n     @@ -47,7 +47,7 @@\n       --- a/Makefile\n       +++ b/Makefile\n      @@\n     - \t-name '*.h' -print))\n     + \t-name '*.h' -print)))\n       \n       LIB_OBJS += abspath.o\n      +LIB_OBJS += add-interactive.o\n     @@ -125,7 +125,7 @@\n       git-config(1).\n       \n      +GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the\n     -+builtin version of git add -i. See 'add.interactive.useBuiltin' in\n     ++built-in version of git add -i. See 'add.interactive.useBuiltin' in\n      +git-config(1).\n      +\n       GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading\n  2:  38cc04c1d9 !  2:  2fc8cc3546 diff: export diffstat interface\n     @@ -12,7 +12,7 @@\n          builtin implementation of git-add--interactive's status.\n      \n          Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>\n     -    Signed-off-by: Slavica Djukic <slawica92@hotmail.com>\n     +    Signed-off-by: Slavica Đukić <slawica92@hotmail.com>\n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n       diff --git a/diff.c b/diff.c\n  3:  ee3e40293c !  3:  6aaa0de4f4 built-in add -i: implement the `status` command\n     @@ -3,7 +3,7 @@\n          built-in add -i: implement the `status` command\n      \n          This implements the `status` command of `git add -i`. The data\n     -    structures introduced in this commit will be extended as needed later.\n     +    structures introduced in this commit will be extended later, as needed.\n      \n          At this point, we re-implement only part of the `list_and_choose()`\n          function of the Perl script `git-add--interactive.perl` and call it\n     @@ -16,12 +16,8 @@\n          will be used to implement the main loop of the built-in `git add -i`, at\n          which point the new `status` command can actually be used.\n      \n     -    Note that we pass the list of items as a `struct item **` as opposed to\n     -    a `struct item *`, to allow for the actual items to contain much more\n     -    information than merely the name.\n     -\n          Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>\n     -    Signed-off-by: Slavica Djukic <slawica92@hotmail.com>\n     +    Signed-off-by: Slavica Đukić <slawica92@hotmail.com>\n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n       diff --git a/add-interactive.c b/add-interactive.c\n     @@ -33,29 +29,35 @@\n      +#include \"diffcore.h\"\n      +#include \"revision.h\"\n      +#include \"refs.h\"\n     ++#include \"string-list.h\"\n      +\n     -+struct item {\n     -+\tconst char *name;\n     ++struct add_i_state {\n     ++\tstruct repository *r;\n      +};\n      +\n     ++static void init_add_i_state(struct add_i_state *s, struct repository *r)\n     ++{\n     ++       s->r = r;\n     ++}\n     ++\n      +struct list_options {\n      +\tconst char *header;\n     -+\tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n     ++\tvoid (*print_item)(int i, struct string_list_item *item, void *print_item_data);\n      +\tvoid *print_item_data;\n      +};\n      +\n     -+static void list(struct item **list, size_t nr, struct list_options *opts)\n     ++static void list(struct string_list *list, struct list_options *opts)\n      +{\n      +\tint i;\n      +\n     -+\tif (!nr)\n     ++\tif (!list->nr)\n      +\t\treturn;\n      +\n      +\tif (opts->header)\n      +\t\tprintf(\"%s\\n\", opts->header);\n      +\n     -+\tfor (i = 0; i < nr; i++) {\n     -+\t\topts->print_item(i, list[i], opts->print_item_data);\n     ++\tfor (i = 0; i < list->nr; i++) {\n     ++\t\topts->print_item(i, list->items + i, opts->print_item_data);\n      +\t\tputchar('\\n');\n      +\t}\n      +}\n     @@ -65,62 +67,34 @@\n      +\tunsigned seen:1, binary:1;\n      +};\n      +\n     -+struct file_list {\n     -+\tstruct file_item {\n     -+\t\tstruct item item;\n     -+\t\tstruct adddel index, worktree;\n     -+\t} **file;\n     -+\tsize_t nr, alloc;\n     ++struct file_item {\n     ++\tstruct adddel index, worktree;\n      +};\n      +\n     -+static void add_file_item(struct file_list *list, const char *name)\n     ++static void add_file_item(struct string_list *files, const char *name)\n      +{\n     -+\tstruct file_item *item;\n     -+\n     -+\tFLEXPTR_ALLOC_STR(item, item.name, name);\n     ++\tstruct file_item *item = xcalloc(sizeof(*item), 1);\n      +\n     -+\tALLOC_GROW(list->file, list->nr + 1, list->alloc);\n     -+\tlist->file[list->nr++] = item;\n     -+}\n     -+\n     -+static void reset_file_list(struct file_list *list)\n     -+{\n     -+\tsize_t i;\n     -+\n     -+\tfor (i = 0; i < list->nr; i++)\n     -+\t\tfree(list->file[i]);\n     -+\tlist->nr = 0;\n     -+}\n     -+\n     -+static void release_file_list(struct file_list *list)\n     -+{\n     -+\treset_file_list(list);\n     -+\tFREE_AND_NULL(list->file);\n     -+\tlist->alloc = 0;\n     -+}\n     -+\n     -+static int file_item_cmp(const void *a, const void *b)\n     -+{\n     -+\tconst struct file_item * const *f1 = a;\n     -+\tconst struct file_item * const *f2 = b;\n     -+\n     -+\treturn strcmp((*f1)->item.name, (*f2)->item.name);\n     ++\tstring_list_append(files, name)->util = item;\n      +}\n      +\n      +struct pathname_entry {\n      +\tstruct hashmap_entry ent;\n     -+\tsize_t index;\n     -+\tchar pathname[FLEX_ARRAY];\n     ++\tconst char *name;\n     ++\tstruct file_item *item;\n      +};\n      +\n      +static int pathname_entry_cmp(const void *unused_cmp_data,\n     -+\t\t\t      const void *entry, const void *entry_or_key,\n     -+\t\t\t      const void *pathname)\n     ++\t\t\t      const struct hashmap_entry *he1,\n     ++\t\t\t      const struct hashmap_entry *he2,\n     ++\t\t\t      const void *name)\n      +{\n     -+\tconst struct pathname_entry *e1 = entry, *e2 = entry_or_key;\n     ++\tconst struct pathname_entry *e1 =\n     ++\t\tcontainer_of(he1, const struct pathname_entry, ent);\n     ++\tconst struct pathname_entry *e2 =\n     ++\t\tcontainer_of(he2, const struct pathname_entry, ent);\n      +\n     -+\treturn strcmp(e1->pathname,\n     -+\t\t      pathname ? (const char *)pathname : e2->pathname);\n     ++\treturn strcmp(e1->name, name ? (const char *)name : e2->name);\n      +}\n      +\n      +struct collection_status {\n     @@ -128,7 +102,7 @@\n      +\n      +\tconst char *reference;\n      +\n     -+\tstruct file_list *list;\n     ++\tstruct string_list *files;\n      +\tstruct hashmap file_map;\n      +};\n      +\n     @@ -149,24 +123,24 @@\n      +\t\tconst char *name = stat.files[i]->name;\n      +\t\tint hash = strhash(name);\n      +\t\tstruct pathname_entry *entry;\n     -+\t\tsize_t file_index;\n     -+\t\tstruct file_item *file;\n     ++\t\tstruct file_item *file_item;\n      +\t\tstruct adddel *adddel;\n      +\n     -+\t\tentry = hashmap_get_from_hash(&s->file_map, hash, name);\n     -+\t\tif (entry)\n     -+\t\t\tfile_index = entry->index;\n     -+\t\telse {\n     -+\t\t\tFLEX_ALLOC_STR(entry, pathname, name);\n     ++\t\tentry = hashmap_get_entry_from_hash(&s->file_map, hash, name,\n     ++\t\t\t\t\t\t    struct pathname_entry, ent);\n     ++\t\tif (!entry) {\n     ++\t\t\tadd_file_item(s->files, name);\n     ++\n     ++\t\t\tentry = xcalloc(sizeof(*entry), 1);\n      +\t\t\thashmap_entry_init(&entry->ent, hash);\n     -+\t\t\tentry->index = file_index = s->list->nr;\n     ++\t\t\tentry->name = s->files->items[s->files->nr - 1].string;\n     ++\t\t\tentry->item = s->files->items[s->files->nr - 1].util;\n      +\t\t\thashmap_add(&s->file_map, &entry->ent);\n     -+\n     -+\t\t\tadd_file_item(s->list, name);\n      +\t\t}\n     -+\t\tfile = s->list->file[file_index];\n      +\n     -+\t\tadddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;\n     ++\t\tfile_item = entry->item;\n     ++\t\tadddel = s->phase == FROM_INDEX ?\n     ++\t\t\t&file_item->index : &file_item->worktree;\n      +\t\tadddel->seen = 1;\n      +\t\tadddel->add = stat.files[i]->added;\n      +\t\tadddel->del = stat.files[i]->deleted;\n     @@ -176,7 +150,7 @@\n      +\tfree_diffstat_info(&stat);\n      +}\n      +\n     -+static int get_modified_files(struct repository *r, struct file_list *list,\n     ++static int get_modified_files(struct repository *r, struct string_list *files,\n      +\t\t\t      const struct pathspec *ps)\n      +{\n      +\tstruct object_id head_oid;\n     @@ -184,10 +158,12 @@\n      +\t\t\t\t\t     &head_oid, NULL);\n      +\tstruct collection_status s = { FROM_WORKTREE };\n      +\n     -+\tif (repo_read_index_preload(r, ps, 0) < 0)\n     ++\tif (discard_index(r->index) < 0 ||\n     ++\t    repo_read_index_preload(r, ps, 0) < 0)\n      +\t\treturn error(_(\"could not read index\"));\n      +\n     -+\ts.list = list;\n     ++\tstring_list_clear(files, 1);\n     ++\ts.files = files;\n      +\thashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);\n      +\n      +\tfor (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {\n     @@ -214,15 +190,15 @@\n      +\t\t\trun_diff_files(&rev, 0);\n      +\t\t}\n      +\t}\n     -+\thashmap_free(&s.file_map, 1);\n     ++\thashmap_free_entries(&s.file_map, struct pathname_entry, ent);\n      +\n      +\t/* While the diffs are ordered already, we ran *two* diffs... */\n     -+\tQSORT(list->file, list->nr, file_item_cmp);\n     ++\tstring_list_sort(files);\n      +\n      +\treturn 0;\n      +}\n      +\n     -+static void populate_wi_changes(struct strbuf *buf,\n     ++static void render_adddel(struct strbuf *buf,\n      +\t\t\t\tstruct adddel *ad, const char *no_changes)\n      +{\n      +\tif (ad->binary)\n     @@ -239,34 +215,31 @@\n      +\tstruct strbuf buf, index, worktree;\n      +};\n      +\n     -+static void print_file_item(int i, struct item *item,\n     ++static void print_file_item(int i, struct string_list_item *item,\n      +\t\t\t    void *print_file_item_data)\n      +{\n     -+\tstruct file_item *c = (struct file_item *)item;\n     ++\tstruct file_item *c = item->util;\n      +\tstruct print_file_item_data *d = print_file_item_data;\n      +\n      +\tstrbuf_reset(&d->index);\n      +\tstrbuf_reset(&d->worktree);\n      +\tstrbuf_reset(&d->buf);\n      +\n     -+\tpopulate_wi_changes(&d->worktree, &c->worktree, _(\"nothing\"));\n     -+\tpopulate_wi_changes(&d->index, &c->index, _(\"unchanged\"));\n     ++\trender_adddel(&d->worktree, &c->worktree, _(\"nothing\"));\n     ++\trender_adddel(&d->index, &c->index, _(\"unchanged\"));\n      +\tstrbuf_addf(&d->buf, d->modified_fmt,\n     -+\t\t    d->index.buf, d->worktree.buf, item->name);\n     ++\t\t    d->index.buf, d->worktree.buf, item->string);\n      +\n      +\tprintf(\" %2d: %s\", i + 1, d->buf.buf);\n      +}\n      +\n     -+static int run_status(struct repository *r, const struct pathspec *ps,\n     -+\t\t      struct file_list *files, struct list_options *opts)\n     ++static int run_status(struct add_i_state *s, const struct pathspec *ps,\n     ++\t\t      struct string_list *files, struct list_options *opts)\n      +{\n     -+\treset_file_list(files);\n     -+\n     -+\tif (get_modified_files(r, files, ps) < 0)\n     ++\tif (get_modified_files(s->r, files, ps) < 0)\n      +\t\treturn -1;\n      +\n     -+\tif (files->nr)\n     -+\t\tlist((struct item **)files->file, files->nr, opts);\n     ++\tlist(files, opts);\n      +\tputchar('\\n');\n      +\n      +\treturn 0;\n     @@ -275,6 +248,7 @@\n       int run_add_i(struct repository *r, const struct pathspec *ps)\n       {\n      -\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n     ++\tstruct add_i_state s = { NULL };\n      +\tstruct print_file_item_data print_file_item_data = {\n      +\t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n      +\t};\n     @@ -282,17 +256,24 @@\n      +\t\tNULL, print_file_item, &print_file_item_data\n      +\t};\n      +\tstruct strbuf header = STRBUF_INIT;\n     -+\tstruct file_list files = { NULL };\n     ++\tstruct string_list files = STRING_LIST_INIT_DUP;\n      +\tint res = 0;\n      +\n     ++\tinit_add_i_state(&s, r);\n      +\tstrbuf_addstr(&header, \"      \");\n      +\tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n      +\t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n      +\topts.header = header.buf;\n      +\n     -+\tres = run_status(r, ps, &files, &opts);\n     ++\tif (discard_index(r->index) < 0 ||\n     ++\t    repo_read_index(r) < 0 ||\n     ++\t    repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,\n     ++\t\t\t\t\t NULL, NULL, NULL) < 0)\n     ++\t\twarning(_(\"could not refresh index\"));\n     ++\n     ++\tres = run_status(&s, ps, &files, &opts);\n      +\n     -+\trelease_file_list(&files);\n     ++\tstring_list_clear(&files, 1);\n      +\tstrbuf_release(&print_file_item_data.buf);\n      +\tstrbuf_release(&print_file_item_data.index);\n      +\tstrbuf_release(&print_file_item_data.worktree);\n  4:  3c855d9fa5 <  -:  ---------- built-in add -i: refresh the index before running `status`\n  5:  24737a09f7 !  4:  e405f07110 built-in add -i: color the header in the `status` command\n     @@ -1,4 +1,4 @@\n     -Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n     +Author: Slavica Đukić <slawica92@hotmail.com>\n      \n          built-in add -i: color the header in the `status` command\n      \n     @@ -7,7 +7,7 @@\n          `git-add--interactive.perl` does.\n      \n          Original-Patch-By: Daniel Ferreira <bnmvco@gmail.com>\n     -    Signed-off-by: Slavica Djukic <slawica92@hotmail.com>\n     +    Signed-off-by: Slavica Đukić <slawica92@hotmail.com>\n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n       diff --git a/add-interactive.c b/add-interactive.c\n     @@ -21,13 +21,14 @@\n       #include \"diffcore.h\"\n       #include \"revision.h\"\n       #include \"refs.h\"\n     +@@\n       \n     -+struct add_i_state {\n     -+\tstruct repository *r;\n     + struct add_i_state {\n     + \tstruct repository *r;\n      +\tint use_color;\n      +\tchar header_color[COLOR_MAXLEN];\n     -+};\n     -+\n     + };\n     + \n      +static void init_color(struct repository *r, struct add_i_state *s,\n      +\t\t       const char *slot_name, char *dst,\n      +\t\t       const char *default_color)\n     @@ -44,8 +45,9 @@\n      +\tfree(key);\n      +}\n      +\n     -+static int init_add_i_state(struct repository *r, struct add_i_state *s)\n     -+{\n     + static void init_add_i_state(struct add_i_state *s, struct repository *r)\n     + {\n     +-       s->r = r;\n      +\tconst char *value;\n      +\n      +\ts->r = r;\n     @@ -58,20 +60,16 @@\n      +\ts->use_color = want_color(s->use_color);\n      +\n      +\tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n     -+\n     -+\treturn 0;\n     -+}\n     -+\n     - struct item {\n     - \tconst char *name;\n     - };\n     + }\n     + \n     + struct list_options {\n      @@\n       \tvoid *print_item_data;\n       };\n       \n     --static void list(struct item **list, size_t nr, struct list_options *opts)\n     -+static void list(struct item **list, size_t nr,\n     -+\t\t struct add_i_state *s, struct list_options *opts)\n     +-static void list(struct string_list *list, struct list_options *opts)\n     ++static void list(struct add_i_state *s, struct string_list *list,\n     ++\t\t struct list_options *opts)\n       {\n       \tint i;\n       \n     @@ -83,51 +81,14 @@\n      +\t\tcolor_fprintf_ln(stdout, s->header_color,\n      +\t\t\t\t \"%s\", opts->header);\n       \n     - \tfor (i = 0; i < nr; i++) {\n     - \t\topts->print_item(i, list[i], opts->print_item_data);\n     + \tfor (i = 0; i < list->nr; i++) {\n     + \t\topts->print_item(i, list->items + i, opts->print_item_data);\n      @@\n     - \tprintf(\" %2d: %s\", i + 1, d->buf.buf);\n     - }\n     - \n     --static int run_status(struct repository *r, const struct pathspec *ps,\n     -+static int run_status(struct add_i_state *s, const struct pathspec *ps,\n     - \t\t      struct file_list *files, struct list_options *opts)\n     - {\n     - \treset_file_list(files);\n     - \n     --\tif (get_modified_files(r, files, ps) < 0)\n     -+\tif (get_modified_files(s->r, files, ps) < 0)\n     + \tif (get_modified_files(s->r, files, ps) < 0)\n       \t\treturn -1;\n       \n     - \tif (files->nr)\n     --\t\tlist((struct item **)files->file, files->nr, opts);\n     -+\t\tlist((struct item **)files->file, files->nr, s, opts);\n     +-\tlist(files, opts);\n     ++\tlist(s, files, opts);\n       \tputchar('\\n');\n       \n       \treturn 0;\n     -@@\n     - \n     - int run_add_i(struct repository *r, const struct pathspec *ps)\n     - {\n     -+\tstruct add_i_state s = { NULL };\n     - \tstruct print_file_item_data print_file_item_data = {\n     - \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n     - \t};\n     -@@\n     - \tstruct file_list files = { NULL };\n     - \tint res = 0;\n     - \n     -+\tif (init_add_i_state(r, &s))\n     -+\t\treturn error(\"could not parse `add -i` config\");\n     -+\n     - \tstrbuf_addstr(&header, \"      \");\n     - \tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n     - \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n     - \topts.header = header.buf;\n     - \n     - \trepo_refresh_and_write_index(r, REFRESH_QUIET, 1);\n     --\tif (run_status(r, ps, &files, &opts) < 0)\n     -+\tif (run_status(&s, ps, &files, &opts) < 0)\n     - \t\tres = -1;\n     - \n     - \trelease_file_list(&files);\n  6:  ac67731cf1 !  5:  25590fbbbe built-in add -i: implement the main loop\n     @@ -45,26 +45,26 @@\n       --- a/add-interactive.c\n       +++ b/add-interactive.c\n      @@\n     - };\n     + }\n       \n       struct list_options {\n      +\tint columns;\n       \tconst char *header;\n     - \tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n     + \tvoid (*print_item)(int i, struct string_list_item *item, void *print_item_data);\n       \tvoid *print_item_data;\n      @@\n     - static void list(struct item **list, size_t nr,\n     - \t\t struct add_i_state *s, struct list_options *opts)\n     + static void list(struct add_i_state *s, struct string_list *list,\n     + \t\t struct list_options *opts)\n       {\n      -\tint i;\n      +\tint i, last_lf = 0;\n       \n     - \tif (!nr)\n     + \tif (!list->nr)\n       \t\treturn;\n      @@\n       \n     - \tfor (i = 0; i < nr; i++) {\n     - \t\topts->print_item(i, list[i], opts->print_item_data);\n     + \tfor (i = 0; i < list->nr; i++) {\n     + \t\topts->print_item(i, list->items + i, opts->print_item_data);\n      +\n      +\t\tif ((opts->columns) && ((i + 1) % (opts->columns))) {\n      +\t\t\tputchar('\\t');\n     @@ -94,8 +94,7 @@\n      + * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n      + * `LIST_AND_CHOOSE_QUIT` is returned.\n      + */\n     -+static ssize_t list_and_choose(struct item **items, size_t nr,\n     -+\t\t\t       struct add_i_state *s,\n     ++static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,\n      +\t\t\t       struct list_and_choose_options *opts)\n      +{\n      +\tstruct strbuf input = STRBUF_INIT;\n     @@ -106,7 +105,7 @@\n      +\n      +\t\tstrbuf_reset(&input);\n      +\n     -+\t\tlist(items, nr, s, &opts->list_opts);\n     ++\t\tlist(s, items, &opts->list_opts);\n      +\n      +\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n      +\t\tfflush(stdout);\n     @@ -140,7 +139,7 @@\n      +\t\t\t}\n      +\n      +\t\t\tp[sep] = '\\0';\n     -+\t\t\tif (index < 0 || index >= nr)\n     ++\t\t\tif (index < 0 || index >= items->nr)\n      +\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n      +\t\t\telse {\n      +\t\t\t\tres = index;\n     @@ -163,17 +162,15 @@\n       \treturn 0;\n       }\n       \n     -+static void print_command_item(int i, struct item *item,\n     ++typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,\n     ++\t\t\t struct string_list *files,\n     ++\t\t\t struct list_options *opts);\n     ++\n     ++static void print_command_item(int i, struct string_list_item *item,\n      +\t\t\t       void *print_command_item_data)\n      +{\n     -+\tprintf(\" %2d: %s\", i + 1, item->name);\n     ++\tprintf(\" %2d: %s\", i + 1, item->string);\n      +}\n     -+\n     -+struct command_item {\n     -+\tstruct item item;\n     -+\tint (*command)(struct add_i_state *s, const struct pathspec *ps,\n     -+\t\t       struct file_list *files, struct list_options *opts);\n     -+};\n      +\n       int run_add_i(struct repository *r, const struct pathspec *ps)\n       {\n     @@ -182,11 +179,13 @@\n      +\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n      +\t\tN_(\"What now\")\n      +\t};\n     -+\tstruct command_item\n     -+\t\tstatus = { { \"status\" }, run_status };\n     -+\tstruct command_item *commands[] = {\n     -+\t\t&status\n     ++\tstruct {\n     ++\t\tconst char *string;\n     ++\t\tcommand_t command;\n     ++\t} command_list[] = {\n     ++\t\t{ \"status\", run_status },\n      +\t};\n     ++\tstruct string_list commands = STRING_LIST_INIT_NODUP;\n      +\n       \tstruct print_file_item_data print_file_item_data = {\n       \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n     @@ -196,27 +195,42 @@\n      +\t\t0, NULL, print_file_item, &print_file_item_data\n       \t};\n       \tstruct strbuf header = STRBUF_INIT;\n     - \tstruct file_list files = { NULL };\n     + \tstruct string_list files = STRING_LIST_INIT_DUP;\n      +\tssize_t i;\n       \tint res = 0;\n       \n     - \tif (init_add_i_state(r, &s))\n     ++\tfor (i = 0; i < ARRAY_SIZE(command_list); i++)\n     ++\t\tstring_list_append(&commands, command_list[i].string)\n     ++\t\t\t->util = command_list[i].command;\n     ++\n     + \tinit_add_i_state(&s, r);\n     ++\n     + \tstrbuf_addstr(&header, \"      \");\n     + \tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n     + \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n      @@\n     - \tif (run_status(&s, ps, &files, &opts) < 0)\n     - \t\tres = -1;\n     + \n     + \tres = run_status(&s, ps, &files, &opts);\n       \n      +\tfor (;;) {\n     -+\t\ti = list_and_choose((struct item **)commands,\n     -+\t\t\t\t    ARRAY_SIZE(commands), &s, &main_loop_opts);\n     ++\t\ti = list_and_choose(&s, &commands, &main_loop_opts);\n      +\t\tif (i == LIST_AND_CHOOSE_QUIT) {\n      +\t\t\tprintf(_(\"Bye.\\n\"));\n      +\t\t\tres = 0;\n      +\t\t\tbreak;\n      +\t\t}\n     -+\t\tif (i != LIST_AND_CHOOSE_ERROR)\n     -+\t\t\tres = commands[i]->command(&s, ps, &files, &opts);\n     ++\t\tif (i != LIST_AND_CHOOSE_ERROR) {\n     ++\t\t\tcommand_t command = commands.items[i].util;\n     ++\t\t\tres = command(&s, ps, &files, &opts);\n     ++\t\t}\n      +\t}\n      +\n     - \trelease_file_list(&files);\n     + \tstring_list_clear(&files, 1);\n       \tstrbuf_release(&print_file_item_data.buf);\n       \tstrbuf_release(&print_file_item_data.index);\n     + \tstrbuf_release(&print_file_item_data.worktree);\n     + \tstrbuf_release(&header);\n     ++\tstring_list_clear(&commands, 0);\n     + \n     + \treturn res;\n     + }\n  7:  c5a699b6b2 <  -:  ---------- Add a function to determine unique prefixes for a list of strings\n  8:  bc7a74f697 !  6:  57fdc01463 built-in add -i: show unique prefixes of the commands\n     @@ -1,4 +1,4 @@\n     -Author: Slavica Djukic <slawica92@hotmail.com>\n     +Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n          built-in add -i: show unique prefixes of the commands\n      \n     @@ -7,97 +7,197 @@\n          given parameters), and shown in the list, and accepted as a shortcut for\n          the command.\n      \n     -    We use the prefix map implementation that we just added in the previous\n     -    commit for that purpose.\n     +    To determine the unique prefixes, as well as to look up the command in\n     +    question, we use a copy of the list and sort it.\n      \n     -    Signed-off-by: Slavica Djukic <slawica92@hotmail.com>\n     +    While this might seem like overkill for a single command, it will make\n     +    much more sense when all the commands are implemented, and when we reuse\n     +    the same logic to present a list of files to edit, with convenient\n     +    unique prefixes.\n     +\n     +    At the start of the development of this patch series, a dedicated data\n     +    structure was introduced that imitated the Trie that the Perl version\n     +    implements. However, this was deemed overkill, and we now simply sort\n     +    the list before determining the length of the unique prefixes by looking\n     +    at each item's neighbor. As a bonus, we now use the same sorted list to\n     +    perform a binary search using the user-provided prefix as search key.\n     +\n     +    Original-patch-by: Slavica Đukić <slawica92@hotmail.com>\n     +    Helped-by: SZEDER Gábor <szeder.dev@gmail.com>\n          Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       diff --git a/add-interactive.c b/add-interactive.c\n       --- a/add-interactive.c\n       +++ b/add-interactive.c\n      @@\n     - #include \"diffcore.h\"\n     - #include \"revision.h\"\n     - #include \"refs.h\"\n     -+#include \"prefix-map.h\"\n     - \n     - struct add_i_state {\n     - \tstruct repository *r;\n     -@@\n     - \treturn 0;\n     + \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n       }\n       \n     --struct item {\n     --\tconst char *name;\n     --};\n     -+static ssize_t find_unique(const char *string,\n     -+\t\t\t   struct prefix_item **list, size_t nr)\n     ++/*\n     ++ * A \"prefix item list\" is a list of items that are identified by a string, and\n     ++ * a unique prefix (if any) is determined for each item.\n     ++ *\n     ++ * It is implemented in the form of a pair of `string_list`s, the first one\n     ++ * duplicating the strings, with the `util` field pointing at a structure whose\n     ++ * first field must be `size_t prefix_length`.\n     ++ *\n     ++ * That `prefix_length` field will be computed by `find_unique_prefixes()`; It\n     ++ * will be set to zero if no valid, unique prefix could be found.\n     ++ *\n     ++ * The second `string_list` is called `sorted` and does _not_ duplicate the\n     ++ * strings but simply reuses the first one's, with the `util` field pointing at\n     ++ * the `string_item_list` of the first `string_list`. It  will be populated and\n     ++ * sorted by `find_unique_prefixes()`.\n     ++ */\n     ++struct prefix_item_list {\n     ++\tstruct string_list items;\n     ++\tstruct string_list sorted;\n     ++\tsize_t min_length, max_length;\n     ++};\n     ++#define PREFIX_ITEM_LIST_INIT \\\n     ++\t{ STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, 1, 4 }\n     ++\n     ++static void prefix_item_list_clear(struct prefix_item_list *list)\n     ++{\n     ++\tstring_list_clear(&list->items, 1);\n     ++\tstring_list_clear(&list->sorted, 0);\n     ++}\n     ++\n     ++static void extend_prefix_length(struct string_list_item *p,\n     ++\t\t\t\t const char *other_string, size_t max_length)\n      +{\n     -+\tssize_t found = -1, i;\n     -+\n     -+\tfor (i = 0; i < nr; i++) {\n     -+\t\tstruct prefix_item *item = list[i];\n     -+\t\tif (!starts_with(item->name, string))\n     -+\t\t\tcontinue;\n     -+\t\tif (found >= 0)\n     -+\t\t\treturn -1;\n     -+\t\tfound = i;\n     ++\tsize_t *len = p->util;\n     ++\n     ++\tif (!*len || memcmp(p->string, other_string, *len))\n     ++\t\treturn;\n     ++\n     ++\tfor (;;) {\n     ++\t\tchar c = p->string[*len];\n     ++\n     ++\t\t/*\n     ++\t\t * Is `p` a strict prefix of `other`? Or have we exhausted the\n     ++\t\t * maximal length of the prefix? Or is the current character a\n     ++\t\t * multi-byte UTF-8 one? If so, there is no valid, unique\n     ++\t\t * prefix.\n     ++\t\t */\n     ++\t\tif (!c || ++*len > max_length || !isascii(c)) {\n     ++\t\t\t*len = 0;\n     ++\t\t\tbreak;\n     ++\t\t}\n     ++\n     ++\t\tif (c != other_string[*len - 1])\n     ++\t\t\tbreak;\n      +\t}\n     ++}\n      +\n     -+\treturn found;\n     ++static void find_unique_prefixes(struct prefix_item_list *list)\n     ++{\n     ++\tsize_t i;\n     ++\n     ++\tif (list->sorted.nr == list->items.nr)\n     ++\t\treturn;\n     ++\n     ++\tstring_list_clear(&list->sorted, 0);\n     ++\t/* Avoid reallocating incrementally */\n     ++\tlist->sorted.items = xmalloc(st_mult(sizeof(*list->sorted.items),\n     ++\t\t\t\t\t     list->items.nr));\n     ++\tlist->sorted.nr = list->sorted.alloc = list->items.nr;\n     ++\n     ++\tfor (i = 0; i < list->items.nr; i++) {\n     ++\t\tlist->sorted.items[i].string = list->items.items[i].string;\n     ++\t\tlist->sorted.items[i].util = list->items.items + i;\n     ++\t}\n     ++\n     ++\tstring_list_sort(&list->sorted);\n     ++\n     ++\tfor (i = 0; i < list->sorted.nr; i++) {\n     ++\t\tstruct string_list_item *sorted_item = list->sorted.items + i;\n     ++\t\tstruct string_list_item *item = sorted_item->util;\n     ++\t\tsize_t *len = item->util;\n     ++\n     ++\t\t*len = 0;\n     ++\t\twhile (*len < list->min_length) {\n     ++\t\t\tchar c = item->string[(*len)++];\n     ++\n     ++\t\t\tif (!c || !isascii(c)) {\n     ++\t\t\t\t*len = 0;\n     ++\t\t\t\tbreak;\n     ++\t\t\t}\n     ++\t\t}\n     ++\n     ++\t\tif (i > 0)\n     ++\t\t\textend_prefix_length(item, sorted_item[-1].string,\n     ++\t\t\t\t\t     list->max_length);\n     ++\t\tif (i + 1 < list->sorted.nr)\n     ++\t\t\textend_prefix_length(item, sorted_item[1].string,\n     ++\t\t\t\t\t     list->max_length);\n     ++\t}\n      +}\n     - \n     ++\n     ++static ssize_t find_unique(const char *string, struct prefix_item_list *list)\n     ++{\n     ++\tint index = string_list_find_insert_index(&list->sorted, string, 1);\n     ++\tstruct string_list_item *item;\n     ++\n     ++\tif (list->items.nr != list->sorted.nr)\n     ++\t\tBUG(\"prefix_item_list in inconsistent state (%\"PRIuMAX\n     ++\t\t    \" vs %\"PRIuMAX\")\",\n     ++\t\t    (uintmax_t)list->items.nr, (uintmax_t)list->sorted.nr);\n     ++\n     ++\tif (index < 0)\n     ++\t\titem = list->sorted.items[-1 - index].util;\n     ++\telse if (index > 0 &&\n     ++\t\t starts_with(list->sorted.items[index - 1].string, string))\n     ++\t\treturn -1;\n     ++\telse if (index + 1 < list->sorted.nr &&\n     ++\t\t starts_with(list->sorted.items[index + 1].string, string))\n     ++\t\treturn -1;\n     ++\telse if (index < list->sorted.nr)\n     ++\t\titem = list->sorted.items[index].util;\n     ++\telse\n     ++\t\treturn -1;\n     ++\treturn item - list->items.items;\n     ++}\n     ++\n       struct list_options {\n       \tint columns;\n       \tconst char *header;\n     --\tvoid (*print_item)(int i, struct item *item, void *print_item_data);\n     -+\tvoid (*print_item)(int i, struct prefix_item *item,\n     -+\t\t\t   void *print_item_data);\n     - \tvoid *print_item_data;\n     - };\n     - \n     --static void list(struct item **list, size_t nr,\n     -+static void list(struct prefix_item **list, size_t nr,\n     - \t\t struct add_i_state *s, struct list_options *opts)\n     - {\n     - \tint i, last_lf = 0;\n      @@\n        * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n        * `LIST_AND_CHOOSE_QUIT` is returned.\n        */\n     --static ssize_t list_and_choose(struct item **items, size_t nr,\n     -+static ssize_t list_and_choose(struct prefix_item **items, size_t nr,\n     - \t\t\t       struct add_i_state *s,\n     +-static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,\n     ++static ssize_t list_and_choose(struct add_i_state *s,\n     ++\t\t\t       struct prefix_item_list *items,\n       \t\t\t       struct list_and_choose_options *opts)\n       {\n       \tstruct strbuf input = STRBUF_INIT;\n       \tssize_t res = LIST_AND_CHOOSE_ERROR;\n       \n     -+\tfind_unique_prefixes(items, nr, 1, 4);\n     ++\tfind_unique_prefixes(items);\n      +\n       \tfor (;;) {\n       \t\tchar *p, *endp;\n       \n     + \t\tstrbuf_reset(&input);\n     + \n     +-\t\tlist(s, items, &opts->list_opts);\n     ++\t\tlist(s, &items->items, &opts->list_opts);\n     + \n     + \t\tprintf(\"%s%s\", opts->prompt, \"> \");\n     + \t\tfflush(stdout);\n      @@\n       \t\t\t}\n       \n       \t\t\tp[sep] = '\\0';\n     +-\t\t\tif (index < 0 || index >= items->nr)\n      +\t\t\tif (index < 0)\n     -+\t\t\t\tindex = find_unique(p, items, nr);\n     ++\t\t\t\tindex = find_unique(p, items);\n      +\n     - \t\t\tif (index < 0 || index >= nr)\n     ++\t\t\tif (index < 0 || index >= items->items.nr)\n       \t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n       \t\t\telse {\n     -@@\n     - \n     - struct file_list {\n     - \tstruct file_item {\n     --\t\tstruct item item;\n     -+\t\tstruct prefix_item item;\n     - \t\tstruct adddel index, worktree;\n     - \t} **file;\n     - \tsize_t nr, alloc;\n     + \t\t\t\tres = index;\n      @@\n       \t\tstrbuf_addstr(buf, no_changes);\n       }\n     @@ -122,50 +222,74 @@\n       struct print_file_item_data {\n       \tconst char *modified_fmt;\n       \tstruct strbuf buf, index, worktree;\n     - };\n     - \n     --static void print_file_item(int i, struct item *item,\n     -+static void print_file_item(int i, struct prefix_item *item,\n     - \t\t\t    void *print_file_item_data)\n     - {\n     - \tstruct file_item *c = (struct file_item *)item;\n      @@\n     - \t\treturn -1;\n     - \n     - \tif (files->nr)\n     --\t\tlist((struct item **)files->file, files->nr, s, opts);\n     -+\t\tlist((struct prefix_item **)files->file, files->nr, s, opts);\n     - \tputchar('\\n');\n     + \t\t\t struct string_list *files,\n     + \t\t\t struct list_options *opts);\n       \n     - \treturn 0;\n     - }\n     - \n     --static void print_command_item(int i, struct item *item,\n     -+static void print_command_item(int i, struct prefix_item *item,\n     ++struct command_item {\n     ++\tsize_t prefix_length;\n     ++\tcommand_t command;\n     ++};\n     ++\n     + static void print_command_item(int i, struct string_list_item *item,\n       \t\t\t       void *print_command_item_data)\n       {\n     --\tprintf(\" %2d: %s\", i + 1, item->name);\n     -+\tif (!item->prefix_length ||\n     -+\t    !is_valid_prefix(item->name, item->prefix_length))\n     -+\t\tprintf(\" %2d: %s\", i + 1, item->name);\n     +-\tprintf(\" %2d: %s\", i + 1, item->string);\n     ++\tstruct command_item *util = item->util;\n     ++\n     ++\tif (!util->prefix_length ||\n     ++\t    !is_valid_prefix(item->string, util->prefix_length))\n     ++\t\tprintf(\" %2d: %s\", i + 1, item->string);\n      +\telse\n     -+\t\tprintf(\" %3d: [%.*s]%s\", i + 1,\n     -+\t\t       (int)item->prefix_length, item->name,\n     -+\t\t       item->name + item->prefix_length);\n     ++\t\tprintf(\" %2d: [%.*s]%s\", i + 1,\n     ++\t\t       (int)util->prefix_length, item->string,\n     ++\t\t       item->string + util->prefix_length);\n       }\n       \n     - struct command_item {\n     --\tstruct item item;\n     -+\tstruct prefix_item item;\n     - \tint (*command)(struct add_i_state *s, const struct pathspec *ps,\n     - \t\t       struct file_list *files, struct list_options *opts);\n     - };\n     + int run_add_i(struct repository *r, const struct pathspec *ps)\n      @@\n     - \t\tres = -1;\n     + \t} command_list[] = {\n     + \t\t{ \"status\", run_status },\n     + \t};\n     +-\tstruct string_list commands = STRING_LIST_INIT_NODUP;\n     ++\tstruct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;\n       \n     - \tfor (;;) {\n     --\t\ti = list_and_choose((struct item **)commands,\n     -+\t\ti = list_and_choose((struct prefix_item **)commands,\n     - \t\t\t\t    ARRAY_SIZE(commands), &s, &main_loop_opts);\n     - \t\tif (i == LIST_AND_CHOOSE_QUIT) {\n     - \t\t\tprintf(_(\"Bye.\\n\"));\n     + \tstruct print_file_item_data print_file_item_data = {\n     + \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n     +@@\n     + \tssize_t i;\n     + \tint res = 0;\n     + \n     +-\tfor (i = 0; i < ARRAY_SIZE(command_list); i++)\n     +-\t\tstring_list_append(&commands, command_list[i].string)\n     +-\t\t\t->util = command_list[i].command;\n     ++\tfor (i = 0; i < ARRAY_SIZE(command_list); i++) {\n     ++\t\tstruct command_item *util = xcalloc(sizeof(*util), 1);\n     ++\t\tutil->command = command_list[i].command;\n     ++\t\tstring_list_append(&commands.items, command_list[i].string)\n     ++\t\t\t->util = util;\n     ++\t}\n     + \n     + \tinit_add_i_state(&s, r);\n     + \n     +@@\n     + \t\t\tbreak;\n     + \t\t}\n     + \t\tif (i != LIST_AND_CHOOSE_ERROR) {\n     +-\t\t\tcommand_t command = commands.items[i].util;\n     +-\t\t\tres = command(&s, ps, &files, &opts);\n     ++\t\t\tstruct command_item *util =\n     ++\t\t\t\tcommands.items.items[i].util;\n     ++\t\t\tres = util->command(&s, ps, &files, &opts);\n     + \t\t}\n     + \t}\n     + \n     +@@\n     + \tstrbuf_release(&print_file_item_data.index);\n     + \tstrbuf_release(&print_file_item_data.worktree);\n     + \tstrbuf_release(&header);\n     +-\tstring_list_clear(&commands, 0);\n     ++\tprefix_item_list_clear(&commands);\n     + \n     + \treturn res;\n     + }\n  9:  74f73e26b4 !  7:  77ad5f333a built-in add -i: support `?` (prompt help)\n     @@ -24,9 +24,9 @@\n       \n       \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n      +\tinit_color(r, s, \"help\", s->help_color, GIT_COLOR_BOLD_RED);\n     - \n     - \treturn 0;\n       }\n     + \n     + /*\n      @@\n       \tstruct list_options list_opts;\n       \n     @@ -48,8 +48,8 @@\n       \t\tfor (;;) {\n       \t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n      @@\n     - \t\t       struct file_list *files, struct list_options *opts);\n     - };\n     + \t\t       item->string + util->prefix_length);\n     + }\n       \n      +static void command_prompt_help(struct add_i_state *s)\n      +{\n     @@ -71,5 +71,5 @@\n      -\t\tN_(\"What now\")\n      +\t\tN_(\"What now\"), command_prompt_help\n       \t};\n     - \tstruct command_item\n     - \t\tstatus = { { \"status\" }, run_status };\n     + \tstruct {\n     + \t\tconst char *string;\n 10:  88001009bc !  8:  3d0b172a7f built-in add -i: use color in the main loop\n     @@ -1,11 +1,11 @@\n     -Author: Slavica Djukic <slawica92@hotmail.com>\n     +Author: Slavica Đukić <slawica92@hotmail.com>\n      \n          built-in add -i: use color in the main loop\n      \n          The error messages as well as the unique prefixes are colored in `git\n          add -i` by default; We need to do the same in the built-in version.\n      \n     -    Signed-off-by: Slavica Djukic <slawica92@hotmail.com>\n     +    Signed-off-by: Slavica Đukić <slawica92@hotmail.com>\n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n       diff --git a/add-interactive.c b/add-interactive.c\n     @@ -28,12 +28,12 @@\n      +\tinit_color(r, s, \"prompt\", s->prompt_color, GIT_COLOR_BOLD_BLUE);\n      +\tinit_color(r, s, \"error\", s->error_color, GIT_COLOR_BOLD_RED);\n      +\tinit_color(r, s, \"reset\", s->reset_color, GIT_COLOR_RESET);\n     - \n     - \treturn 0;\n       }\n     + \n     + /*\n      @@\n       \n     - \t\tlist(items, nr, s, &opts->list_opts);\n     + \t\tlist(s, &items->items, &opts->list_opts);\n       \n      -\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n      +\t\tcolor_fprintf(stdout, s->prompt_color, \"%s\", opts->prompt);\n     @@ -42,9 +42,9 @@\n       \n       \t\tif (strbuf_getline(&input, stdin) == EOF) {\n      @@\n     - \t\t\t\tindex = find_unique(p, items, nr);\n     + \t\t\t\tindex = find_unique(p, items);\n       \n     - \t\t\tif (index < 0 || index >= nr)\n     + \t\t\tif (index < 0 || index >= items->items.nr)\n      -\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n      +\t\t\t\tcolor_fprintf_ln(stdout, s->error_color,\n      +\t\t\t\t\t\t _(\"Huh (%s)?\"), p);\n     @@ -52,43 +52,46 @@\n       \t\t\t\tres = index;\n       \t\t\t\tbreak;\n      @@\n     - \treturn 0;\n     - }\n     + \tcommand_t command;\n     + };\n       \n      +struct print_command_item_data {\n      +\tconst char *color, *reset;\n      +};\n      +\n     - static void print_command_item(int i, struct prefix_item *item,\n     + static void print_command_item(int i, struct string_list_item *item,\n       \t\t\t       void *print_command_item_data)\n       {\n      +\tstruct print_command_item_data *d = print_command_item_data;\n     -+\n     - \tif (!item->prefix_length ||\n     - \t    !is_valid_prefix(item->name, item->prefix_length))\n     - \t\tprintf(\" %2d: %s\", i + 1, item->name);\n     + \tstruct command_item *util = item->util;\n     + \n     + \tif (!util->prefix_length ||\n     + \t    !is_valid_prefix(item->string, util->prefix_length))\n     + \t\tprintf(\" %2d: %s\", i + 1, item->string);\n       \telse\n     --\t\tprintf(\" %3d: [%.*s]%s\", i + 1,\n     --\t\t       (int)item->prefix_length, item->name,\n     +-\t\tprintf(\" %2d: [%.*s]%s\", i + 1,\n     +-\t\t       (int)util->prefix_length, item->string,\n     +-\t\t       item->string + util->prefix_length);\n      +\t\tprintf(\" %2d: %s%.*s%s%s\", i + 1,\n     -+\t\t       d->color, (int)item->prefix_length, item->name, d->reset,\n     - \t\t       item->name + item->prefix_length);\n     ++\t\t       d->color, (int)util->prefix_length, item->string,\n     ++\t\t       d->reset, item->string + util->prefix_length);\n       }\n       \n     + static void command_prompt_help(struct add_i_state *s)\n      @@\n       int run_add_i(struct repository *r, const struct pathspec *ps)\n       {\n       \tstruct add_i_state s = { NULL };\n     -+\tstruct print_command_item_data data;\n     ++\tstruct print_command_item_data data = { \"[\", \"]\" };\n       \tstruct list_and_choose_options main_loop_opts = {\n      -\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n      +\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, &data },\n       \t\tN_(\"What now\"), command_prompt_help\n       \t};\n     - \tstruct command_item\n     + \tstruct {\n      @@\n     - \tif (init_add_i_state(r, &s))\n     - \t\treturn error(\"could not parse `add -i` config\");\n     + \n     + \tinit_add_i_state(&s, r);\n       \n      +\t/*\n      +\t * When color was asked for, use the prompt color for\n     @@ -97,9 +100,6 @@\n      +\tif (s.use_color) {\n      +\t\tdata.color = s.prompt_color;\n      +\t\tdata.reset = s.reset_color;\n     -+\t} else {\n     -+\t\tdata.color = \"[\";\n     -+\t\tdata.reset = \"]\";\n      +\t}\n      +\n       \tstrbuf_addstr(&header, \"      \");\n 11:  b27fbe289f !  9:  85e508ef11 built-in add -i: implement the `help` command\n     @@ -1,4 +1,4 @@\n     -Author: Johannes Schindelin <johannes.schindelin@gmx.de>\n     +Author: Slavica Đukić <slawica92@hotmail.com>\n      \n          built-in add -i: implement the `help` command\n      \n     @@ -8,7 +8,7 @@\n          To make sure that it renders exactly like the Perl version of `git add\n          -i`, we also add a test case for that to `t3701-add-interactive.sh`.\n      \n     -    Signed-off-by: Slavica Djukic <slawica92@hotmail.com>\n     +    Signed-off-by: Slavica Đukić <slawica92@hotmail.com>\n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n       diff --git a/add-interactive.c b/add-interactive.c\n     @@ -19,7 +19,7 @@\n       }\n       \n      +static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,\n     -+\t\t    struct file_list *unused_files,\n     ++\t\t    struct string_list *unused_files,\n      +\t\t    struct list_options *unused_opts)\n      +{\n      +\tcolor_fprintf_ln(stdout, s->help_color, \"status        - %s\",\n     @@ -38,23 +38,17 @@\n      +\treturn 0;\n      +}\n      +\n     - struct print_command_item_data {\n     - \tconst char *color, *reset;\n     - };\n     + typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,\n     + \t\t\t struct string_list *files,\n     + \t\t\t struct list_options *opts);\n      @@\n     - \t\tN_(\"What now\"), command_prompt_help\n     - \t};\n     - \tstruct command_item\n     --\t\tstatus = { { \"status\" }, run_status };\n     -+\t\tstatus = { { \"status\" }, run_status },\n     -+\t\thelp = { { \"help\" }, run_help };\n     - \tstruct command_item *commands[] = {\n     --\t\t&status\n     -+\t\t&status,\n     -+\t\t&help\n     + \t\tcommand_t command;\n     + \t} command_list[] = {\n     + \t\t{ \"status\", run_status },\n     ++\t\t{ \"help\", run_help },\n       \t};\n     + \tstruct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;\n       \n     - \tstruct print_file_item_data print_file_item_data = {\n      \n       diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\n       --- a/t/t3701-add-interactive.sh\n\n-- \ngitgitgadget\n"},{"id":"385384","messageId":"77ad5f333a7f72135ed084dc2d0cf20d372302c8.1572869730.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v5.git.1572869729.gitgitgadget@gmail.com","subject":"[PATCH v5 7/9] built-in add -i: support `?` (prompt help)","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-04T12:15:27Z","receivedAt":"2019-11-04T12:15:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWith this change, we print out the same colored help text that the\nPerl-based `git add -i` prints in the main loop when question mark is\nentered.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 22 +++++++++++++++++++++-\n 1 file changed, 21 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex eb559555ad..d96e18fce5 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -11,6 +11,7 @@ struct add_i_state {\n \tstruct repository *r;\n \tint use_color;\n \tchar header_color[COLOR_MAXLEN];\n+\tchar help_color[COLOR_MAXLEN];\n };\n \n static void init_color(struct repository *r, struct add_i_state *s,\n@@ -43,6 +44,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)\n \ts->use_color = want_color(s->use_color);\n \n \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n+\tinit_color(r, s, \"help\", s->help_color, GIT_COLOR_BOLD_RED);\n }\n \n /*\n@@ -210,6 +212,7 @@ struct list_and_choose_options {\n \tstruct list_options list_opts;\n \n \tconst char *prompt;\n+\tvoid (*print_help)(struct add_i_state *s);\n };\n \n #define LIST_AND_CHOOSE_ERROR (-1)\n@@ -250,6 +253,11 @@ static ssize_t list_and_choose(struct add_i_state *s,\n \t\tif (!input.len)\n \t\t\tbreak;\n \n+\t\tif (!strcmp(input.buf, \"?\")) {\n+\t\t\topts->print_help(s);\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tp = input.buf;\n \t\tfor (;;) {\n \t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n@@ -513,12 +521,24 @@ static void print_command_item(int i, struct string_list_item *item,\n \t\t       item->string + util->prefix_length);\n }\n \n+static void command_prompt_help(struct add_i_state *s)\n+{\n+\tconst char *help_color = s->help_color;\n+\tcolor_fprintf_ln(stdout, help_color, \"%s\", _(\"Prompt help:\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"1          - %s\",\n+\t\t\t _(\"select a numbered item\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"foo        - %s\",\n+\t\t\t _(\"select item based on unique prefix\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"           - %s\",\n+\t\t\t _(\"(empty) select nothing\"));\n+}\n+\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n \tstruct list_and_choose_options main_loop_opts = {\n \t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n-\t\tN_(\"What now\")\n+\t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct {\n \t\tconst char *string;\n-- \ngitgitgadget\n\n"},{"id":"385385","messageId":"25590fbbbee7efc34477bfea233684e93ee7fe60.1572869730.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v5.git.1572869729.gitgitgadget@gmail.com","subject":"[PATCH v5 5/9] built-in add -i: implement the main loop","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-04T12:15:25Z","receivedAt":"2019-11-04T12:15:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe reason why we did not start with the main loop to begin with is that\nit is the first user of `list_and_choose()`, which uses the `list()`\nfunction that we conveniently introduced for use by the `status`\ncommand.\n\nApart from the \"and choose\" part, there are more differences between the\nway the `status` command calls the `list_and_choose()` function in the\nPerl version of `git add -i` compared to the other callers of said\nfunction. The most important ones:\n\n- The list is not only shown, but the user is also asked to make a\n  choice, possibly selecting multiple entries.\n\n- The list of items is prefixed with a marker indicating what items have\n  been selected, if multi-selection is allowed.\n\n- Initially, for each item a unique prefix (if there exists any within\n  the given parameters) is determined, and shown in the list, and\n  accepted as a shortcut for the selection.\n\nThese features will be implemented later, except the part where the user\ncan choose a command. At this stage, though, the built-in `git add -i`\nstill only supports the `status` command, with the remaining commands to\nfollow over the course of the next commits.\n\nIn addition, we also modify `list()` to support displaying the commands\nin columns, even if there is currently only one.\n\nThe Perl script `git-add--interactive.perl` mixed the purposes of the\n\"list\" and the \"and choose\" part into the same function. In the C\nversion, we will keep them separate instead, calling the `list()`\nfunction from the `list_and_choose()` function.\n\nNote that we only have a prompt ending in a single \">\" at this stage;\nlater commits will add commands that display a double \">>\" to indicate\nthat the user is in a different loop than the main one.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 135 +++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 133 insertions(+), 2 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 174e07ce83..c6f7fbad36 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -46,6 +46,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)\n }\n \n struct list_options {\n+\tint columns;\n \tconst char *header;\n \tvoid (*print_item)(int i, struct string_list_item *item, void *print_item_data);\n \tvoid *print_item_data;\n@@ -54,7 +55,7 @@ struct list_options {\n static void list(struct add_i_state *s, struct string_list *list,\n \t\t struct list_options *opts)\n {\n-\tint i;\n+\tint i, last_lf = 0;\n \n \tif (!list->nr)\n \t\treturn;\n@@ -65,8 +66,96 @@ static void list(struct add_i_state *s, struct string_list *list,\n \n \tfor (i = 0; i < list->nr; i++) {\n \t\topts->print_item(i, list->items + i, opts->print_item_data);\n+\n+\t\tif ((opts->columns) && ((i + 1) % (opts->columns))) {\n+\t\t\tputchar('\\t');\n+\t\t\tlast_lf = 0;\n+\t\t}\n+\t\telse {\n+\t\t\tputchar('\\n');\n+\t\t\tlast_lf = 1;\n+\t\t}\n+\t}\n+\n+\tif (!last_lf)\n \t\tputchar('\\n');\n+}\n+struct list_and_choose_options {\n+\tstruct list_options list_opts;\n+\n+\tconst char *prompt;\n+};\n+\n+#define LIST_AND_CHOOSE_ERROR (-1)\n+#define LIST_AND_CHOOSE_QUIT  (-2)\n+\n+/*\n+ * Returns the selected index.\n+ *\n+ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n+ * `LIST_AND_CHOOSE_QUIT` is returned.\n+ */\n+static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,\n+\t\t\t       struct list_and_choose_options *opts)\n+{\n+\tstruct strbuf input = STRBUF_INIT;\n+\tssize_t res = LIST_AND_CHOOSE_ERROR;\n+\n+\tfor (;;) {\n+\t\tchar *p, *endp;\n+\n+\t\tstrbuf_reset(&input);\n+\n+\t\tlist(s, items, &opts->list_opts);\n+\n+\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n+\t\tfflush(stdout);\n+\n+\t\tif (strbuf_getline(&input, stdin) == EOF) {\n+\t\t\tputchar('\\n');\n+\t\t\tres = LIST_AND_CHOOSE_QUIT;\n+\t\t\tbreak;\n+\t\t}\n+\t\tstrbuf_trim(&input);\n+\n+\t\tif (!input.len)\n+\t\t\tbreak;\n+\n+\t\tp = input.buf;\n+\t\tfor (;;) {\n+\t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n+\t\t\tssize_t index = -1;\n+\n+\t\t\tif (!sep) {\n+\t\t\t\tif (!*p)\n+\t\t\t\t\tbreak;\n+\t\t\t\tp++;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (isdigit(*p)) {\n+\t\t\t\tindex = strtoul(p, &endp, 10) - 1;\n+\t\t\t\tif (endp != p + sep)\n+\t\t\t\t\tindex = -1;\n+\t\t\t}\n+\n+\t\t\tp[sep] = '\\0';\n+\t\t\tif (index < 0 || index >= items->nr)\n+\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n+\t\t\telse {\n+\t\t\t\tres = index;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tp += sep + 1;\n+\t\t}\n+\n+\t\tif (res != LIST_AND_CHOOSE_ERROR)\n+\t\t\tbreak;\n \t}\n+\n+\tstrbuf_release(&input);\n+\treturn res;\n }\n \n struct adddel {\n@@ -252,20 +341,48 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \treturn 0;\n }\n \n+typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,\n+\t\t\t struct string_list *files,\n+\t\t\t struct list_options *opts);\n+\n+static void print_command_item(int i, struct string_list_item *item,\n+\t\t\t       void *print_command_item_data)\n+{\n+\tprintf(\" %2d: %s\", i + 1, item->string);\n+}\n+\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n+\tstruct list_and_choose_options main_loop_opts = {\n+\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n+\t\tN_(\"What now\")\n+\t};\n+\tstruct {\n+\t\tconst char *string;\n+\t\tcommand_t command;\n+\t} command_list[] = {\n+\t\t{ \"status\", run_status },\n+\t};\n+\tstruct string_list commands = STRING_LIST_INIT_NODUP;\n+\n \tstruct print_file_item_data print_file_item_data = {\n \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n \t};\n \tstruct list_options opts = {\n-\t\tNULL, print_file_item, &print_file_item_data\n+\t\t0, NULL, print_file_item, &print_file_item_data\n \t};\n \tstruct strbuf header = STRBUF_INIT;\n \tstruct string_list files = STRING_LIST_INIT_DUP;\n+\tssize_t i;\n \tint res = 0;\n \n+\tfor (i = 0; i < ARRAY_SIZE(command_list); i++)\n+\t\tstring_list_append(&commands, command_list[i].string)\n+\t\t\t->util = command_list[i].command;\n+\n \tinit_add_i_state(&s, r);\n+\n \tstrbuf_addstr(&header, \"      \");\n \tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n@@ -279,11 +396,25 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \n \tres = run_status(&s, ps, &files, &opts);\n \n+\tfor (;;) {\n+\t\ti = list_and_choose(&s, &commands, &main_loop_opts);\n+\t\tif (i == LIST_AND_CHOOSE_QUIT) {\n+\t\t\tprintf(_(\"Bye.\\n\"));\n+\t\t\tres = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (i != LIST_AND_CHOOSE_ERROR) {\n+\t\t\tcommand_t command = commands.items[i].util;\n+\t\t\tres = command(&s, ps, &files, &opts);\n+\t\t}\n+\t}\n+\n \tstring_list_clear(&files, 1);\n \tstrbuf_release(&print_file_item_data.buf);\n \tstrbuf_release(&print_file_item_data.index);\n \tstrbuf_release(&print_file_item_data.worktree);\n \tstrbuf_release(&header);\n+\tstring_list_clear(&commands, 0);\n \n \treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"385386","messageId":"3d0b172a7f863fe079b77589a6410b228dcaba96.1572869730.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v5.git.1572869729.gitgitgadget@gmail.com","subject":"[PATCH v5 8/9] built-in add -i: use color in the main loop","fromName":"Slavica Đukić via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-04T12:15:28Z","receivedAt":"2019-11-04T12:15:44Z","isPatch":true,"sender":{"key":"name:Slavica Đukić","avatar":null},"body":"From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>\n\nThe error messages as well as the unique prefixes are colored in `git\nadd -i` by default; We need to do the same in the built-in version.\n\nSigned-off-by: Slavica Đukić <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 35 +++++++++++++++++++++++++++++------\n 1 file changed, 29 insertions(+), 6 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex d96e18fce5..64d84ba1dc 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -12,6 +12,9 @@ struct add_i_state {\n \tint use_color;\n \tchar header_color[COLOR_MAXLEN];\n \tchar help_color[COLOR_MAXLEN];\n+\tchar prompt_color[COLOR_MAXLEN];\n+\tchar error_color[COLOR_MAXLEN];\n+\tchar reset_color[COLOR_MAXLEN];\n };\n \n static void init_color(struct repository *r, struct add_i_state *s,\n@@ -45,6 +48,9 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)\n \n \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n \tinit_color(r, s, \"help\", s->help_color, GIT_COLOR_BOLD_RED);\n+\tinit_color(r, s, \"prompt\", s->prompt_color, GIT_COLOR_BOLD_BLUE);\n+\tinit_color(r, s, \"error\", s->error_color, GIT_COLOR_BOLD_RED);\n+\tinit_color(r, s, \"reset\", s->reset_color, GIT_COLOR_RESET);\n }\n \n /*\n@@ -240,7 +246,8 @@ static ssize_t list_and_choose(struct add_i_state *s,\n \n \t\tlist(s, &items->items, &opts->list_opts);\n \n-\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n+\t\tcolor_fprintf(stdout, s->prompt_color, \"%s\", opts->prompt);\n+\t\tfputs(\"> \", stdout);\n \t\tfflush(stdout);\n \n \t\tif (strbuf_getline(&input, stdin) == EOF) {\n@@ -281,7 +288,8 @@ static ssize_t list_and_choose(struct add_i_state *s,\n \t\t\t\tindex = find_unique(p, items);\n \n \t\t\tif (index < 0 || index >= items->items.nr)\n-\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n+\t\t\t\tcolor_fprintf_ln(stdout, s->error_color,\n+\t\t\t\t\t\t _(\"Huh (%s)?\"), p);\n \t\t\telse {\n \t\t\t\tres = index;\n \t\t\t\tbreak;\n@@ -507,18 +515,23 @@ struct command_item {\n \tcommand_t command;\n };\n \n+struct print_command_item_data {\n+\tconst char *color, *reset;\n+};\n+\n static void print_command_item(int i, struct string_list_item *item,\n \t\t\t       void *print_command_item_data)\n {\n+\tstruct print_command_item_data *d = print_command_item_data;\n \tstruct command_item *util = item->util;\n \n \tif (!util->prefix_length ||\n \t    !is_valid_prefix(item->string, util->prefix_length))\n \t\tprintf(\" %2d: %s\", i + 1, item->string);\n \telse\n-\t\tprintf(\" %2d: [%.*s]%s\", i + 1,\n-\t\t       (int)util->prefix_length, item->string,\n-\t\t       item->string + util->prefix_length);\n+\t\tprintf(\" %2d: %s%.*s%s%s\", i + 1,\n+\t\t       d->color, (int)util->prefix_length, item->string,\n+\t\t       d->reset, item->string + util->prefix_length);\n }\n \n static void command_prompt_help(struct add_i_state *s)\n@@ -536,8 +549,9 @@ static void command_prompt_help(struct add_i_state *s)\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n+\tstruct print_command_item_data data = { \"[\", \"]\" };\n \tstruct list_and_choose_options main_loop_opts = {\n-\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n+\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, &data },\n \t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct {\n@@ -568,6 +582,15 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \n \tinit_add_i_state(&s, r);\n \n+\t/*\n+\t * When color was asked for, use the prompt color for\n+\t * highlighting, otherwise use square brackets.\n+\t */\n+\tif (s.use_color) {\n+\t\tdata.color = s.prompt_color;\n+\t\tdata.reset = s.reset_color;\n+\t}\n+\n \tstrbuf_addstr(&header, \"      \");\n \tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n-- \ngitgitgadget\n\n"},{"id":"385387","messageId":"85e508ef119658c154da35482095a4f0f15adc80.1572869730.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v5.git.1572869729.gitgitgadget@gmail.com","subject":"[PATCH v5 9/9] built-in add -i: implement the `help` command","fromName":"Slavica Đukić via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-04T12:15:29Z","receivedAt":"2019-11-04T12:15:46Z","isPatch":true,"sender":{"key":"name:Slavica Đukić","avatar":null},"body":"From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>\n\nThis imitates the code to show the help text from the Perl script\n`git-add--interactive.perl` in the built-in version.\n\nTo make sure that it renders exactly like the Perl version of `git add\n-i`, we also add a test case for that to `t3701-add-interactive.sh`.\n\nSigned-off-by: Slavica Đukić <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c          | 21 +++++++++++++++++++++\n t/t3701-add-interactive.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 46 insertions(+)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 64d84ba1dc..4e5241e865 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -506,6 +506,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \treturn 0;\n }\n \n+static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,\n+\t\t    struct string_list *unused_files,\n+\t\t    struct list_options *unused_opts)\n+{\n+\tcolor_fprintf_ln(stdout, s->help_color, \"status        - %s\",\n+\t\t\t _(\"show paths with changes\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"update        - %s\",\n+\t\t\t _(\"add working tree state to the staged set of changes\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"revert        - %s\",\n+\t\t\t _(\"revert staged set of changes back to the HEAD version\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"patch         - %s\",\n+\t\t\t _(\"pick hunks and update selectively\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"diff          - %s\",\n+\t\t\t _(\"view diff between HEAD and index\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"add untracked - %s\",\n+\t\t\t _(\"add contents of untracked files to the staged set of changes\"));\n+\n+\treturn 0;\n+}\n+\n typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,\n \t\t\t struct string_list *files,\n \t\t\t struct list_options *opts);\n@@ -559,6 +579,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\tcommand_t command;\n \t} command_list[] = {\n \t\t{ \"status\", run_status },\n+\t\t{ \"help\", run_help },\n \t};\n \tstruct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;\n \ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex d50e165ca8..d4f9386621 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' '\n \ttest_write_lines a b a b a a b a b a >expect &&\n \ttest_cmp expect a\n '\n+\n+test_expect_success 'show help from add--helper' '\n+\tgit reset --hard &&\n+\tcat >expect <<-EOF &&\n+\n+\t<BOLD>*** Commands ***<RESET>\n+\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>\n+\t<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>\n+\t<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>\n+\t<BOLD;RED>patch         - pick hunks and update selectively<RESET>\n+\t<BOLD;RED>diff          - view diff between HEAD and index<RESET>\n+\t<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>\n+\t<BOLD>*** Commands ***<RESET>\n+\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t<BOLD;BLUE>What now<RESET>>$SP\n+\tBye.\n+\tEOF\n+\ttest_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&\n+\ttest_decode_color <actual.colored >actual &&\n+\ttest_i18ncmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"385388","messageId":"57fdc01463b5fe1637dcfa4b027bde8e99f7fe86.1572869730.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v5.git.1572869729.gitgitgadget@gmail.com","subject":"[PATCH v5 6/9] built-in add -i: show unique prefixes of the commands","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-04T12:15:26Z","receivedAt":"2019-11-04T12:15:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nJust like in the Perl script `git-add--interactive.perl`, for each\ncommand a unique prefix is determined (if there exists any within the\ngiven parameters), and shown in the list, and accepted as a shortcut for\nthe command.\n\nTo determine the unique prefixes, as well as to look up the command in\nquestion, we use a copy of the list and sort it.\n\nWhile this might seem like overkill for a single command, it will make\nmuch more sense when all the commands are implemented, and when we reuse\nthe same logic to present a list of files to edit, with convenient\nunique prefixes.\n\nAt the start of the development of this patch series, a dedicated data\nstructure was introduced that imitated the Trie that the Perl version\nimplements. However, this was deemed overkill, and we now simply sort\nthe list before determining the length of the unique prefixes by looking\nat each item's neighbor. As a bonus, we now use the same sorted list to\nperform a binary search using the user-provided prefix as search key.\n\nOriginal-patch-by: Slavica Đukić <slawica92@hotmail.com>\nHelped-by: SZEDER Gábor <szeder.dev@gmail.com>\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n---\n add-interactive.c | 188 +++++++++++++++++++++++++++++++++++++++++++---\n 1 file changed, 177 insertions(+), 11 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex c6f7fbad36..eb559555ad 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -45,6 +45,132 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)\n \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n }\n \n+/*\n+ * A \"prefix item list\" is a list of items that are identified by a string, and\n+ * a unique prefix (if any) is determined for each item.\n+ *\n+ * It is implemented in the form of a pair of `string_list`s, the first one\n+ * duplicating the strings, with the `util` field pointing at a structure whose\n+ * first field must be `size_t prefix_length`.\n+ *\n+ * That `prefix_length` field will be computed by `find_unique_prefixes()`; It\n+ * will be set to zero if no valid, unique prefix could be found.\n+ *\n+ * The second `string_list` is called `sorted` and does _not_ duplicate the\n+ * strings but simply reuses the first one's, with the `util` field pointing at\n+ * the `string_item_list` of the first `string_list`. It  will be populated and\n+ * sorted by `find_unique_prefixes()`.\n+ */\n+struct prefix_item_list {\n+\tstruct string_list items;\n+\tstruct string_list sorted;\n+\tsize_t min_length, max_length;\n+};\n+#define PREFIX_ITEM_LIST_INIT \\\n+\t{ STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, 1, 4 }\n+\n+static void prefix_item_list_clear(struct prefix_item_list *list)\n+{\n+\tstring_list_clear(&list->items, 1);\n+\tstring_list_clear(&list->sorted, 0);\n+}\n+\n+static void extend_prefix_length(struct string_list_item *p,\n+\t\t\t\t const char *other_string, size_t max_length)\n+{\n+\tsize_t *len = p->util;\n+\n+\tif (!*len || memcmp(p->string, other_string, *len))\n+\t\treturn;\n+\n+\tfor (;;) {\n+\t\tchar c = p->string[*len];\n+\n+\t\t/*\n+\t\t * Is `p` a strict prefix of `other`? Or have we exhausted the\n+\t\t * maximal length of the prefix? Or is the current character a\n+\t\t * multi-byte UTF-8 one? If so, there is no valid, unique\n+\t\t * prefix.\n+\t\t */\n+\t\tif (!c || ++*len > max_length || !isascii(c)) {\n+\t\t\t*len = 0;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (c != other_string[*len - 1])\n+\t\t\tbreak;\n+\t}\n+}\n+\n+static void find_unique_prefixes(struct prefix_item_list *list)\n+{\n+\tsize_t i;\n+\n+\tif (list->sorted.nr == list->items.nr)\n+\t\treturn;\n+\n+\tstring_list_clear(&list->sorted, 0);\n+\t/* Avoid reallocating incrementally */\n+\tlist->sorted.items = xmalloc(st_mult(sizeof(*list->sorted.items),\n+\t\t\t\t\t     list->items.nr));\n+\tlist->sorted.nr = list->sorted.alloc = list->items.nr;\n+\n+\tfor (i = 0; i < list->items.nr; i++) {\n+\t\tlist->sorted.items[i].string = list->items.items[i].string;\n+\t\tlist->sorted.items[i].util = list->items.items + i;\n+\t}\n+\n+\tstring_list_sort(&list->sorted);\n+\n+\tfor (i = 0; i < list->sorted.nr; i++) {\n+\t\tstruct string_list_item *sorted_item = list->sorted.items + i;\n+\t\tstruct string_list_item *item = sorted_item->util;\n+\t\tsize_t *len = item->util;\n+\n+\t\t*len = 0;\n+\t\twhile (*len < list->min_length) {\n+\t\t\tchar c = item->string[(*len)++];\n+\n+\t\t\tif (!c || !isascii(c)) {\n+\t\t\t\t*len = 0;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (i > 0)\n+\t\t\textend_prefix_length(item, sorted_item[-1].string,\n+\t\t\t\t\t     list->max_length);\n+\t\tif (i + 1 < list->sorted.nr)\n+\t\t\textend_prefix_length(item, sorted_item[1].string,\n+\t\t\t\t\t     list->max_length);\n+\t}\n+}\n+\n+static ssize_t find_unique(const char *string, struct prefix_item_list *list)\n+{\n+\tint index = string_list_find_insert_index(&list->sorted, string, 1);\n+\tstruct string_list_item *item;\n+\n+\tif (list->items.nr != list->sorted.nr)\n+\t\tBUG(\"prefix_item_list in inconsistent state (%\"PRIuMAX\n+\t\t    \" vs %\"PRIuMAX\")\",\n+\t\t    (uintmax_t)list->items.nr, (uintmax_t)list->sorted.nr);\n+\n+\tif (index < 0)\n+\t\titem = list->sorted.items[-1 - index].util;\n+\telse if (index > 0 &&\n+\t\t starts_with(list->sorted.items[index - 1].string, string))\n+\t\treturn -1;\n+\telse if (index + 1 < list->sorted.nr &&\n+\t\t starts_with(list->sorted.items[index + 1].string, string))\n+\t\treturn -1;\n+\telse if (index < list->sorted.nr)\n+\t\titem = list->sorted.items[index].util;\n+\telse\n+\t\treturn -1;\n+\treturn item - list->items.items;\n+}\n+\n struct list_options {\n \tint columns;\n \tconst char *header;\n@@ -95,18 +221,21 @@ struct list_and_choose_options {\n  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n  * `LIST_AND_CHOOSE_QUIT` is returned.\n  */\n-static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,\n+static ssize_t list_and_choose(struct add_i_state *s,\n+\t\t\t       struct prefix_item_list *items,\n \t\t\t       struct list_and_choose_options *opts)\n {\n \tstruct strbuf input = STRBUF_INIT;\n \tssize_t res = LIST_AND_CHOOSE_ERROR;\n \n+\tfind_unique_prefixes(items);\n+\n \tfor (;;) {\n \t\tchar *p, *endp;\n \n \t\tstrbuf_reset(&input);\n \n-\t\tlist(s, items, &opts->list_opts);\n+\t\tlist(s, &items->items, &opts->list_opts);\n \n \t\tprintf(\"%s%s\", opts->prompt, \"> \");\n \t\tfflush(stdout);\n@@ -140,7 +269,10 @@ static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,\n \t\t\t}\n \n \t\t\tp[sep] = '\\0';\n-\t\t\tif (index < 0 || index >= items->nr)\n+\t\t\tif (index < 0)\n+\t\t\t\tindex = find_unique(p, items);\n+\n+\t\t\tif (index < 0 || index >= items->items.nr)\n \t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n \t\t\telse {\n \t\t\t\tres = index;\n@@ -306,6 +438,23 @@ static void render_adddel(struct strbuf *buf,\n \t\tstrbuf_addstr(buf, no_changes);\n }\n \n+/* filters out prefixes which have special meaning to list_and_choose() */\n+static int is_valid_prefix(const char *prefix, size_t prefix_len)\n+{\n+\treturn prefix_len && prefix &&\n+\t\t/*\n+\t\t * We expect `prefix` to be NUL terminated, therefore this\n+\t\t * `strcspn()` call is okay, even if it might do much more\n+\t\t * work than strictly necessary.\n+\t\t */\n+\t\tstrcspn(prefix, \" \\t\\r\\n,\") >= prefix_len &&\t/* separators */\n+\t\t*prefix != '-' &&\t\t\t\t/* deselection */\n+\t\t!isdigit(*prefix) &&\t\t\t\t/* selection */\n+\t\t(prefix_len != 1 ||\n+\t\t (*prefix != '*' &&\t\t\t\t/* \"all\" wildcard */\n+\t\t  *prefix != '?'));\t\t\t\t/* prompt help */\n+}\n+\n struct print_file_item_data {\n \tconst char *modified_fmt;\n \tstruct strbuf buf, index, worktree;\n@@ -345,10 +494,23 @@ typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,\n \t\t\t struct string_list *files,\n \t\t\t struct list_options *opts);\n \n+struct command_item {\n+\tsize_t prefix_length;\n+\tcommand_t command;\n+};\n+\n static void print_command_item(int i, struct string_list_item *item,\n \t\t\t       void *print_command_item_data)\n {\n-\tprintf(\" %2d: %s\", i + 1, item->string);\n+\tstruct command_item *util = item->util;\n+\n+\tif (!util->prefix_length ||\n+\t    !is_valid_prefix(item->string, util->prefix_length))\n+\t\tprintf(\" %2d: %s\", i + 1, item->string);\n+\telse\n+\t\tprintf(\" %2d: [%.*s]%s\", i + 1,\n+\t\t       (int)util->prefix_length, item->string,\n+\t\t       item->string + util->prefix_length);\n }\n \n int run_add_i(struct repository *r, const struct pathspec *ps)\n@@ -364,7 +526,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t} command_list[] = {\n \t\t{ \"status\", run_status },\n \t};\n-\tstruct string_list commands = STRING_LIST_INIT_NODUP;\n+\tstruct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;\n \n \tstruct print_file_item_data print_file_item_data = {\n \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n@@ -377,9 +539,12 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tssize_t i;\n \tint res = 0;\n \n-\tfor (i = 0; i < ARRAY_SIZE(command_list); i++)\n-\t\tstring_list_append(&commands, command_list[i].string)\n-\t\t\t->util = command_list[i].command;\n+\tfor (i = 0; i < ARRAY_SIZE(command_list); i++) {\n+\t\tstruct command_item *util = xcalloc(sizeof(*util), 1);\n+\t\tutil->command = command_list[i].command;\n+\t\tstring_list_append(&commands.items, command_list[i].string)\n+\t\t\t->util = util;\n+\t}\n \n \tinit_add_i_state(&s, r);\n \n@@ -404,8 +569,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\t\tbreak;\n \t\t}\n \t\tif (i != LIST_AND_CHOOSE_ERROR) {\n-\t\t\tcommand_t command = commands.items[i].util;\n-\t\t\tres = command(&s, ps, &files, &opts);\n+\t\t\tstruct command_item *util =\n+\t\t\t\tcommands.items.items[i].util;\n+\t\t\tres = util->command(&s, ps, &files, &opts);\n \t\t}\n \t}\n \n@@ -414,7 +580,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tstrbuf_release(&print_file_item_data.index);\n \tstrbuf_release(&print_file_item_data.worktree);\n \tstrbuf_release(&header);\n-\tstring_list_clear(&commands, 0);\n+\tprefix_item_list_clear(&commands);\n \n \treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"385754","messageId":"xmqqh83fnfah.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"ff59d2d0b3b8b591a806ef71b4bcfd350000b06e.1572869729.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-11-08T04:49:26Z","receivedAt":"2019-11-08T04:49:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> This is hardly the first conversion of a Git command that is implemented\n> as a script to a built-in. So far, the most successful strategy for such\n> conversions has been to add a built-in helper and call that for more and\n> more functionality from the script, as more and more parts are\n> converted.\n>\n> With the interactive add, we choose a different strategy....\n\nThis is hardly the first conversion that we took the \"build the\nwhole program piece by piece and flip the whole thing on with\nusebuiltin\" conversion successfully.  Pratik's rebase-in-c series\ncomes to mind.\n\nPersonally, I do not think the first two paragraphs of the proposed\nlog message do not belong here.  Cover letter is a different story\nand it may make sense to explain why the approach was taken there,\nbut here, I'd prefer to see it more succinctly tell what approach is\ntaken and go directly to describe what this step in that approach\ndoes to the readers, which is more important.\n\n> diff --git a/Documentation/config/add.txt b/Documentation/config/add.txt\n>  \tvariables.\n> +\n> +add.interactive.useBuiltin::\n> +\t[EXPERIMENTAL] Set to `true` to use the experimental built-in\n> +\timplementation of the interactive version of linkgit:git-add[1]\n> +\tinstead of the Perl script version. Is `false` by default.\n\nGood.\n\n> diff --git a/Makefile b/Makefile\n> index 58b92af54b..6c4a1e0ee5 100644\n> --- a/Makefile\n> +++ b/Makefile\n>  LIB_OBJS += abspath.o\n> +LIB_OBJS += add-interactive.o\n>  LIB_OBJS += advice.o\n>  LIB_OBJS += alias.o\n>  LIB_OBJS += alloc.o\n\nOK.\n\n> diff --git a/add-interactive.c b/add-interactive.c\n> new file mode 100644\n> index 0000000000..482e458dc6\n> --- /dev/null\n> +++ b/add-interactive.c\n> @@ -0,0 +1,7 @@\n> +#include \"cache.h\"\n> +#include \"add-interactive.h\"\n> +\n> +int run_add_i(struct repository *r, const struct pathspec *ps)\n> +{\n> +\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n> +}\n\nOK, with or without s/commands/sub&/;\n\n> diff --git a/add-interactive.h b/add-interactive.h\n> new file mode 100644\n> index 0000000000..7043b8741d\n> --- /dev/null\n> +++ b/add-interactive.h\n\nOK.\n\n> diff --git a/builtin/add.c b/builtin/add.c\n> index dd18e5c9b6..4f625691b5 100644\n> --- a/builtin/add.c\n> +++ b/builtin/add.c\n> @@ -20,6 +20,7 @@\n>  #include \"bulk-checkin.h\"\n>  #include \"argv-array.h\"\n>  #include \"submodule.h\"\n> +#include \"add-interactive.h\"\n>  \n>  static const char * const builtin_add_usage[] = {\n>  \tN_(\"git add [<options>] [--] <pathspec>...\"),\n> @@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,\n>  {\n>  \tint status, i;\n>  \tstruct argv_array argv = ARGV_ARRAY_INIT;\n> +\tint use_builtin_add_i =\n> +\t\tgit_env_bool(\"GIT_TEST_ADD_I_USE_BUILTIN\", -1);\n\nHave blank here at the boundary between decl and stmt ...\n\n> +\tif (use_builtin_add_i < 0)\n> +\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n> +\t\t\t\t    &use_builtin_add_i);\n> +\n\n... and lose it here (optional).\n\n> +\tif (use_builtin_add_i == 1 && !patch_mode)\n> +\t\treturn !!run_add_i(the_repository, pathspec);\n>  \n\nStrictly speaking, we can bypass the probing of environment and\nconfig when upon the entry of the function, where patch_mode is\nalready known.  I do not know offhand if rearranging the code to\ntake advantage of that fact would result in a flow that is also\neasier to follow, but I suspect it would.\n\n> +GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the\n> +built-in version of git add -i. See 'add.interactive.useBuiltin' in\n> +git-config(1).\n\nMakes sense.\n\nThanks.\n"},{"id":"385755","messageId":"xmqqd0e3nexw.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"2fc8cc354690841cd8496d9a7660faf572580aef.1572869730.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 2/9] diff: export diffstat interface","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-11-08T04:56:59Z","receivedAt":"2019-11-08T04:57:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Daniel Ferreira via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Daniel Ferreira <bnmvco@gmail.com>\n>\n> Make the diffstat interface (namely, the diffstat_t struct and\n> compute_diffstat) no longer be internal to diff.c and allow it to be used\n> by other parts of git.\n>\n> This is helpful for code that may want to easily extract information\n> from files using the diff machinery, while flushing it differently from\n> how the show_* functions used by diff_flush() do it. One example is the\n> builtin implementation of git-add--interactive's status.\n>\n> Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>\n> Signed-off-by: Slavica Đukić <slawica92@hotmail.com>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  diff.c | 39 ++++++++++++++++-----------------------\n>  diff.h | 20 ++++++++++++++++++++\n>  2 files changed, 36 insertions(+), 23 deletions(-)\n\nLooks good.  The diffstat API seems to be quite well isolated to\nallow this with minimum (eh, rather no) changes.\n\nThanks.\n\n>\n> diff --git a/diff.c b/diff.c\n> index afe4400a60..5703a9b78f 100644\n> --- a/diff.c\n> +++ b/diff.c\n> @@ -2495,22 +2495,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)\n>  \t}\n>  }\n>  \n> -struct diffstat_t {\n> -\tint nr;\n> -\tint alloc;\n> -\tstruct diffstat_file {\n> -\t\tchar *from_name;\n> -\t\tchar *name;\n> -\t\tchar *print_name;\n> -\t\tconst char *comments;\n> -\t\tunsigned is_unmerged:1;\n> -\t\tunsigned is_binary:1;\n> -\t\tunsigned is_renamed:1;\n> -\t\tunsigned is_interesting:1;\n> -\t\tuintmax_t added, deleted;\n> -\t} **files;\n> -};\n> -\n>  static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,\n>  \t\t\t\t\t  const char *name_a,\n>  \t\t\t\t\t  const char *name_b)\n> @@ -3157,7 +3141,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o\n>  \tgather_dirstat(options, &dir, changed, \"\", 0);\n>  }\n>  \n> -static void free_diffstat_info(struct diffstat_t *diffstat)\n> +void free_diffstat_info(struct diffstat_t *diffstat)\n>  {\n>  \tint i;\n>  \tfor (i = 0; i < diffstat->nr; i++) {\n> @@ -6283,12 +6267,7 @@ void diff_flush(struct diff_options *options)\n>  \t    dirstat_by_line) {\n>  \t\tstruct diffstat_t diffstat;\n>  \n> -\t\tmemset(&diffstat, 0, sizeof(struct diffstat_t));\n> -\t\tfor (i = 0; i < q->nr; i++) {\n> -\t\t\tstruct diff_filepair *p = q->queue[i];\n> -\t\t\tif (check_pair_status(p))\n> -\t\t\t\tdiff_flush_stat(p, options, &diffstat);\n> -\t\t}\n> +\t\tcompute_diffstat(options, &diffstat, q);\n>  \t\tif (output_format & DIFF_FORMAT_NUMSTAT)\n>  \t\t\tshow_numstat(&diffstat, options);\n>  \t\tif (output_format & DIFF_FORMAT_DIFFSTAT)\n> @@ -6621,6 +6600,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)\n>  \treturn ignored;\n>  }\n>  \n> +void compute_diffstat(struct diff_options *options,\n> +\t\t      struct diffstat_t *diffstat,\n> +\t\t      struct diff_queue_struct *q)\n> +{\n> +\tint i;\n> +\n> +\tmemset(diffstat, 0, sizeof(struct diffstat_t));\n> +\tfor (i = 0; i < q->nr; i++) {\n> +\t\tstruct diff_filepair *p = q->queue[i];\n> +\t\tif (check_pair_status(p))\n> +\t\t\tdiff_flush_stat(p, options, diffstat);\n> +\t}\n> +}\n> +\n>  void diff_addremove(struct diff_options *options,\n>  \t\t    int addremove, unsigned mode,\n>  \t\t    const struct object_id *oid,\n> diff --git a/diff.h b/diff.h\n> index 7f8f024feb..d986ddc3b5 100644\n> --- a/diff.h\n> +++ b/diff.h\n> @@ -245,6 +245,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);\n>  void diff_emit_submodule_pipethrough(struct diff_options *o,\n>  \t\t\t\t     const char *line, int len);\n>  \n> +struct diffstat_t {\n> +\tint nr;\n> +\tint alloc;\n> +\tstruct diffstat_file {\n> +\t\tchar *from_name;\n> +\t\tchar *name;\n> +\t\tchar *print_name;\n> +\t\tconst char *comments;\n> +\t\tunsigned is_unmerged:1;\n> +\t\tunsigned is_binary:1;\n> +\t\tunsigned is_renamed:1;\n> +\t\tunsigned is_interesting:1;\n> +\t\tuintmax_t added, deleted;\n> +\t} **files;\n> +};\n> +\n>  enum color_diff {\n>  \tDIFF_RESET = 0,\n>  \tDIFF_CONTEXT = 1,\n> @@ -334,6 +350,10 @@ void diff_change(struct diff_options *,\n>  \n>  struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);\n>  \n> +void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,\n> +\t\t      struct diff_queue_struct *q);\n> +void free_diffstat_info(struct diffstat_t *diffstat);\n> +\n>  #define DIFF_SETUP_REVERSE      \t1\n>  #define DIFF_SETUP_USE_SIZE_CACHE\t4\n"},{"id":"385756","messageId":"xmqq8sorner2.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"6aaa0de4f4564b1d8da0691851dac0d22f87e565.1572869730.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 3/9] built-in add -i: implement the `status` command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-11-08T05:01:05Z","receivedAt":"2019-11-08T05:01:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Daniel Ferreira via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Daniel Ferreira <bnmvco@gmail.com>\n>\n> This implements the `status` command of `git add -i`. The data\n> structures introduced in this commit will be extended later, as needed.\n>\n> At this point, we re-implement only part of the `list_and_choose()`\n> function of the Perl script `git-add--interactive.perl` and call it\n> `list()`. It does not yet color anything, or do columns, or allow user\n> input.\n\nOK, so that is why we unconditionally do the status thing directly\ninside run_add_i() and nothing else.  Makes sense.\n\n> Over the course of the next commits, we will introduce a\n> `list_and_choose()` function that uses `list()` to display the list of\n> options and let the user choose one or more of the displayed items. This\n> will be used to implement the main loop of the built-in `git add -i`, at\n> which point the new `status` command can actually be used.\n\nVery well written.\n"},{"id":"385757","messageId":"xmqq1rujndza.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"25590fbbbee7efc34477bfea233684e93ee7fe60.1572869730.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 5/9] built-in add -i: implement the main loop","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-11-08T05:17:45Z","receivedAt":"2019-11-08T05:17:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> The reason why we did not start with the main loop to begin with is that\n> it is the first user of `list_and_choose()`, which uses the `list()`\n> function that we conveniently introduced for use by the `status`\n> command.\n>\n> Apart from the \"and choose\" part, there are more differences between the\n> way the `status` command calls the `list_and_choose()` function in the\n> Perl version of `git add -i` compared to the other callers of said\n> function. The most important ones:\n>\n> - The list is not only shown, but the user is also asked to make a\n>   choice, possibly selecting multiple entries.\n\nThe list_and_choose() we have here shows and lets users choose and\nreturns the choice, but the above makes it sound as if it only shows\nand the caller is responsible for asking the end-user input.  Is\nthis description outdated or something?\n\nPerl allows us to return multiple choices, where it is a bit hard to\nexpress it in C (perhaps because we are passing in an array of\nstructs to be shown as choices, list_and_choose could set a bit in\nthese structs to signal \"this one, that one and that other one was\nchosen\", returning how many are chosen in total, to extend the\nversion here to bring it to feature-parity?).  So at this step, it\nonly lets the user one choice (or abort or ask for help).  Isn't the\nlack of multiple choice the only difference this bullet item wants\nto highlight?\n\n> The Perl script `git-add--interactive.perl` mixed the purposes of the\n> \"list\" and the \"and choose\" part into the same function. In the C\n> version, we will keep them separate instead, calling the `list()`\n> function from the `list_and_choose()` function.\n\nThat makes sense.\n\n> +static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,\n> +\t\t\t       struct list_and_choose_options *opts)\n> +{\n> +\tstruct strbuf input = STRBUF_INIT;\n> +\tssize_t res = LIST_AND_CHOOSE_ERROR;\n> +\n> +\tfor (;;) {\n> +\t\tchar *p, *endp;\n\nThe scope of endp looks way too wide in this function, isn't it?\nEven in the final state of the series, it only gets used to parse\nan integer input using strtoul, inside a block of three lines.\n\nOther than that, the code at this step was a pleasant read overall.\n\nThanks.\n"},{"id":"385830","messageId":"nycvar.QRO.7.76.6.1911091154550.46@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"xmqqh83fnfah.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-11-09T11:06:07Z","receivedAt":"2019-11-09T11:06:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 8 Nov 2019, Junio C Hamano wrote:\n\n> \"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\n> writes:\n>\n> > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> >\n> > This is hardly the first conversion of a Git command that is implemented\n> > as a script to a built-in. So far, the most successful strategy for such\n> > conversions has been to add a built-in helper and call that for more and\n> > more functionality from the script, as more and more parts are\n> > converted.\n> >\n> > With the interactive add, we choose a different strategy....\n>\n> This is hardly the first conversion that we took the \"build the\n> whole program piece by piece and flip the whole thing on with\n> usebuiltin\" conversion successfully.  Pratik's rebase-in-c series\n> comes to mind.\n>\n> Personally, I do not think the first two paragraphs of the proposed\n> log message do not belong here.  Cover letter is a different story\n> and it may make sense to explain why the approach was taken there,\n> but here, I'd prefer to see it more succinctly tell what approach is\n> taken and go directly to describe what this step in that approach\n> does to the readers, which is more important.\n\nI reworded the commit message:\n\n    Start to implement a built-in version of `git add --interactive`\n\n    To convert the interactive `add` to C, we start with a bare-bones\n    version of the built-in interactive add, guarded by the new\n    `add.interactive.useBuiltin` config variable, and then add more and more\n    functionality to it, until it is feature complete.\n\n    This is in contrast to previous conversions to C, where we started with\n    a built-in helper that spawns the script by default, but optionally\n    executes the C code instead. The sole reason for this deviation from\n    previous practice is that on Windows (where such a conversion has the\n    most benefits in terms of speed and robustness) we face the very\n    specific problem that a `system()` call in Perl seems to close `stdin`\n    in the parent process when the spawned process consumes even one\n    character from `stdin`. And that just does not work for us here, as it\n    would stop the main loop as soon as any interactive command was\n    performed by the helper. Which is almost all of the commands in `git add\n    -i`.\n\n    It is almost as if Perl told us once again that it does not want us to\n    use it on Windows.\n\n    At this point, the built-in version of `git add -i` only states that it\n    cannot do anything yet ;-)\n\nHopefully you like this one better?\n\n> > diff --git a/builtin/add.c b/builtin/add.c\n> > index dd18e5c9b6..4f625691b5 100644\n> > --- a/builtin/add.c\n> > +++ b/builtin/add.c\n> > @@ -20,6 +20,7 @@\n> >  #include \"bulk-checkin.h\"\n> >  #include \"argv-array.h\"\n> >  #include \"submodule.h\"\n> > +#include \"add-interactive.h\"\n> >\n> >  static const char * const builtin_add_usage[] = {\n> >  \tN_(\"git add [<options>] [--] <pathspec>...\"),\n> > @@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode,\n> >  {\n> >  \tint status, i;\n> >  \tstruct argv_array argv = ARGV_ARRAY_INIT;\n> > +\tint use_builtin_add_i =\n> > +\t\tgit_env_bool(\"GIT_TEST_ADD_I_USE_BUILTIN\", -1);\n>\n> Have blank here at the boundary between decl and stmt ...\n>\n> > +\tif (use_builtin_add_i < 0)\n> > +\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n> > +\t\t\t\t    &use_builtin_add_i);\n> > +\n>\n> ... and lose it here (optional).\n\nDone.\n>\n> > +\tif (use_builtin_add_i == 1 && !patch_mode)\n> > +\t\treturn !!run_add_i(the_repository, pathspec);\n> >\n>\n> Strictly speaking, we can bypass the probing of environment and\n> config when upon the entry of the function, where patch_mode is\n> already known.  I do not know offhand if rearranging the code to\n> take advantage of that fact would result in a flow that is also\n> easier to follow, but I suspect it would.\n\nOkay. I changed it to:\n\n\tif (!patch_mode) {\n\t\tif (use_builtin_add_i < 0)\n\t\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n\t\t\t\t\t    &use_builtin_add_i);\n\t\tif (use_builtin_add_i == 1)\n\t\t\treturn !!run_add_i(the_repository, pathspec);\n\t}\n\nThanks,\nDscho\n"},{"id":"385831","messageId":"nycvar.QRO.7.76.6.1911091211560.46@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"xmqq1rujndza.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v5 5/9] built-in add -i: implement the main loop","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-11-09T11:21:17Z","receivedAt":"2019-11-09T11:21:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 8 Nov 2019, Junio C Hamano wrote:\n\n> \"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\n> writes:\n>\n> > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> >\n> > The reason why we did not start with the main loop to begin with is that\n> > it is the first user of `list_and_choose()`, which uses the `list()`\n> > function that we conveniently introduced for use by the `status`\n> > command.\n> >\n> > Apart from the \"and choose\" part, there are more differences between the\n> > way the `status` command calls the `list_and_choose()` function in the\n> > Perl version of `git add -i` compared to the other callers of said\n> > function. The most important ones:\n> >\n> > - The list is not only shown, but the user is also asked to make a\n> >   choice, possibly selecting multiple entries.\n>\n> The list_and_choose() we have here shows and lets users choose and\n> returns the choice, but the above makes it sound as if it only shows\n> and the caller is responsible for asking the end-user input.  Is\n> this description outdated or something?\n>\n> Perl allows us to return multiple choices, where it is a bit hard to\n> express it in C (perhaps because we are passing in an array of\n> structs to be shown as choices, list_and_choose could set a bit in\n> these structs to signal \"this one, that one and that other one was\n> chosen\", returning how many are chosen in total, to extend the\n> version here to bring it to feature-parity?).  So at this step, it\n> only lets the user one choice (or abort or ask for help).  Isn't the\n> lack of multiple choice the only difference this bullet item wants\n> to highlight?\n\nI changed the commit message to:\n\n    built-in add -i: implement the main loop\n\n    The reason why we did not start with the main loop to begin with is that\n    it is the first user of `list_and_choose()`, which uses the `list()`\n    function that we conveniently introduced for use by the `status`\n    command.\n\n    In contrast to the Perl version, in the built-in interactive `add`, we\n    will keep the `list()` function (which only displays items) and the\n    `list_and_choose()` function (which uses `list()` to display the items,\n    and only takes care of the \"and choose\" part) separate.\n\n    The `list_and_choose()` function, as implemented in\n    `git-add--interactive.perl` knows a few more tricks than the function we\n    introduce in this patch:\n\n    - There is a flag to let the user select multiple items.\n\n    - In multi-select mode, the list of items is prefixed with a marker\n      indicating what items have been selected.\n\n    - Initially, for each item a unique prefix is determined (if there\n      exists any within the given parameters), and shown in the list, and\n      accepted as a shortcut for the selection.\n\n    These features will be implemented in the C version later.\n\n    This patch does not add any new main loop command, of course, the\n    built-in `git add -i` still only supports the `status` command. The\n    remaining commands to follow over the course of the next commits.\n\n    To accommodate for listing the commands in columns, preparing for the\n    commands that will be implemented over the course of the next\n    patches/patch series, we teach the `list()` function to do precisely\n    that.\n\n    Note that we only have a prompt ending in a single \">\" at this stage;\n    later commits will add commands that display a double \">>\" to indicate\n    that the user is in a different loop than the main one.\n\n> > The Perl script `git-add--interactive.perl` mixed the purposes of the\n> > \"list\" and the \"and choose\" part into the same function. In the C\n> > version, we will keep them separate instead, calling the `list()`\n> > function from the `list_and_choose()` function.\n>\n> That makes sense.\n>\n> > +static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,\n> > +\t\t\t       struct list_and_choose_options *opts)\n> > +{\n> > +\tstruct strbuf input = STRBUF_INIT;\n> > +\tssize_t res = LIST_AND_CHOOSE_ERROR;\n> > +\n> > +\tfor (;;) {\n> > +\t\tchar *p, *endp;\n>\n> The scope of endp looks way too wide in this function, isn't it?\n> Even in the final state of the series, it only gets used to parse\n> an integer input using strtoul, inside a block of three lines.\n\nTrue. I moved the declaration of `endp` into that three-line (now\nfour-line) scope.\n\n> Other than that, the code at this step was a pleasant read overall.\n\nThank you for your review!\nDscho\n"},{"id":"385843","messageId":"xmqqtv7cjj2n.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"nycvar.QRO.7.76.6.1911091154550.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-11-10T07:18:08Z","receivedAt":"2019-11-10T07:18:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> I reworded the commit message:\n>\n>     Start to implement a built-in version of `git add --interactive`\n>\n>     To convert the interactive `add` to C, we start with a bare-bones\n>     version of the built-in interactive add, guarded by the new\n>     `add.interactive.useBuiltin` config variable, and then add more and more\n>     functionality to it, until it is feature complete.\n>\n>     This is in contrast to previous conversions to C, where we started with\n>     a built-in helper that spawns the script by default, but optionally\n>     executes the C code instead. The sole reason for this deviation from\n>     previous practice is that on Windows (where such a conversion has the\n>     most benefits in terms of speed and robustness) we face the very\n>     specific problem that a `system()` call in Perl seems to close `stdin`\n>     in the parent process when the spawned process consumes even one\n>     character from `stdin`. And that just does not work for us here, as it\n>     would stop the main loop as soon as any interactive command was\n>     performed by the helper. Which is almost all of the commands in `git add\n>     -i`.\n>\n>     It is almost as if Perl told us once again that it does not want us to\n>     use it on Windows.\n>\n>     At this point, the built-in version of `git add -i` only states that it\n>     cannot do anything yet ;-)\n>\n> Hopefully you like this one better?\n\nNot really.  I find the \"we could do the other way but we don't, and\nI hate Perl\" totally irrelevant and misleading.\n\nUnless it is in GSoC or something that wants to avoid a total\nfailure of nothing to show at the end of the period, in which case a\npiecemeal \"we did not finish, but at least we have a handful of\nsubcommands rewritten to show for the consolation prize\" might be a\nway to have \"something\" that resembles \"working\".  But if we value\nthe quality of the final product over having to have something to\nshow in a set term (like you as a paid programmer working as a\nprofessional), building piece by piece in the final framework\n(i.e. \"in C as a builtin\") and flipping the \"useBuiltin\" to turn on\nthe whole thing at the end would be the preferrable way to do this\nkind of thing, I would think.  Also, it is less wasteful, not having\nto worry about the inter-language glue code.  Even if the original\nis in shell, not in Perl, we have quite a lot of glue code to throw\nvalues back and forth across the language boundary to imitate what\nused to be mere assignments to global shell variables between shell\nfunctions and their callers, and always somebody screws up quoting\nthere ;-)\n\n>> > +\tif (use_builtin_add_i == 1 && !patch_mode)\n>> > +\t\treturn !!run_add_i(the_repository, pathspec);\n>> >\n>>\n>> Strictly speaking, we can bypass the probing of environment and\n>> config when upon the entry of the function, where patch_mode is\n>> already known.  I do not know offhand if rearranging the code to\n>> take advantage of that fact would result in a flow that is also\n>> easier to follow, but I suspect it would.\n>\n> Okay. I changed it to:\n>\n> \tif (!patch_mode) {\n> \t\tif (use_builtin_add_i < 0)\n> \t\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n> \t\t\t\t\t    &use_builtin_add_i);\n> \t\tif (use_builtin_add_i == 1)\n> \t\t\treturn !!run_add_i(the_repository, pathspec);\n> \t}\n\nDoesn't look so bad as I feared.\n\nThanks.\n"},{"id":"385900","messageId":"nycvar.QRO.7.76.6.1911110949590.46@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"xmqqtv7cjj2n.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-11-11T09:15:00Z","receivedAt":"2019-11-11T09:15:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Sun, 10 Nov 2019, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>\n> > I reworded the commit message:\n> >\n> >     Start to implement a built-in version of `git add --interactive`\n> >\n> >     To convert the interactive `add` to C, we start with a bare-bones\n> >     version of the built-in interactive add, guarded by the new\n> >     `add.interactive.useBuiltin` config variable, and then add more and more\n> >     functionality to it, until it is feature complete.\n> >\n> >     This is in contrast to previous conversions to C, where we started with\n> >     a built-in helper that spawns the script by default, but optionally\n> >     executes the C code instead. The sole reason for this deviation from\n> >     previous practice is that on Windows (where such a conversion has the\n> >     most benefits in terms of speed and robustness) we face the very\n> >     specific problem that a `system()` call in Perl seems to close `stdin`\n> >     in the parent process when the spawned process consumes even one\n> >     character from `stdin`. And that just does not work for us here, as it\n> >     would stop the main loop as soon as any interactive command was\n> >     performed by the helper. Which is almost all of the commands in `git add\n> >     -i`.\n> >\n> >     It is almost as if Perl told us once again that it does not want us to\n> >     use it on Windows.\n> >\n> >     At this point, the built-in version of `git add -i` only states that it\n> >     cannot do anything yet ;-)\n> >\n> > Hopefully you like this one better?\n>\n> Not really.  I find the \"we could do the other way but we don't, and\n> I hate Perl\" totally irrelevant and misleading.\n\nOkay, I understand that you take exception at my criticism of Git's use\nof Perl, and I fully understand that you think I blame you for it\nbecause you added most of it.\n\nAnd I agree that this sidetrack is totally irrelevant for the patch\nunder discussion.\n\nI do think, however, that the discussion of \"we wanted to do it the\nother way, but when we tried, it did not work\" is relevant, even if I\nshortened it to \"we use a different approach than previous conversions,\nbecause that previous approach would not work\".\n\nTruth be told: I would have _much rather_ stayed with the previous\n`--helper` approach, as that would have made it possible to have a\npassing test suite at every step, with and without\n`GIT_TEST_ADD_I_USE_BUILTIN=true`.\n\nThe \"let GIT_TEST_ADD_I_USE_BUILTIN=true use the built-in even for\nfunctions we _know_ are not implemented\" way only gives us the full\ncomfort of a passing test suite at the very end of all six patch series,\nif which the patch series we are currently discussing is merely the\nfirst.\n\nIf I was a reviewer of this patch series rather than the sender, I would\nbe a bit uncomfortable with the fact that `GIT_TEST_ADD_I_USE_BUILTIN`\ncannot be added to the CI/PR builds' `linux-gcc` over-job, not until\nmuch, much later. In fact, it can only be added as the very last patch\nin the very last of the six patch series.\n\nAnd as I write this, I realize that I never spelled that out in the\ncommit message, and it is a rather important point for reviewers to see\naddressed pre-emptively, in my opinion.\n\nTherefore I revised the commit message again:\n\n    Start to implement a built-in version of `git add --interactive`\n\n    Unlike previous conversions to C, where we started with a built-in\n    helper, we start this conversion by adding an interception in the\n    `run_add_interactive()` function when the new opt-in\n    `add.interactive.useBuiltin` config knob is turned on (or the\n    corresponding environment variable `GIT_TEST_ADD_I_USE_BUILTIN`), and\n    calling the new internal API function `run_add_i()` that is implemented\n    directly in libgit.a.\n\n    At this point, the built-in version of `git add -i` only states that it\n    cannot do anything yet. In subsequent patches/patch series, the\n    `run_add_i()` function will gain more and more functionality, until it\n    is feature complete. The whole arc of the conversion can be found in the\n    PRs #170-175 at https://github.com/gitgitgadget/git.\n\n    The \"--helper approach\" can unfortunately not be used here: on Windows\n    we face the very specific problem that a `system()` call in\n    Perl seems to close `stdin` in the parent process when the spawned\n    process consumes even one character from `stdin`. Which prevents us from\n    implementing the main loop in C and still trying to hand off to the Perl\n    script.\n\n    The very real downside of the approach we have to take here is that the\n    test suite won't pass with `GIT_TEST_ADD_I_USE_BUILTIN=true` until the\n    conversion is complete (the `--helper` approach would have let it pass,\n    even at each of the incremental conversion steps).\n\nAny suggestions how to improve that commit message?\n\nCiao,\nDscho\n"},{"id":"385903","messageId":"xmqqeeyehawj.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"nycvar.QRO.7.76.6.1911110949590.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-11-11T12:09:48Z","receivedAt":"2019-11-11T12:09:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> And I agree that this sidetrack is totally irrelevant for the patch\n> under discussion.\n>\n> I do think, however, that the discussion of \"we wanted to do it the\n> other way, but when we tried, it did not work\" is relevant, even if I\n> shortened it to \"we use a different approach than previous conversions,\n> because that previous approach would not work\".\n\nRegardless of the language the scripted version was written in, I\nthink the '--helper' approach is always the poorer choice between\nthe two [*1*].  It limits the modular decomposition to what suits the\noriginal language, the impedance mismatch between the original and\ntarget language forces us to unnatural style of inter module\ncommunication, and the unnatural interface layer, which we know has\nto be discarded at the end, must be written [*2*].\n\nSo, I'd prefer to see \"because this is a better way in the longer\nterm\" over \"because the --helper approach would not work\".\n\n[Footnote]\n\n*1* In only one case I would recommend using \"--helper\" approach,\n    though.  When you are not expecting the developer to be able to\n    come up with a better split of the program into modules than how\n    the scripted version is, and you want to ensure that the\n    developer have something to show when they faild to complete the\n    project after N weeks.  You are a more experienced developer\n    than an average GSoC student, and there is no pencils-down time,\n    so the exception would not apply.\n\n*2* In \"git submodule\" for example it was quite natural for the\n    module that gives a list of submodules with its traits the\n    program cares about to be written as a shell function that\n    writes the data to its standard output.  And consuming modules\n    sit at the downstream of a pipe, accepting its output.  When you\n    are writing these modules both in C, you wouldn't connect them\n    with pipe to carry the list of submodules, but a piecemeal\n    conversion using the \"--helper\" approach meant that there always\n    remained _some_ consumer that wants to read from the pipe, so\n    long after the module lister was rewritten in C, it still needed\n    to support a mode where it sends its output to the pipe, instead\n    of just passing an array of structures.\n"},{"id":"386016","messageId":"nycvar.QRO.7.76.6.1911121459270.46@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"xmqqeeyehawj.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-11-12T15:03:45Z","receivedAt":"2019-11-12T15:04:14Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Mon, 11 Nov 2019, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>\n> > And I agree that this sidetrack is totally irrelevant for the patch\n> > under discussion.\n> >\n> > I do think, however, that the discussion of \"we wanted to do it the\n> > other way, but when we tried, it did not work\" is relevant, even if I\n> > shortened it to \"we use a different approach than previous conversions,\n> > because that previous approach would not work\".\n>\n> Regardless of the language the scripted version was written in, I\n> think the '--helper' approach is always the poorer choice between\n> the two [*1*].  It limits the modular decomposition to what suits the\n> original language, the impedance mismatch between the original and\n> target language forces us to unnatural style of inter module\n> communication, and the unnatural interface layer, which we know has\n> to be discarded at the end, must be written [*2*].\n>\n> So, I'd prefer to see \"because this is a better way in the longer\n> term\" over \"because the --helper approach would not work\".\n\nHmm. I feel distinctly unheard.\n\nIt may appear compelling, conceptually, to shun the `--helper` approach,\nbut the more important reality is that it is the only one that makes an\nincremental conversion possible at all.\n\nIt took an entire month of 60-hour weeks to complete the conversion of\n`git add -i`/`git add -p` to C, and only at the very end was I able to\nrun the test suite with `GIT_TEST_ADD_I_USE_BUILTIN=true` and see it\npass.\n\nThat is an awfully long time, and you know fully well that this amount\nof work equates to three to four Outreachy/GSoC seasons. That would be\nan insane amount of time to go without the confidence of a passing test\nsuite.\n\nIn contrast, we were able to complete the conversions of the interactive\nrebase as well as of `git stash` within _a single season_. I attribute a\nlarge part of that success to the ability to keep the tests green during\nthe incremental conversion _because of_ the `--helper` approach.\n\nSo no, I do not think that your suggestion to reword the commit message\nis something we want to do.  Instead, I think the commit message needs\nto be rephrased until I get the point across clearly.\n\nIt is indeed _in spite of_ the success of the `--helper` approach that\nwe cannot use it here.\n\nCiao,\nDscho\n\n>\n> [Footnote]\n>\n> *1* In only one case I would recommend using \"--helper\" approach,\n>     though.  When you are not expecting the developer to be able to\n>     come up with a better split of the program into modules than how\n>     the scripted version is, and you want to ensure that the\n>     developer have something to show when they faild to complete the\n>     project after N weeks.  You are a more experienced developer\n>     than an average GSoC student, and there is no pencils-down time,\n>     so the exception would not apply.\n>\n> *2* In \"git submodule\" for example it was quite natural for the\n>     module that gives a list of submodules with its traits the\n>     program cares about to be written as a shell function that\n>     writes the data to its standard output.  And consuming modules\n>     sit at the downstream of a pipe, accepting its output.  When you\n>     are writing these modules both in C, you wouldn't connect them\n>     with pipe to carry the list of submodules, but a piecemeal\n>     conversion using the \"--helper\" approach meant that there always\n>     remained _some_ consumer that wants to read from the pipe, so\n>     long after the module lister was rewritten in C, it still needed\n>     to support a mode where it sends its output to the pipe, instead\n>     of just passing an array of structures.\n>\n"},{"id":"386092","messageId":"xmqqd0dwbfd6.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"nycvar.QRO.7.76.6.1911121459270.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-11-13T03:54:29Z","receivedAt":"2019-11-13T03:54:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> Regardless of the language the scripted version was written in, I\n>> think the '--helper' approach is always the poorer choice between\n>> the two [*1*].  It limits the modular decomposition to what suits the\n>> original language, the impedance mismatch between the original and\n>> target language forces us to unnatural style of inter module\n>> communication, and the unnatural interface layer, which we know has\n>> to be discarded at the end, must be written [*2*].\n>>\n>> So, I'd prefer to see \"because this is a better way in the longer\n>> term\" over \"because the --helper approach would not work\".\n>\n> Hmm. I feel distinctly unheard.\n\nThe feeling is mutual ;-)\n\n> It may appear compelling, conceptually, to shun the `--helper` approach,\n> but the more important reality is that it is the only one that makes an\n> incremental conversion possible at all.\n>\n> It took an entire month of 60-hour weeks to complete the conversion of\n> `git add -i`/`git add -p` to C, and only at the very end was I able to\n> run the test suite with `GIT_TEST_ADD_I_USE_BUILTIN=true` and see it\n> pass.\n\nYeah, that is developer comfort, and of course it is nice to have\nthan not to have it.\n\nBut compared to the downside impact to the quality of end result\nthat is inherent to the '--helper' approach, I'd prioritize the\nquality of the end result over developer comfort.\n\n> It is indeed _in spite of_ the success of the `--helper` approach that\n> we cannot use it here.\n\nAs I do not see those past '--helper' ones necessarily successes, we\nmust agree to disagree here.\n\nIn any case, the log message needs to express why _you_ ended up\ntaking the non-helper approach.  Even though it is far less\nrelevant, compared to that, what other approach you instead wanted\nto take, I do not veto you from having your own opinion.\n\n>> [Footnote]\n>>\n>> *1* In only one case I would recommend using \"--helper\" approach,\n>>     though.  When you are not expecting the developer to be able to\n>>     come up with a better split of the program into modules than how\n>>     the scripted version is, and you want to ensure that the\n>>     developer have something to show when they faild to complete the\n>>     project after N weeks.  You are a more experienced developer\n>>     than an average GSoC student, and there is no pencils-down time,\n>>     so the exception would not apply.\n>>\n>> *2* In \"git submodule\" for example it was quite natural for the\n>>     module that gives a list of submodules with its traits the\n>>     program cares about to be written as a shell function that\n>>     writes the data to its standard output.  And consuming modules\n>>     sit at the downstream of a pipe, accepting its output.  When you\n>>     are writing these modules both in C, you wouldn't connect them\n>>     with pipe to carry the list of submodules, but a piecemeal\n>>     conversion using the \"--helper\" approach meant that there always\n>>     remained _some_ consumer that wants to read from the pipe, so\n>>     long after the module lister was rewritten in C, it still needed\n>>     to support a mode where it sends its output to the pipe, instead\n>>     of just passing an array of structures.\n>>\n"},{"id":"386109","messageId":"nycvar.QRO.7.76.6.1911131326300.46@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"xmqqd0dwbfd6.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-11-13T12:30:05Z","receivedAt":"2019-11-13T12:30:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Wed, 13 Nov 2019, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>\n> >> Regardless of the language the scripted version was written in, I\n> >> think the '--helper' approach is always the poorer choice between\n> >> the two [*1*].  It limits the modular decomposition to what suits the\n> >> original language, the impedance mismatch between the original and\n> >> target language forces us to unnatural style of inter module\n> >> communication, and the unnatural interface layer, which we know has\n> >> to be discarded at the end, must be written [*2*].\n> >>\n> >> So, I'd prefer to see \"because this is a better way in the longer\n> >> term\" over \"because the --helper approach would not work\".\n> >\n> > Hmm. I feel distinctly unheard.\n>\n> The feeling is mutual ;-)\n\nMaybe I could take your comments more seriously if you acknowledged the\nfact that I am _very_ familiar with the vagaries of converting scripts\nto C. Like, very, very, very familiar.\n\n> > It may appear compelling, conceptually, to shun the `--helper` approach,\n> > but the more important reality is that it is the only one that makes an\n> > incremental conversion possible at all.\n> >\n> > It took an entire month of 60-hour weeks to complete the conversion of\n> > `git add -i`/`git add -p` to C, and only at the very end was I able to\n> > run the test suite with `GIT_TEST_ADD_I_USE_BUILTIN=true` and see it\n> > pass.\n>\n> Yeah, that is developer comfort, and of course it is nice to have\n> than not to have it.\n\nComfort has little to do with it. Driving out bugs has a lot more to do\nwith it. Which is the point I am trying to get across the entire time.\n\n> But compared to the downside impact to the quality of end result\n> that is inherent to the '--helper' approach, I'd prioritize the\n> quality of the end result over developer comfort.\n>\n> > It is indeed _in spite of_ the success of the `--helper` approach that\n> > we cannot use it here.\n>\n> As I do not see those past '--helper' ones necessarily successes, we\n> must agree to disagree here.\n\nRight. But if I recall, you never even saw the need for the conversions\nin the first place. Maybe you still don't?\n\n> In any case, the log message needs to express why _you_ ended up\n> taking the non-helper approach.  Even though it is far less\n> relevant, compared to that, what other approach you instead wanted\n> to take, I do not veto you from having your own opinion.\n\nOkay. I will take that as an indication that I can go forward with the\nlatest proposal. After all, I described pretty well, I think, why _I_\nended up taking the non-helper approach.\n\nThanks,\nDscho\n\n> >> [Footnote]\n> >>\n> >> *1* In only one case I would recommend using \"--helper\" approach,\n> >>     though.  When you are not expecting the developer to be able to\n> >>     come up with a better split of the program into modules than how\n> >>     the scripted version is, and you want to ensure that the\n> >>     developer have something to show when they faild to complete the\n> >>     project after N weeks.  You are a more experienced developer\n> >>     than an average GSoC student, and there is no pencils-down time,\n> >>     so the exception would not apply.\n> >>\n> >> *2* In \"git submodule\" for example it was quite natural for the\n> >>     module that gives a list of submodules with its traits the\n> >>     program cares about to be written as a shell function that\n> >>     writes the data to its standard output.  And consuming modules\n> >>     sit at the downstream of a pipe, accepting its output.  When you\n> >>     are writing these modules both in C, you wouldn't connect them\n> >>     with pipe to carry the list of submodules, but a piecemeal\n> >>     conversion using the \"--helper\" approach meant that there always\n> >>     remained _some_ consumer that wants to read from the pipe, so\n> >>     long after the module lister was rewritten in C, it still needed\n> >>     to support a mode where it sends its output to the pipe, instead\n> >>     of just passing an array of structures.\n> >>\n>\n"},{"id":"386110","messageId":"f42d7b13107743fc1aa9cc42ee188fdeb10c702b.1573648866.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v6.git.1573648866.gitgitgadget@gmail.com","subject":"[PATCH v6 2/9] diff: export diffstat interface","fromName":"Daniel Ferreira via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-13T12:40:58Z","receivedAt":"2019-11-13T12:41:12Z","isPatch":true,"sender":{"key":"bnmvco@gmail.com","avatar":"https://avatars.githubusercontent.com/u/735397?v=4"},"body":"From: Daniel Ferreira <bnmvco@gmail.com>\n\nMake the diffstat interface (namely, the diffstat_t struct and\ncompute_diffstat) no longer be internal to diff.c and allow it to be used\nby other parts of git.\n\nThis is helpful for code that may want to easily extract information\nfrom files using the diff machinery, while flushing it differently from\nhow the show_* functions used by diff_flush() do it. One example is the\nbuiltin implementation of git-add--interactive's status.\n\nSigned-off-by: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Đukić <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 39 ++++++++++++++++-----------------------\n diff.h | 20 ++++++++++++++++++++\n 2 files changed, 36 insertions(+), 23 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex afe4400a60..5703a9b78f 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -2495,22 +2495,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)\n \t}\n }\n \n-struct diffstat_t {\n-\tint nr;\n-\tint alloc;\n-\tstruct diffstat_file {\n-\t\tchar *from_name;\n-\t\tchar *name;\n-\t\tchar *print_name;\n-\t\tconst char *comments;\n-\t\tunsigned is_unmerged:1;\n-\t\tunsigned is_binary:1;\n-\t\tunsigned is_renamed:1;\n-\t\tunsigned is_interesting:1;\n-\t\tuintmax_t added, deleted;\n-\t} **files;\n-};\n-\n static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,\n \t\t\t\t\t  const char *name_a,\n \t\t\t\t\t  const char *name_b)\n@@ -3157,7 +3141,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o\n \tgather_dirstat(options, &dir, changed, \"\", 0);\n }\n \n-static void free_diffstat_info(struct diffstat_t *diffstat)\n+void free_diffstat_info(struct diffstat_t *diffstat)\n {\n \tint i;\n \tfor (i = 0; i < diffstat->nr; i++) {\n@@ -6283,12 +6267,7 @@ void diff_flush(struct diff_options *options)\n \t    dirstat_by_line) {\n \t\tstruct diffstat_t diffstat;\n \n-\t\tmemset(&diffstat, 0, sizeof(struct diffstat_t));\n-\t\tfor (i = 0; i < q->nr; i++) {\n-\t\t\tstruct diff_filepair *p = q->queue[i];\n-\t\t\tif (check_pair_status(p))\n-\t\t\t\tdiff_flush_stat(p, options, &diffstat);\n-\t\t}\n+\t\tcompute_diffstat(options, &diffstat, q);\n \t\tif (output_format & DIFF_FORMAT_NUMSTAT)\n \t\t\tshow_numstat(&diffstat, options);\n \t\tif (output_format & DIFF_FORMAT_DIFFSTAT)\n@@ -6621,6 +6600,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)\n \treturn ignored;\n }\n \n+void compute_diffstat(struct diff_options *options,\n+\t\t      struct diffstat_t *diffstat,\n+\t\t      struct diff_queue_struct *q)\n+{\n+\tint i;\n+\n+\tmemset(diffstat, 0, sizeof(struct diffstat_t));\n+\tfor (i = 0; i < q->nr; i++) {\n+\t\tstruct diff_filepair *p = q->queue[i];\n+\t\tif (check_pair_status(p))\n+\t\t\tdiff_flush_stat(p, options, diffstat);\n+\t}\n+}\n+\n void diff_addremove(struct diff_options *options,\n \t\t    int addremove, unsigned mode,\n \t\t    const struct object_id *oid,\ndiff --git a/diff.h b/diff.h\nindex 7f8f024feb..d986ddc3b5 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -245,6 +245,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);\n void diff_emit_submodule_pipethrough(struct diff_options *o,\n \t\t\t\t     const char *line, int len);\n \n+struct diffstat_t {\n+\tint nr;\n+\tint alloc;\n+\tstruct diffstat_file {\n+\t\tchar *from_name;\n+\t\tchar *name;\n+\t\tchar *print_name;\n+\t\tconst char *comments;\n+\t\tunsigned is_unmerged:1;\n+\t\tunsigned is_binary:1;\n+\t\tunsigned is_renamed:1;\n+\t\tunsigned is_interesting:1;\n+\t\tuintmax_t added, deleted;\n+\t} **files;\n+};\n+\n enum color_diff {\n \tDIFF_RESET = 0,\n \tDIFF_CONTEXT = 1,\n@@ -334,6 +350,10 @@ void diff_change(struct diff_options *,\n \n struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);\n \n+void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,\n+\t\t      struct diff_queue_struct *q);\n+void free_diffstat_info(struct diffstat_t *diffstat);\n+\n #define DIFF_SETUP_REVERSE      \t1\n #define DIFF_SETUP_USE_SIZE_CACHE\t4\n \n-- \ngitgitgadget\n\n"},{"id":"386111","messageId":"pull.170.v6.git.1573648866.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v5.git.1572869729.gitgitgadget@gmail.com","subject":"[PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far)","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-13T12:40:56Z","receivedAt":"2019-11-13T12:41:13Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is the first leg on the long journey to a fully built-in git add -i \n(next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3\n[https://github.com/gitgitgadget/git/pull/172], 4\n[https://github.com/gitgitgadget/git/pull/173], 5\n[https://github.com/gitgitgadget/git/pull/174], and 6\n[https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are\nnot necessarily up to date, and will be re-targeted to the appropriate\nbranches in https://github.com/gitster/git as soon as Junio picks them up.\n\nThis here patch series reflects the part that was submitted a couple of\ntimes (see https://github.com/gitgitgadget/git/pull/103) during the\nOutreachy project by Slavica Ðukic that continued the journey based on an\ninitial patch series by Daniel Ferreira.\n\nIt only implements the status and the help part, in the interest of making\nthe review remotely more reviewable.\n\nAs I am a heavy user of git add -p myself and use a patched version for\nseveral months already (it is so nice to not suffer over one second startup\nuntil the MSYS2 Perl finally shows me anything, instead it feels\ninstantaneous), I integrated these patch series into Git for Windows\nalready, as an opt-in feature guarded by the config variable \nadd.interactive.useBuiltin (and Git for Windows' installer knows to detect\nthis version and offer the option in the graphical user interface).\n\nChanges since v4:\n\n * Rebased onto current master to make use of Thomas Gummerer's \n   repo_refresh_and_write_index() as well as to avoid merge conflicts with\n   Eric Wong's work on struct hashmap.\n * Instead of rolling a dedicated data struct to simulate a Trie, we now use \n   string-list extensively (an unsorted copy and a sorted one, the latter to\n   determine unique prefixes). This had massive ramifications on the rest of\n   the patches... For example, the struct command_item structure no longer\n   contains the name field, but is intended to be a util in a string_list.\n * Changed the commit messages and author lines to reflect Slavica's name\n   correctly.\n * Touched up a couple commit messages.\n\nChanges since v3:\n\n * Rebased to v2.23.0 to reduce friction.\n * free_diffstat_info() is now made public as well, and used, to avoid a\n   memory leak.\n * Prepared the patches for ew/hashmap (which is strict about the hashmap\n   entries' type in hashmap_entry_init() and friends).\n * The private data types have been moved from prefix-map.h to prefix-map.c.\n * A lot of int types were converted to more appropriate size_t in \n   prefix-map.c.\n * A misleading parameter name list was renamed to the correct array.\n * The code comment above find_unique_prefixes() was (hopefully) improved.\n * The run_help() function's signature now reflects that most of the\n   parameters are actually unused.\n\nChanges since v2:\n\n * Rebased to master to avoid merge conflicts.\n * Renumbered the prefix-map test to avoid conflicts with two patch series\n   that are currently in-flight in pu.\n\nChanges since v1:\n\n * The config machinery was reworked completely, to not use a callback to \n   git_config(), but instead to query the config via the repo_config_get_*() \n   functions. This also prevents a future \"Huh???\" moment: the internal add\n   --interactive API accepts a parameter of type struct repository *r, but\n   the previous configuration did not use that to query the config (and\n   could in the future be a repository other than the_repository).\n   \n   \n * As a consequence, the color sequences are no longer stored in file-local\n   variables, but passed around via a struct.\n   \n   \n * Instead of using the magical constant -2 to quit the main loop, it is now\n   defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is\n   defined as -1 and used where appropriate).\n   \n   \n * Improved the add_prefix_item() function by avoiding buffer overruns, not\n   reusing the struct that is used for lookup also for adding the new item,\n   and by strengthening the bug check.\n\nDaniel Ferreira (2):\n  diff: export diffstat interface\n  built-in add -i: implement the `status` command\n\nJohannes Schindelin (4):\n  Start to implement a built-in version of `git add --interactive`\n  built-in add -i: implement the main loop\n  built-in add -i: show unique prefixes of the commands\n  built-in add -i: support `?` (prompt help)\n\nSlavica Đukić (3):\n  built-in add -i: color the header in the `status` command\n  built-in add -i: use color in the main loop\n  built-in add -i: implement the `help` command\n\n Documentation/config/add.txt |   5 +\n Makefile                     |   1 +\n add-interactive.c            | 651 +++++++++++++++++++++++++++++++++++\n add-interactive.h            |   8 +\n builtin/add.c                |  12 +\n diff.c                       |  39 +--\n diff.h                       |  20 ++\n t/README                     |   4 +\n t/t3701-add-interactive.sh   |  25 ++\n 9 files changed, 742 insertions(+), 23 deletions(-)\n create mode 100644 add-interactive.c\n create mode 100644 add-interactive.h\n\n\nbase-commit: 566a1439f6f56c2171b8853ddbca0ad3f5098770\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v6\nPull-Request: https://github.com/gitgitgadget/git/pull/170\n\nRange-diff vs v5:\n\n  1:  ff59d2d0b3 !  1:  5d9962d434 Start to implement a built-in version of `git add --interactive`\n     @@ -2,31 +2,31 @@\n      \n          Start to implement a built-in version of `git add --interactive`\n      \n     -    This is hardly the first conversion of a Git command that is implemented\n     -    as a script to a built-in. So far, the most successful strategy for such\n     -    conversions has been to add a built-in helper and call that for more and\n     -    more functionality from the script, as more and more parts are\n     -    converted.\n     +    Unlike previous conversions to C, where we started with a built-in\n     +    helper, we start this conversion by adding an interception in the\n     +    `run_add_interactive()` function when the new opt-in\n     +    `add.interactive.useBuiltin` config knob is turned on (or the\n     +    corresponding environment variable `GIT_TEST_ADD_I_USE_BUILTIN`), and\n     +    calling the new internal API function `run_add_i()` that is implemented\n     +    directly in libgit.a.\n      \n     -    With the interactive add, we choose a different strategy. The sole\n     -    reason for this is that on Windows (where such a conversion has the most\n     -    benefits in terms of speed and robustness) we face the very specific\n     -    problem that a `system()` call in Perl seems to close `stdin` in the\n     -    parent process when the spawned process consumes even one character from\n     -    `stdin`. And that just does not work for us here, as it would stop the\n     -    main loop as soon as any interactive command was performed by the\n     -    helper. Which is almost all of the commands in `git add -i`.\n     -\n     -    It is almost as if Perl told us once again that it does not want us to\n     -    use it on Windows.\n     +    At this point, the built-in version of `git add -i` only states that it\n     +    cannot do anything yet. In subsequent patches/patch series, the\n     +    `run_add_i()` function will gain more and more functionality, until it\n     +    is feature complete. The whole arc of the conversion can be found in the\n     +    PRs #170-175 at https://github.com/gitgitgadget/git.\n      \n     -    Instead, we follow the opposite route where we start with a bare-bones\n     -    version of the built-in interactive add, guarded by the new\n     -    `add.interactive.useBuiltin` config variable, and then add more and more\n     -    functionality to it, until it is feature complete.\n     +    The \"--helper approach\" can unfortunately not be used here: on Windows\n     +    we face the very specific problem that a `system()` call in\n     +    Perl seems to close `stdin` in the parent process when the spawned\n     +    process consumes even one character from `stdin`. Which prevents us from\n     +    implementing the main loop in C and still trying to hand off to the Perl\n     +    script.\n      \n     -    At this point, the built-in version of `git add -i` only states that it\n     -    cannot do anything yet ;-)\n     +    The very real downside of the approach we have to take here is that the\n     +    test suite won't pass with `GIT_TEST_ADD_I_USE_BUILTIN=true` until the\n     +    conversion is complete (the `--helper` approach would have let it pass,\n     +    even at each of the incremental conversion steps).\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     @@ -99,12 +99,14 @@\n       \tstruct argv_array argv = ARGV_ARRAY_INIT;\n      +\tint use_builtin_add_i =\n      +\t\tgit_env_bool(\"GIT_TEST_ADD_I_USE_BUILTIN\", -1);\n     -+\tif (use_builtin_add_i < 0)\n     -+\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n     -+\t\t\t\t    &use_builtin_add_i);\n      +\n     -+\tif (use_builtin_add_i == 1 && !patch_mode)\n     -+\t\treturn !!run_add_i(the_repository, pathspec);\n     ++\tif (!patch_mode) {\n     ++\t\tif (use_builtin_add_i < 0)\n     ++\t\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n     ++\t\t\t\t\t    &use_builtin_add_i);\n     ++\t\tif (use_builtin_add_i == 1)\n     ++\t\t\treturn !!run_add_i(the_repository, pathspec);\n     ++\t}\n       \n       \targv_array_push(&argv, \"add--interactive\");\n       \tif (patch_mode)\n  2:  2fc8cc3546 =  2:  f42d7b1310 diff: export diffstat interface\n  3:  6aaa0de4f4 =  3:  4836191271 built-in add -i: implement the `status` command\n  4:  e405f07110 =  4:  d61cf9daeb built-in add -i: color the header in the `status` command\n  5:  25590fbbbe !  5:  b0c04e6ec6 built-in add -i: implement the main loop\n     @@ -7,33 +7,34 @@\n          function that we conveniently introduced for use by the `status`\n          command.\n      \n     -    Apart from the \"and choose\" part, there are more differences between the\n     -    way the `status` command calls the `list_and_choose()` function in the\n     -    Perl version of `git add -i` compared to the other callers of said\n     -    function. The most important ones:\n     +    In contrast to the Perl version, in the built-in interactive `add`, we\n     +    will keep the `list()` function (which only displays items) and the\n     +    `list_and_choose()` function (which uses `list()` to display the items,\n     +    and only takes care of the \"and choose\" part) separate.\n      \n     -    - The list is not only shown, but the user is also asked to make a\n     -      choice, possibly selecting multiple entries.\n     +    The `list_and_choose()` function, as implemented in\n     +    `git-add--interactive.perl` knows a few more tricks than the function we\n     +    introduce in this patch:\n      \n     -    - The list of items is prefixed with a marker indicating what items have\n     -      been selected, if multi-selection is allowed.\n     +    - There is a flag to let the user select multiple items.\n      \n     -    - Initially, for each item a unique prefix (if there exists any within\n     -      the given parameters) is determined, and shown in the list, and\n     +    - In multi-select mode, the list of items is prefixed with a marker\n     +      indicating what items have been selected.\n     +\n     +    - Initially, for each item a unique prefix is determined (if there\n     +      exists any within the given parameters), and shown in the list, and\n            accepted as a shortcut for the selection.\n      \n     -    These features will be implemented later, except the part where the user\n     -    can choose a command. At this stage, though, the built-in `git add -i`\n     -    still only supports the `status` command, with the remaining commands to\n     -    follow over the course of the next commits.\n     +    These features will be implemented in the C version later.\n      \n     -    In addition, we also modify `list()` to support displaying the commands\n     -    in columns, even if there is currently only one.\n     +    This patch does not add any new main loop command, of course, the\n     +    built-in `git add -i` still only supports the `status` command. The\n     +    remaining commands to follow over the course of the next commits.\n      \n     -    The Perl script `git-add--interactive.perl` mixed the purposes of the\n     -    \"list\" and the \"and choose\" part into the same function. In the C\n     -    version, we will keep them separate instead, calling the `list()`\n     -    function from the `list_and_choose()` function.\n     +    To accommodate for listing the commands in columns, preparing for the\n     +    commands that will be implemented over the course of the next\n     +    patches/patch series, we teach the `list()` function to do precisely\n     +    that.\n      \n          Note that we only have a prompt ending in a single \">\" at this stage;\n          later commits will add commands that display a double \">>\" to indicate\n     @@ -101,7 +102,7 @@\n      +\tssize_t res = LIST_AND_CHOOSE_ERROR;\n      +\n      +\tfor (;;) {\n     -+\t\tchar *p, *endp;\n     ++\t\tchar *p;\n      +\n      +\t\tstrbuf_reset(&input);\n      +\n     @@ -133,6 +134,7 @@\n      +\t\t\t}\n      +\n      +\t\t\tif (isdigit(*p)) {\n     ++\t\t\t\tchar *endp;\n      +\t\t\t\tindex = strtoul(p, &endp, 10) - 1;\n      +\t\t\t\tif (endp != p + sep)\n      +\t\t\t\t\tindex = -1;\n  6:  57fdc01463 !  6:  b6459be5eb built-in add -i: show unique prefixes of the commands\n     @@ -177,7 +177,7 @@\n      +\tfind_unique_prefixes(items);\n      +\n       \tfor (;;) {\n     - \t\tchar *p, *endp;\n     + \t\tchar *p;\n       \n       \t\tstrbuf_reset(&input);\n       \n  7:  77ad5f333a =  7:  bdf9058d9e built-in add -i: support `?` (prompt help)\n  8:  3d0b172a7f =  8:  eafeedc49b built-in add -i: use color in the main loop\n  9:  85e508ef11 =  9:  7fda76255a built-in add -i: implement the `help` command\n\n-- \ngitgitgadget\n"},{"id":"386112","messageId":"5d9962d4344fa182b37cd8d969da01bc603414be.1573648866.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v6.git.1573648866.gitgitgadget@gmail.com","subject":"[PATCH v6 1/9] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-13T12:40:57Z","receivedAt":"2019-11-13T12:41:14Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nUnlike previous conversions to C, where we started with a built-in\nhelper, we start this conversion by adding an interception in the\n`run_add_interactive()` function when the new opt-in\n`add.interactive.useBuiltin` config knob is turned on (or the\ncorresponding environment variable `GIT_TEST_ADD_I_USE_BUILTIN`), and\ncalling the new internal API function `run_add_i()` that is implemented\ndirectly in libgit.a.\n\nAt this point, the built-in version of `git add -i` only states that it\ncannot do anything yet. In subsequent patches/patch series, the\n`run_add_i()` function will gain more and more functionality, until it\nis feature complete. The whole arc of the conversion can be found in the\nPRs #170-175 at https://github.com/gitgitgadget/git.\n\nThe \"--helper approach\" can unfortunately not be used here: on Windows\nwe face the very specific problem that a `system()` call in\nPerl seems to close `stdin` in the parent process when the spawned\nprocess consumes even one character from `stdin`. Which prevents us from\nimplementing the main loop in C and still trying to hand off to the Perl\nscript.\n\nThe very real downside of the approach we have to take here is that the\ntest suite won't pass with `GIT_TEST_ADD_I_USE_BUILTIN=true` until the\nconversion is complete (the `--helper` approach would have let it pass,\neven at each of the incremental conversion steps).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config/add.txt |  5 +++++\n Makefile                     |  1 +\n add-interactive.c            |  7 +++++++\n add-interactive.h            |  8 ++++++++\n builtin/add.c                | 12 ++++++++++++\n t/README                     |  4 ++++\n 6 files changed, 37 insertions(+)\n create mode 100644 add-interactive.c\n create mode 100644 add-interactive.h\n\ndiff --git a/Documentation/config/add.txt b/Documentation/config/add.txt\nindex 4d753f006e..c9f748f81c 100644\n--- a/Documentation/config/add.txt\n+++ b/Documentation/config/add.txt\n@@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::\n \toption of linkgit:git-add[1].  `add.ignore-errors` is deprecated,\n \tas it does not follow the usual naming convention for configuration\n \tvariables.\n+\n+add.interactive.useBuiltin::\n+\t[EXPERIMENTAL] Set to `true` to use the experimental built-in\n+\timplementation of the interactive version of linkgit:git-add[1]\n+\tinstead of the Perl script version. Is `false` by default.\ndiff --git a/Makefile b/Makefile\nindex 58b92af54b..6c4a1e0ee5 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -823,6 +823,7 @@ LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentat\n \t-name '*.h' -print)))\n \n LIB_OBJS += abspath.o\n+LIB_OBJS += add-interactive.o\n LIB_OBJS += advice.o\n LIB_OBJS += alias.o\n LIB_OBJS += alloc.o\ndiff --git a/add-interactive.c b/add-interactive.c\nnew file mode 100644\nindex 0000000000..482e458dc6\n--- /dev/null\n+++ b/add-interactive.c\n@@ -0,0 +1,7 @@\n+#include \"cache.h\"\n+#include \"add-interactive.h\"\n+\n+int run_add_i(struct repository *r, const struct pathspec *ps)\n+{\n+\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n+}\ndiff --git a/add-interactive.h b/add-interactive.h\nnew file mode 100644\nindex 0000000000..7043b8741d\n--- /dev/null\n+++ b/add-interactive.h\n@@ -0,0 +1,8 @@\n+#ifndef ADD_INTERACTIVE_H\n+#define ADD_INTERACTIVE_H\n+\n+struct repository;\n+struct pathspec;\n+int run_add_i(struct repository *r, const struct pathspec *ps);\n+\n+#endif\ndiff --git a/builtin/add.c b/builtin/add.c\nindex dd18e5c9b6..d4686d5218 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -20,6 +20,7 @@\n #include \"bulk-checkin.h\"\n #include \"argv-array.h\"\n #include \"submodule.h\"\n+#include \"add-interactive.h\"\n \n static const char * const builtin_add_usage[] = {\n \tN_(\"git add [<options>] [--] <pathspec>...\"),\n@@ -185,6 +186,16 @@ int run_add_interactive(const char *revision, const char *patch_mode,\n {\n \tint status, i;\n \tstruct argv_array argv = ARGV_ARRAY_INIT;\n+\tint use_builtin_add_i =\n+\t\tgit_env_bool(\"GIT_TEST_ADD_I_USE_BUILTIN\", -1);\n+\n+\tif (!patch_mode) {\n+\t\tif (use_builtin_add_i < 0)\n+\t\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n+\t\t\t\t\t    &use_builtin_add_i);\n+\t\tif (use_builtin_add_i == 1)\n+\t\t\treturn !!run_add_i(the_repository, pathspec);\n+\t}\n \n \targv_array_push(&argv, \"add--interactive\");\n \tif (patch_mode)\n@@ -319,6 +330,7 @@ static int add_config(const char *var, const char *value, void *cb)\n \t\tignore_add_errors = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \ndiff --git a/t/README b/t/README\nindex 60d5b77bcc..5132ec83f8 100644\n--- a/t/README\n+++ b/t/README\n@@ -397,6 +397,10 @@ GIT_TEST_STASH_USE_BUILTIN=<boolean>, when false, disables the\n built-in version of git-stash. See 'stash.useBuiltin' in\n git-config(1).\n \n+GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the\n+built-in version of git add -i. See 'add.interactive.useBuiltin' in\n+git-config(1).\n+\n GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading\n of the index for the whole test suite by bypassing the default number of\n cache entries and thread minimums. Setting this to 1 will make the\n-- \ngitgitgadget\n\n"},{"id":"386113","messageId":"4836191271fb260df837d431c55a1d8316edbdb4.1573648866.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v6.git.1573648866.gitgitgadget@gmail.com","subject":"[PATCH v6 3/9] built-in add -i: implement the `status` command","fromName":"Daniel Ferreira via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-13T12:40:59Z","receivedAt":"2019-11-13T12:41:14Z","isPatch":true,"sender":{"key":"bnmvco@gmail.com","avatar":"https://avatars.githubusercontent.com/u/735397?v=4"},"body":"From: Daniel Ferreira <bnmvco@gmail.com>\n\nThis implements the `status` command of `git add -i`. The data\nstructures introduced in this commit will be extended later, as needed.\n\nAt this point, we re-implement only part of the `list_and_choose()`\nfunction of the Perl script `git-add--interactive.perl` and call it\n`list()`. It does not yet color anything, or do columns, or allow user\ninput.\n\nOver the course of the next commits, we will introduce a\n`list_and_choose()` function that uses `list()` to display the list of\noptions and let the user choose one or more of the displayed items. This\nwill be used to implement the main loop of the built-in `git add -i`, at\nwhich point the new `status` command can actually be used.\n\nSigned-off-by: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Đukić <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 251 +++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 250 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 482e458dc6..aa35184d87 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -1,7 +1,256 @@\n #include \"cache.h\"\n #include \"add-interactive.h\"\n+#include \"diffcore.h\"\n+#include \"revision.h\"\n+#include \"refs.h\"\n+#include \"string-list.h\"\n+\n+struct add_i_state {\n+\tstruct repository *r;\n+};\n+\n+static void init_add_i_state(struct add_i_state *s, struct repository *r)\n+{\n+       s->r = r;\n+}\n+\n+struct list_options {\n+\tconst char *header;\n+\tvoid (*print_item)(int i, struct string_list_item *item, void *print_item_data);\n+\tvoid *print_item_data;\n+};\n+\n+static void list(struct string_list *list, struct list_options *opts)\n+{\n+\tint i;\n+\n+\tif (!list->nr)\n+\t\treturn;\n+\n+\tif (opts->header)\n+\t\tprintf(\"%s\\n\", opts->header);\n+\n+\tfor (i = 0; i < list->nr; i++) {\n+\t\topts->print_item(i, list->items + i, opts->print_item_data);\n+\t\tputchar('\\n');\n+\t}\n+}\n+\n+struct adddel {\n+\tuintmax_t add, del;\n+\tunsigned seen:1, binary:1;\n+};\n+\n+struct file_item {\n+\tstruct adddel index, worktree;\n+};\n+\n+static void add_file_item(struct string_list *files, const char *name)\n+{\n+\tstruct file_item *item = xcalloc(sizeof(*item), 1);\n+\n+\tstring_list_append(files, name)->util = item;\n+}\n+\n+struct pathname_entry {\n+\tstruct hashmap_entry ent;\n+\tconst char *name;\n+\tstruct file_item *item;\n+};\n+\n+static int pathname_entry_cmp(const void *unused_cmp_data,\n+\t\t\t      const struct hashmap_entry *he1,\n+\t\t\t      const struct hashmap_entry *he2,\n+\t\t\t      const void *name)\n+{\n+\tconst struct pathname_entry *e1 =\n+\t\tcontainer_of(he1, const struct pathname_entry, ent);\n+\tconst struct pathname_entry *e2 =\n+\t\tcontainer_of(he2, const struct pathname_entry, ent);\n+\n+\treturn strcmp(e1->name, name ? (const char *)name : e2->name);\n+}\n+\n+struct collection_status {\n+\tenum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase;\n+\n+\tconst char *reference;\n+\n+\tstruct string_list *files;\n+\tstruct hashmap file_map;\n+};\n+\n+static void collect_changes_cb(struct diff_queue_struct *q,\n+\t\t\t       struct diff_options *options,\n+\t\t\t       void *data)\n+{\n+\tstruct collection_status *s = data;\n+\tstruct diffstat_t stat = { 0 };\n+\tint i;\n+\n+\tif (!q->nr)\n+\t\treturn;\n+\n+\tcompute_diffstat(options, &stat, q);\n+\n+\tfor (i = 0; i < stat.nr; i++) {\n+\t\tconst char *name = stat.files[i]->name;\n+\t\tint hash = strhash(name);\n+\t\tstruct pathname_entry *entry;\n+\t\tstruct file_item *file_item;\n+\t\tstruct adddel *adddel;\n+\n+\t\tentry = hashmap_get_entry_from_hash(&s->file_map, hash, name,\n+\t\t\t\t\t\t    struct pathname_entry, ent);\n+\t\tif (!entry) {\n+\t\t\tadd_file_item(s->files, name);\n+\n+\t\t\tentry = xcalloc(sizeof(*entry), 1);\n+\t\t\thashmap_entry_init(&entry->ent, hash);\n+\t\t\tentry->name = s->files->items[s->files->nr - 1].string;\n+\t\t\tentry->item = s->files->items[s->files->nr - 1].util;\n+\t\t\thashmap_add(&s->file_map, &entry->ent);\n+\t\t}\n+\n+\t\tfile_item = entry->item;\n+\t\tadddel = s->phase == FROM_INDEX ?\n+\t\t\t&file_item->index : &file_item->worktree;\n+\t\tadddel->seen = 1;\n+\t\tadddel->add = stat.files[i]->added;\n+\t\tadddel->del = stat.files[i]->deleted;\n+\t\tif (stat.files[i]->is_binary)\n+\t\t\tadddel->binary = 1;\n+\t}\n+\tfree_diffstat_info(&stat);\n+}\n+\n+static int get_modified_files(struct repository *r, struct string_list *files,\n+\t\t\t      const struct pathspec *ps)\n+{\n+\tstruct object_id head_oid;\n+\tint is_initial = !resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING,\n+\t\t\t\t\t     &head_oid, NULL);\n+\tstruct collection_status s = { FROM_WORKTREE };\n+\n+\tif (discard_index(r->index) < 0 ||\n+\t    repo_read_index_preload(r, ps, 0) < 0)\n+\t\treturn error(_(\"could not read index\"));\n+\n+\tstring_list_clear(files, 1);\n+\ts.files = files;\n+\thashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);\n+\n+\tfor (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {\n+\t\tstruct rev_info rev;\n+\t\tstruct setup_revision_opt opt = { 0 };\n+\n+\t\topt.def = is_initial ?\n+\t\t\tempty_tree_oid_hex() : oid_to_hex(&head_oid);\n+\n+\t\tinit_revisions(&rev, NULL);\n+\t\tsetup_revisions(0, NULL, &rev, &opt);\n+\n+\t\trev.diffopt.output_format = DIFF_FORMAT_CALLBACK;\n+\t\trev.diffopt.format_callback = collect_changes_cb;\n+\t\trev.diffopt.format_callback_data = &s;\n+\n+\t\tif (ps)\n+\t\t\tcopy_pathspec(&rev.prune_data, ps);\n+\n+\t\tif (s.phase == FROM_INDEX)\n+\t\t\trun_diff_index(&rev, 1);\n+\t\telse {\n+\t\t\trev.diffopt.flags.ignore_dirty_submodules = 1;\n+\t\t\trun_diff_files(&rev, 0);\n+\t\t}\n+\t}\n+\thashmap_free_entries(&s.file_map, struct pathname_entry, ent);\n+\n+\t/* While the diffs are ordered already, we ran *two* diffs... */\n+\tstring_list_sort(files);\n+\n+\treturn 0;\n+}\n+\n+static void render_adddel(struct strbuf *buf,\n+\t\t\t\tstruct adddel *ad, const char *no_changes)\n+{\n+\tif (ad->binary)\n+\t\tstrbuf_addstr(buf, _(\"binary\"));\n+\telse if (ad->seen)\n+\t\tstrbuf_addf(buf, \"+%\"PRIuMAX\"/-%\"PRIuMAX,\n+\t\t\t    (uintmax_t)ad->add, (uintmax_t)ad->del);\n+\telse\n+\t\tstrbuf_addstr(buf, no_changes);\n+}\n+\n+struct print_file_item_data {\n+\tconst char *modified_fmt;\n+\tstruct strbuf buf, index, worktree;\n+};\n+\n+static void print_file_item(int i, struct string_list_item *item,\n+\t\t\t    void *print_file_item_data)\n+{\n+\tstruct file_item *c = item->util;\n+\tstruct print_file_item_data *d = print_file_item_data;\n+\n+\tstrbuf_reset(&d->index);\n+\tstrbuf_reset(&d->worktree);\n+\tstrbuf_reset(&d->buf);\n+\n+\trender_adddel(&d->worktree, &c->worktree, _(\"nothing\"));\n+\trender_adddel(&d->index, &c->index, _(\"unchanged\"));\n+\tstrbuf_addf(&d->buf, d->modified_fmt,\n+\t\t    d->index.buf, d->worktree.buf, item->string);\n+\n+\tprintf(\" %2d: %s\", i + 1, d->buf.buf);\n+}\n+\n+static int run_status(struct add_i_state *s, const struct pathspec *ps,\n+\t\t      struct string_list *files, struct list_options *opts)\n+{\n+\tif (get_modified_files(s->r, files, ps) < 0)\n+\t\treturn -1;\n+\n+\tlist(files, opts);\n+\tputchar('\\n');\n+\n+\treturn 0;\n+}\n \n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n-\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n+\tstruct add_i_state s = { NULL };\n+\tstruct print_file_item_data print_file_item_data = {\n+\t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n+\t};\n+\tstruct list_options opts = {\n+\t\tNULL, print_file_item, &print_file_item_data\n+\t};\n+\tstruct strbuf header = STRBUF_INIT;\n+\tstruct string_list files = STRING_LIST_INIT_DUP;\n+\tint res = 0;\n+\n+\tinit_add_i_state(&s, r);\n+\tstrbuf_addstr(&header, \"      \");\n+\tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n+\t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n+\topts.header = header.buf;\n+\n+\tif (discard_index(r->index) < 0 ||\n+\t    repo_read_index(r) < 0 ||\n+\t    repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,\n+\t\t\t\t\t NULL, NULL, NULL) < 0)\n+\t\twarning(_(\"could not refresh index\"));\n+\n+\tres = run_status(&s, ps, &files, &opts);\n+\n+\tstring_list_clear(&files, 1);\n+\tstrbuf_release(&print_file_item_data.buf);\n+\tstrbuf_release(&print_file_item_data.index);\n+\tstrbuf_release(&print_file_item_data.worktree);\n+\tstrbuf_release(&header);\n+\n+\treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"386114","messageId":"b6459be5eb249a6d19615eb9d1cd8cb526eebc83.1573648866.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v6.git.1573648866.gitgitgadget@gmail.com","subject":"[PATCH v6 6/9] built-in add -i: show unique prefixes of the commands","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-13T12:41:02Z","receivedAt":"2019-11-13T12:41:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nJust like in the Perl script `git-add--interactive.perl`, for each\ncommand a unique prefix is determined (if there exists any within the\ngiven parameters), and shown in the list, and accepted as a shortcut for\nthe command.\n\nTo determine the unique prefixes, as well as to look up the command in\nquestion, we use a copy of the list and sort it.\n\nWhile this might seem like overkill for a single command, it will make\nmuch more sense when all the commands are implemented, and when we reuse\nthe same logic to present a list of files to edit, with convenient\nunique prefixes.\n\nAt the start of the development of this patch series, a dedicated data\nstructure was introduced that imitated the Trie that the Perl version\nimplements. However, this was deemed overkill, and we now simply sort\nthe list before determining the length of the unique prefixes by looking\nat each item's neighbor. As a bonus, we now use the same sorted list to\nperform a binary search using the user-provided prefix as search key.\n\nOriginal-patch-by: Slavica Đukić <slawica92@hotmail.com>\nHelped-by: SZEDER Gábor <szeder.dev@gmail.com>\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n---\n add-interactive.c | 188 +++++++++++++++++++++++++++++++++++++++++++---\n 1 file changed, 177 insertions(+), 11 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 0f99a52a72..7b6bcf6f8a 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -45,6 +45,132 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)\n \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n }\n \n+/*\n+ * A \"prefix item list\" is a list of items that are identified by a string, and\n+ * a unique prefix (if any) is determined for each item.\n+ *\n+ * It is implemented in the form of a pair of `string_list`s, the first one\n+ * duplicating the strings, with the `util` field pointing at a structure whose\n+ * first field must be `size_t prefix_length`.\n+ *\n+ * That `prefix_length` field will be computed by `find_unique_prefixes()`; It\n+ * will be set to zero if no valid, unique prefix could be found.\n+ *\n+ * The second `string_list` is called `sorted` and does _not_ duplicate the\n+ * strings but simply reuses the first one's, with the `util` field pointing at\n+ * the `string_item_list` of the first `string_list`. It  will be populated and\n+ * sorted by `find_unique_prefixes()`.\n+ */\n+struct prefix_item_list {\n+\tstruct string_list items;\n+\tstruct string_list sorted;\n+\tsize_t min_length, max_length;\n+};\n+#define PREFIX_ITEM_LIST_INIT \\\n+\t{ STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, 1, 4 }\n+\n+static void prefix_item_list_clear(struct prefix_item_list *list)\n+{\n+\tstring_list_clear(&list->items, 1);\n+\tstring_list_clear(&list->sorted, 0);\n+}\n+\n+static void extend_prefix_length(struct string_list_item *p,\n+\t\t\t\t const char *other_string, size_t max_length)\n+{\n+\tsize_t *len = p->util;\n+\n+\tif (!*len || memcmp(p->string, other_string, *len))\n+\t\treturn;\n+\n+\tfor (;;) {\n+\t\tchar c = p->string[*len];\n+\n+\t\t/*\n+\t\t * Is `p` a strict prefix of `other`? Or have we exhausted the\n+\t\t * maximal length of the prefix? Or is the current character a\n+\t\t * multi-byte UTF-8 one? If so, there is no valid, unique\n+\t\t * prefix.\n+\t\t */\n+\t\tif (!c || ++*len > max_length || !isascii(c)) {\n+\t\t\t*len = 0;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (c != other_string[*len - 1])\n+\t\t\tbreak;\n+\t}\n+}\n+\n+static void find_unique_prefixes(struct prefix_item_list *list)\n+{\n+\tsize_t i;\n+\n+\tif (list->sorted.nr == list->items.nr)\n+\t\treturn;\n+\n+\tstring_list_clear(&list->sorted, 0);\n+\t/* Avoid reallocating incrementally */\n+\tlist->sorted.items = xmalloc(st_mult(sizeof(*list->sorted.items),\n+\t\t\t\t\t     list->items.nr));\n+\tlist->sorted.nr = list->sorted.alloc = list->items.nr;\n+\n+\tfor (i = 0; i < list->items.nr; i++) {\n+\t\tlist->sorted.items[i].string = list->items.items[i].string;\n+\t\tlist->sorted.items[i].util = list->items.items + i;\n+\t}\n+\n+\tstring_list_sort(&list->sorted);\n+\n+\tfor (i = 0; i < list->sorted.nr; i++) {\n+\t\tstruct string_list_item *sorted_item = list->sorted.items + i;\n+\t\tstruct string_list_item *item = sorted_item->util;\n+\t\tsize_t *len = item->util;\n+\n+\t\t*len = 0;\n+\t\twhile (*len < list->min_length) {\n+\t\t\tchar c = item->string[(*len)++];\n+\n+\t\t\tif (!c || !isascii(c)) {\n+\t\t\t\t*len = 0;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (i > 0)\n+\t\t\textend_prefix_length(item, sorted_item[-1].string,\n+\t\t\t\t\t     list->max_length);\n+\t\tif (i + 1 < list->sorted.nr)\n+\t\t\textend_prefix_length(item, sorted_item[1].string,\n+\t\t\t\t\t     list->max_length);\n+\t}\n+}\n+\n+static ssize_t find_unique(const char *string, struct prefix_item_list *list)\n+{\n+\tint index = string_list_find_insert_index(&list->sorted, string, 1);\n+\tstruct string_list_item *item;\n+\n+\tif (list->items.nr != list->sorted.nr)\n+\t\tBUG(\"prefix_item_list in inconsistent state (%\"PRIuMAX\n+\t\t    \" vs %\"PRIuMAX\")\",\n+\t\t    (uintmax_t)list->items.nr, (uintmax_t)list->sorted.nr);\n+\n+\tif (index < 0)\n+\t\titem = list->sorted.items[-1 - index].util;\n+\telse if (index > 0 &&\n+\t\t starts_with(list->sorted.items[index - 1].string, string))\n+\t\treturn -1;\n+\telse if (index + 1 < list->sorted.nr &&\n+\t\t starts_with(list->sorted.items[index + 1].string, string))\n+\t\treturn -1;\n+\telse if (index < list->sorted.nr)\n+\t\titem = list->sorted.items[index].util;\n+\telse\n+\t\treturn -1;\n+\treturn item - list->items.items;\n+}\n+\n struct list_options {\n \tint columns;\n \tconst char *header;\n@@ -95,18 +221,21 @@ struct list_and_choose_options {\n  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n  * `LIST_AND_CHOOSE_QUIT` is returned.\n  */\n-static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,\n+static ssize_t list_and_choose(struct add_i_state *s,\n+\t\t\t       struct prefix_item_list *items,\n \t\t\t       struct list_and_choose_options *opts)\n {\n \tstruct strbuf input = STRBUF_INIT;\n \tssize_t res = LIST_AND_CHOOSE_ERROR;\n \n+\tfind_unique_prefixes(items);\n+\n \tfor (;;) {\n \t\tchar *p;\n \n \t\tstrbuf_reset(&input);\n \n-\t\tlist(s, items, &opts->list_opts);\n+\t\tlist(s, &items->items, &opts->list_opts);\n \n \t\tprintf(\"%s%s\", opts->prompt, \"> \");\n \t\tfflush(stdout);\n@@ -141,7 +270,10 @@ static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,\n \t\t\t}\n \n \t\t\tp[sep] = '\\0';\n-\t\t\tif (index < 0 || index >= items->nr)\n+\t\t\tif (index < 0)\n+\t\t\t\tindex = find_unique(p, items);\n+\n+\t\t\tif (index < 0 || index >= items->items.nr)\n \t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n \t\t\telse {\n \t\t\t\tres = index;\n@@ -307,6 +439,23 @@ static void render_adddel(struct strbuf *buf,\n \t\tstrbuf_addstr(buf, no_changes);\n }\n \n+/* filters out prefixes which have special meaning to list_and_choose() */\n+static int is_valid_prefix(const char *prefix, size_t prefix_len)\n+{\n+\treturn prefix_len && prefix &&\n+\t\t/*\n+\t\t * We expect `prefix` to be NUL terminated, therefore this\n+\t\t * `strcspn()` call is okay, even if it might do much more\n+\t\t * work than strictly necessary.\n+\t\t */\n+\t\tstrcspn(prefix, \" \\t\\r\\n,\") >= prefix_len &&\t/* separators */\n+\t\t*prefix != '-' &&\t\t\t\t/* deselection */\n+\t\t!isdigit(*prefix) &&\t\t\t\t/* selection */\n+\t\t(prefix_len != 1 ||\n+\t\t (*prefix != '*' &&\t\t\t\t/* \"all\" wildcard */\n+\t\t  *prefix != '?'));\t\t\t\t/* prompt help */\n+}\n+\n struct print_file_item_data {\n \tconst char *modified_fmt;\n \tstruct strbuf buf, index, worktree;\n@@ -346,10 +495,23 @@ typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,\n \t\t\t struct string_list *files,\n \t\t\t struct list_options *opts);\n \n+struct command_item {\n+\tsize_t prefix_length;\n+\tcommand_t command;\n+};\n+\n static void print_command_item(int i, struct string_list_item *item,\n \t\t\t       void *print_command_item_data)\n {\n-\tprintf(\" %2d: %s\", i + 1, item->string);\n+\tstruct command_item *util = item->util;\n+\n+\tif (!util->prefix_length ||\n+\t    !is_valid_prefix(item->string, util->prefix_length))\n+\t\tprintf(\" %2d: %s\", i + 1, item->string);\n+\telse\n+\t\tprintf(\" %2d: [%.*s]%s\", i + 1,\n+\t\t       (int)util->prefix_length, item->string,\n+\t\t       item->string + util->prefix_length);\n }\n \n int run_add_i(struct repository *r, const struct pathspec *ps)\n@@ -365,7 +527,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t} command_list[] = {\n \t\t{ \"status\", run_status },\n \t};\n-\tstruct string_list commands = STRING_LIST_INIT_NODUP;\n+\tstruct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;\n \n \tstruct print_file_item_data print_file_item_data = {\n \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n@@ -378,9 +540,12 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tssize_t i;\n \tint res = 0;\n \n-\tfor (i = 0; i < ARRAY_SIZE(command_list); i++)\n-\t\tstring_list_append(&commands, command_list[i].string)\n-\t\t\t->util = command_list[i].command;\n+\tfor (i = 0; i < ARRAY_SIZE(command_list); i++) {\n+\t\tstruct command_item *util = xcalloc(sizeof(*util), 1);\n+\t\tutil->command = command_list[i].command;\n+\t\tstring_list_append(&commands.items, command_list[i].string)\n+\t\t\t->util = util;\n+\t}\n \n \tinit_add_i_state(&s, r);\n \n@@ -405,8 +570,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\t\tbreak;\n \t\t}\n \t\tif (i != LIST_AND_CHOOSE_ERROR) {\n-\t\t\tcommand_t command = commands.items[i].util;\n-\t\t\tres = command(&s, ps, &files, &opts);\n+\t\t\tstruct command_item *util =\n+\t\t\t\tcommands.items.items[i].util;\n+\t\t\tres = util->command(&s, ps, &files, &opts);\n \t\t}\n \t}\n \n@@ -415,7 +581,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tstrbuf_release(&print_file_item_data.index);\n \tstrbuf_release(&print_file_item_data.worktree);\n \tstrbuf_release(&header);\n-\tstring_list_clear(&commands, 0);\n+\tprefix_item_list_clear(&commands);\n \n \treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"386115","messageId":"bdf9058d9e71ac279a630234efd333c3c082cc96.1573648866.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v6.git.1573648866.gitgitgadget@gmail.com","subject":"[PATCH v6 7/9] built-in add -i: support `?` (prompt help)","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-13T12:41:03Z","receivedAt":"2019-11-13T12:41:19Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWith this change, we print out the same colored help text that the\nPerl-based `git add -i` prints in the main loop when question mark is\nentered.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 22 +++++++++++++++++++++-\n 1 file changed, 21 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 7b6bcf6f8a..8d3829fe68 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -11,6 +11,7 @@ struct add_i_state {\n \tstruct repository *r;\n \tint use_color;\n \tchar header_color[COLOR_MAXLEN];\n+\tchar help_color[COLOR_MAXLEN];\n };\n \n static void init_color(struct repository *r, struct add_i_state *s,\n@@ -43,6 +44,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)\n \ts->use_color = want_color(s->use_color);\n \n \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n+\tinit_color(r, s, \"help\", s->help_color, GIT_COLOR_BOLD_RED);\n }\n \n /*\n@@ -210,6 +212,7 @@ struct list_and_choose_options {\n \tstruct list_options list_opts;\n \n \tconst char *prompt;\n+\tvoid (*print_help)(struct add_i_state *s);\n };\n \n #define LIST_AND_CHOOSE_ERROR (-1)\n@@ -250,6 +253,11 @@ static ssize_t list_and_choose(struct add_i_state *s,\n \t\tif (!input.len)\n \t\t\tbreak;\n \n+\t\tif (!strcmp(input.buf, \"?\")) {\n+\t\t\topts->print_help(s);\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tp = input.buf;\n \t\tfor (;;) {\n \t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n@@ -514,12 +522,24 @@ static void print_command_item(int i, struct string_list_item *item,\n \t\t       item->string + util->prefix_length);\n }\n \n+static void command_prompt_help(struct add_i_state *s)\n+{\n+\tconst char *help_color = s->help_color;\n+\tcolor_fprintf_ln(stdout, help_color, \"%s\", _(\"Prompt help:\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"1          - %s\",\n+\t\t\t _(\"select a numbered item\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"foo        - %s\",\n+\t\t\t _(\"select item based on unique prefix\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"           - %s\",\n+\t\t\t _(\"(empty) select nothing\"));\n+}\n+\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n \tstruct list_and_choose_options main_loop_opts = {\n \t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n-\t\tN_(\"What now\")\n+\t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct {\n \t\tconst char *string;\n-- \ngitgitgadget\n\n"},{"id":"386116","messageId":"7fda76255a82dbc92dc7861d51767ef275422788.1573648866.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v6.git.1573648866.gitgitgadget@gmail.com","subject":"[PATCH v6 9/9] built-in add -i: implement the `help` command","fromName":"Slavica Đukić via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-13T12:41:05Z","receivedAt":"2019-11-13T12:41:46Z","isPatch":true,"sender":{"key":"name:Slavica Đukić","avatar":null},"body":"From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>\n\nThis imitates the code to show the help text from the Perl script\n`git-add--interactive.perl` in the built-in version.\n\nTo make sure that it renders exactly like the Perl version of `git add\n-i`, we also add a test case for that to `t3701-add-interactive.sh`.\n\nSigned-off-by: Slavica Đukić <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c          | 21 +++++++++++++++++++++\n t/t3701-add-interactive.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 46 insertions(+)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 8f5531c86c..5d89863bab 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -507,6 +507,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \treturn 0;\n }\n \n+static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,\n+\t\t    struct string_list *unused_files,\n+\t\t    struct list_options *unused_opts)\n+{\n+\tcolor_fprintf_ln(stdout, s->help_color, \"status        - %s\",\n+\t\t\t _(\"show paths with changes\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"update        - %s\",\n+\t\t\t _(\"add working tree state to the staged set of changes\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"revert        - %s\",\n+\t\t\t _(\"revert staged set of changes back to the HEAD version\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"patch         - %s\",\n+\t\t\t _(\"pick hunks and update selectively\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"diff          - %s\",\n+\t\t\t _(\"view diff between HEAD and index\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"add untracked - %s\",\n+\t\t\t _(\"add contents of untracked files to the staged set of changes\"));\n+\n+\treturn 0;\n+}\n+\n typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,\n \t\t\t struct string_list *files,\n \t\t\t struct list_options *opts);\n@@ -560,6 +580,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\tcommand_t command;\n \t} command_list[] = {\n \t\t{ \"status\", run_status },\n+\t\t{ \"help\", run_help },\n \t};\n \tstruct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;\n \ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex d50e165ca8..d4f9386621 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' '\n \ttest_write_lines a b a b a a b a b a >expect &&\n \ttest_cmp expect a\n '\n+\n+test_expect_success 'show help from add--helper' '\n+\tgit reset --hard &&\n+\tcat >expect <<-EOF &&\n+\n+\t<BOLD>*** Commands ***<RESET>\n+\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>\n+\t<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>\n+\t<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>\n+\t<BOLD;RED>patch         - pick hunks and update selectively<RESET>\n+\t<BOLD;RED>diff          - view diff between HEAD and index<RESET>\n+\t<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>\n+\t<BOLD>*** Commands ***<RESET>\n+\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t<BOLD;BLUE>What now<RESET>>$SP\n+\tBye.\n+\tEOF\n+\ttest_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&\n+\ttest_decode_color <actual.colored >actual &&\n+\ttest_i18ncmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"386117","messageId":"b0c04e6ec60f972f353d7ec93086f0894f089350.1573648866.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v6.git.1573648866.gitgitgadget@gmail.com","subject":"[PATCH v6 5/9] built-in add -i: implement the main loop","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-13T12:41:01Z","receivedAt":"2019-11-13T12:41:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe reason why we did not start with the main loop to begin with is that\nit is the first user of `list_and_choose()`, which uses the `list()`\nfunction that we conveniently introduced for use by the `status`\ncommand.\n\nIn contrast to the Perl version, in the built-in interactive `add`, we\nwill keep the `list()` function (which only displays items) and the\n`list_and_choose()` function (which uses `list()` to display the items,\nand only takes care of the \"and choose\" part) separate.\n\nThe `list_and_choose()` function, as implemented in\n`git-add--interactive.perl` knows a few more tricks than the function we\nintroduce in this patch:\n\n- There is a flag to let the user select multiple items.\n\n- In multi-select mode, the list of items is prefixed with a marker\n  indicating what items have been selected.\n\n- Initially, for each item a unique prefix is determined (if there\n  exists any within the given parameters), and shown in the list, and\n  accepted as a shortcut for the selection.\n\nThese features will be implemented in the C version later.\n\nThis patch does not add any new main loop command, of course, the\nbuilt-in `git add -i` still only supports the `status` command. The\nremaining commands to follow over the course of the next commits.\n\nTo accommodate for listing the commands in columns, preparing for the\ncommands that will be implemented over the course of the next\npatches/patch series, we teach the `list()` function to do precisely\nthat.\n\nNote that we only have a prompt ending in a single \">\" at this stage;\nlater commits will add commands that display a double \">>\" to indicate\nthat the user is in a different loop than the main one.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 136 +++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 134 insertions(+), 2 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 174e07ce83..0f99a52a72 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -46,6 +46,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)\n }\n \n struct list_options {\n+\tint columns;\n \tconst char *header;\n \tvoid (*print_item)(int i, struct string_list_item *item, void *print_item_data);\n \tvoid *print_item_data;\n@@ -54,7 +55,7 @@ struct list_options {\n static void list(struct add_i_state *s, struct string_list *list,\n \t\t struct list_options *opts)\n {\n-\tint i;\n+\tint i, last_lf = 0;\n \n \tif (!list->nr)\n \t\treturn;\n@@ -65,8 +66,97 @@ static void list(struct add_i_state *s, struct string_list *list,\n \n \tfor (i = 0; i < list->nr; i++) {\n \t\topts->print_item(i, list->items + i, opts->print_item_data);\n+\n+\t\tif ((opts->columns) && ((i + 1) % (opts->columns))) {\n+\t\t\tputchar('\\t');\n+\t\t\tlast_lf = 0;\n+\t\t}\n+\t\telse {\n+\t\t\tputchar('\\n');\n+\t\t\tlast_lf = 1;\n+\t\t}\n+\t}\n+\n+\tif (!last_lf)\n \t\tputchar('\\n');\n+}\n+struct list_and_choose_options {\n+\tstruct list_options list_opts;\n+\n+\tconst char *prompt;\n+};\n+\n+#define LIST_AND_CHOOSE_ERROR (-1)\n+#define LIST_AND_CHOOSE_QUIT  (-2)\n+\n+/*\n+ * Returns the selected index.\n+ *\n+ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n+ * `LIST_AND_CHOOSE_QUIT` is returned.\n+ */\n+static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,\n+\t\t\t       struct list_and_choose_options *opts)\n+{\n+\tstruct strbuf input = STRBUF_INIT;\n+\tssize_t res = LIST_AND_CHOOSE_ERROR;\n+\n+\tfor (;;) {\n+\t\tchar *p;\n+\n+\t\tstrbuf_reset(&input);\n+\n+\t\tlist(s, items, &opts->list_opts);\n+\n+\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n+\t\tfflush(stdout);\n+\n+\t\tif (strbuf_getline(&input, stdin) == EOF) {\n+\t\t\tputchar('\\n');\n+\t\t\tres = LIST_AND_CHOOSE_QUIT;\n+\t\t\tbreak;\n+\t\t}\n+\t\tstrbuf_trim(&input);\n+\n+\t\tif (!input.len)\n+\t\t\tbreak;\n+\n+\t\tp = input.buf;\n+\t\tfor (;;) {\n+\t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n+\t\t\tssize_t index = -1;\n+\n+\t\t\tif (!sep) {\n+\t\t\t\tif (!*p)\n+\t\t\t\t\tbreak;\n+\t\t\t\tp++;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (isdigit(*p)) {\n+\t\t\t\tchar *endp;\n+\t\t\t\tindex = strtoul(p, &endp, 10) - 1;\n+\t\t\t\tif (endp != p + sep)\n+\t\t\t\t\tindex = -1;\n+\t\t\t}\n+\n+\t\t\tp[sep] = '\\0';\n+\t\t\tif (index < 0 || index >= items->nr)\n+\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n+\t\t\telse {\n+\t\t\t\tres = index;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tp += sep + 1;\n+\t\t}\n+\n+\t\tif (res != LIST_AND_CHOOSE_ERROR)\n+\t\t\tbreak;\n \t}\n+\n+\tstrbuf_release(&input);\n+\treturn res;\n }\n \n struct adddel {\n@@ -252,20 +342,48 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \treturn 0;\n }\n \n+typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,\n+\t\t\t struct string_list *files,\n+\t\t\t struct list_options *opts);\n+\n+static void print_command_item(int i, struct string_list_item *item,\n+\t\t\t       void *print_command_item_data)\n+{\n+\tprintf(\" %2d: %s\", i + 1, item->string);\n+}\n+\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n+\tstruct list_and_choose_options main_loop_opts = {\n+\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n+\t\tN_(\"What now\")\n+\t};\n+\tstruct {\n+\t\tconst char *string;\n+\t\tcommand_t command;\n+\t} command_list[] = {\n+\t\t{ \"status\", run_status },\n+\t};\n+\tstruct string_list commands = STRING_LIST_INIT_NODUP;\n+\n \tstruct print_file_item_data print_file_item_data = {\n \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n \t};\n \tstruct list_options opts = {\n-\t\tNULL, print_file_item, &print_file_item_data\n+\t\t0, NULL, print_file_item, &print_file_item_data\n \t};\n \tstruct strbuf header = STRBUF_INIT;\n \tstruct string_list files = STRING_LIST_INIT_DUP;\n+\tssize_t i;\n \tint res = 0;\n \n+\tfor (i = 0; i < ARRAY_SIZE(command_list); i++)\n+\t\tstring_list_append(&commands, command_list[i].string)\n+\t\t\t->util = command_list[i].command;\n+\n \tinit_add_i_state(&s, r);\n+\n \tstrbuf_addstr(&header, \"      \");\n \tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n@@ -279,11 +397,25 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \n \tres = run_status(&s, ps, &files, &opts);\n \n+\tfor (;;) {\n+\t\ti = list_and_choose(&s, &commands, &main_loop_opts);\n+\t\tif (i == LIST_AND_CHOOSE_QUIT) {\n+\t\t\tprintf(_(\"Bye.\\n\"));\n+\t\t\tres = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (i != LIST_AND_CHOOSE_ERROR) {\n+\t\t\tcommand_t command = commands.items[i].util;\n+\t\t\tres = command(&s, ps, &files, &opts);\n+\t\t}\n+\t}\n+\n \tstring_list_clear(&files, 1);\n \tstrbuf_release(&print_file_item_data.buf);\n \tstrbuf_release(&print_file_item_data.index);\n \tstrbuf_release(&print_file_item_data.worktree);\n \tstrbuf_release(&header);\n+\tstring_list_clear(&commands, 0);\n \n \treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"386118","messageId":"eafeedc49b5dfc414577813804571e48ae6b1e92.1573648866.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v6.git.1573648866.gitgitgadget@gmail.com","subject":"[PATCH v6 8/9] built-in add -i: use color in the main loop","fromName":"Slavica Đukić via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-13T12:41:04Z","receivedAt":"2019-11-13T12:41:50Z","isPatch":true,"sender":{"key":"name:Slavica Đukić","avatar":null},"body":"From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>\n\nThe error messages as well as the unique prefixes are colored in `git\nadd -i` by default; We need to do the same in the built-in version.\n\nSigned-off-by: Slavica Đukić <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 35 +++++++++++++++++++++++++++++------\n 1 file changed, 29 insertions(+), 6 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 8d3829fe68..8f5531c86c 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -12,6 +12,9 @@ struct add_i_state {\n \tint use_color;\n \tchar header_color[COLOR_MAXLEN];\n \tchar help_color[COLOR_MAXLEN];\n+\tchar prompt_color[COLOR_MAXLEN];\n+\tchar error_color[COLOR_MAXLEN];\n+\tchar reset_color[COLOR_MAXLEN];\n };\n \n static void init_color(struct repository *r, struct add_i_state *s,\n@@ -45,6 +48,9 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)\n \n \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n \tinit_color(r, s, \"help\", s->help_color, GIT_COLOR_BOLD_RED);\n+\tinit_color(r, s, \"prompt\", s->prompt_color, GIT_COLOR_BOLD_BLUE);\n+\tinit_color(r, s, \"error\", s->error_color, GIT_COLOR_BOLD_RED);\n+\tinit_color(r, s, \"reset\", s->reset_color, GIT_COLOR_RESET);\n }\n \n /*\n@@ -240,7 +246,8 @@ static ssize_t list_and_choose(struct add_i_state *s,\n \n \t\tlist(s, &items->items, &opts->list_opts);\n \n-\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n+\t\tcolor_fprintf(stdout, s->prompt_color, \"%s\", opts->prompt);\n+\t\tfputs(\"> \", stdout);\n \t\tfflush(stdout);\n \n \t\tif (strbuf_getline(&input, stdin) == EOF) {\n@@ -282,7 +289,8 @@ static ssize_t list_and_choose(struct add_i_state *s,\n \t\t\t\tindex = find_unique(p, items);\n \n \t\t\tif (index < 0 || index >= items->items.nr)\n-\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n+\t\t\t\tcolor_fprintf_ln(stdout, s->error_color,\n+\t\t\t\t\t\t _(\"Huh (%s)?\"), p);\n \t\t\telse {\n \t\t\t\tres = index;\n \t\t\t\tbreak;\n@@ -508,18 +516,23 @@ struct command_item {\n \tcommand_t command;\n };\n \n+struct print_command_item_data {\n+\tconst char *color, *reset;\n+};\n+\n static void print_command_item(int i, struct string_list_item *item,\n \t\t\t       void *print_command_item_data)\n {\n+\tstruct print_command_item_data *d = print_command_item_data;\n \tstruct command_item *util = item->util;\n \n \tif (!util->prefix_length ||\n \t    !is_valid_prefix(item->string, util->prefix_length))\n \t\tprintf(\" %2d: %s\", i + 1, item->string);\n \telse\n-\t\tprintf(\" %2d: [%.*s]%s\", i + 1,\n-\t\t       (int)util->prefix_length, item->string,\n-\t\t       item->string + util->prefix_length);\n+\t\tprintf(\" %2d: %s%.*s%s%s\", i + 1,\n+\t\t       d->color, (int)util->prefix_length, item->string,\n+\t\t       d->reset, item->string + util->prefix_length);\n }\n \n static void command_prompt_help(struct add_i_state *s)\n@@ -537,8 +550,9 @@ static void command_prompt_help(struct add_i_state *s)\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n+\tstruct print_command_item_data data = { \"[\", \"]\" };\n \tstruct list_and_choose_options main_loop_opts = {\n-\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n+\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, &data },\n \t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct {\n@@ -569,6 +583,15 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \n \tinit_add_i_state(&s, r);\n \n+\t/*\n+\t * When color was asked for, use the prompt color for\n+\t * highlighting, otherwise use square brackets.\n+\t */\n+\tif (s.use_color) {\n+\t\tdata.color = s.prompt_color;\n+\t\tdata.reset = s.reset_color;\n+\t}\n+\n \tstrbuf_addstr(&header, \"      \");\n \tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n-- \ngitgitgadget\n\n"},{"id":"386119","messageId":"d61cf9daeb7e265715a0bd5b922fa124c65ead81.1573648866.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v6.git.1573648866.gitgitgadget@gmail.com","subject":"[PATCH v6 4/9] built-in add -i: color the header in the `status` command","fromName":"Slavica Đukić via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-13T12:41:00Z","receivedAt":"2019-11-13T12:41:51Z","isPatch":true,"sender":{"key":"name:Slavica Đukić","avatar":null},"body":"From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>\n\nFor simplicity, we only implemented the `status` command without colors.\nThis patch starts adding color, matching what the Perl script\n`git-add--interactive.perl` does.\n\nOriginal-Patch-By: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Đukić <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 41 +++++++++++++++++++++++++++++++++++++----\n 1 file changed, 37 insertions(+), 4 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex aa35184d87..174e07ce83 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -1,5 +1,7 @@\n #include \"cache.h\"\n #include \"add-interactive.h\"\n+#include \"color.h\"\n+#include \"config.h\"\n #include \"diffcore.h\"\n #include \"revision.h\"\n #include \"refs.h\"\n@@ -7,11 +9,40 @@\n \n struct add_i_state {\n \tstruct repository *r;\n+\tint use_color;\n+\tchar header_color[COLOR_MAXLEN];\n };\n \n+static void init_color(struct repository *r, struct add_i_state *s,\n+\t\t       const char *slot_name, char *dst,\n+\t\t       const char *default_color)\n+{\n+\tchar *key = xstrfmt(\"color.interactive.%s\", slot_name);\n+\tconst char *value;\n+\n+\tif (!s->use_color)\n+\t\tdst[0] = '\\0';\n+\telse if (repo_config_get_value(r, key, &value) ||\n+\t\t color_parse(value, dst))\n+\t\tstrlcpy(dst, default_color, COLOR_MAXLEN);\n+\n+\tfree(key);\n+}\n+\n static void init_add_i_state(struct add_i_state *s, struct repository *r)\n {\n-       s->r = r;\n+\tconst char *value;\n+\n+\ts->r = r;\n+\n+\tif (repo_config_get_value(r, \"color.interactive\", &value))\n+\t\ts->use_color = -1;\n+\telse\n+\t\ts->use_color =\n+\t\t\tgit_config_colorbool(\"color.interactive\", value);\n+\ts->use_color = want_color(s->use_color);\n+\n+\tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n }\n \n struct list_options {\n@@ -20,7 +51,8 @@ struct list_options {\n \tvoid *print_item_data;\n };\n \n-static void list(struct string_list *list, struct list_options *opts)\n+static void list(struct add_i_state *s, struct string_list *list,\n+\t\t struct list_options *opts)\n {\n \tint i;\n \n@@ -28,7 +60,8 @@ static void list(struct string_list *list, struct list_options *opts)\n \t\treturn;\n \n \tif (opts->header)\n-\t\tprintf(\"%s\\n\", opts->header);\n+\t\tcolor_fprintf_ln(stdout, s->header_color,\n+\t\t\t\t \"%s\", opts->header);\n \n \tfor (i = 0; i < list->nr; i++) {\n \t\topts->print_item(i, list->items + i, opts->print_item_data);\n@@ -213,7 +246,7 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \tif (get_modified_files(s->r, files, ps) < 0)\n \t\treturn -1;\n \n-\tlist(files, opts);\n+\tlist(s, files, opts);\n \tputchar('\\n');\n \n \treturn 0;\n-- \ngitgitgadget\n\n"},{"id":"386120","messageId":"nycvar.QRO.7.76.6.1911131345310.46@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"pull.170.v6.git.1573648866.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far)","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-11-13T12:46:53Z","receivedAt":"2019-11-13T12:47:24Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Wed, 13 Nov 2019, Johannes Schindelin via GitGitGadget wrote:\n\n> This is the first leg on the long journey to a fully built-in git add -i\n> (next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3\n> [https://github.com/gitgitgadget/git/pull/172], 4\n> [https://github.com/gitgitgadget/git/pull/173], 5\n> [https://github.com/gitgitgadget/git/pull/174], and 6\n> [https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are\n> not necessarily up to date, and will be re-targeted to the appropriate\n> branches in https://github.com/gitster/git as soon as Junio picks them up.\n>\n> This here patch series reflects the part that was submitted a couple of\n> times (see https://github.com/gitgitgadget/git/pull/103) during the\n> Outreachy project by Slavica Ðukic that continued the journey based on an\n> initial patch series by Daniel Ferreira.\n>\n> It only implements the status and the help part, in the interest of making\n> the review remotely more reviewable.\n>\n> As I am a heavy user of git add -p myself and use a patched version for\n> several months already (it is so nice to not suffer over one second startup\n> until the MSYS2 Perl finally shows me anything, instead it feels\n> instantaneous), I integrated these patch series into Git for Windows\n> already, as an opt-in feature guarded by the config variable\n> add.interactive.useBuiltin (and Git for Windows' installer knows to detect\n> this version and offer the option in the graphical user interface).\n\nAnd of course I forgot to summarize the changes since v5:\n\n- Reworded two commit messages.\n- Clarified code that does not affect `patch_mode`.\n- Restricted scope of the very local variable `endp`.\n\nCiao,\nJohannes\n\n> Changes since v4:\n>\n>  * Rebased onto current master to make use of Thomas Gummerer's\n>    repo_refresh_and_write_index() as well as to avoid merge conflicts with\n>    Eric Wong's work on struct hashmap.\n>  * Instead of rolling a dedicated data struct to simulate a Trie, we now use\n>    string-list extensively (an unsorted copy and a sorted one, the latter to\n>    determine unique prefixes). This had massive ramifications on the rest of\n>    the patches... For example, the struct command_item structure no longer\n>    contains the name field, but is intended to be a util in a string_list.\n>  * Changed the commit messages and author lines to reflect Slavica's name\n>    correctly.\n>  * Touched up a couple commit messages.\n>\n> Changes since v3:\n>\n>  * Rebased to v2.23.0 to reduce friction.\n>  * free_diffstat_info() is now made public as well, and used, to avoid a\n>    memory leak.\n>  * Prepared the patches for ew/hashmap (which is strict about the hashmap\n>    entries' type in hashmap_entry_init() and friends).\n>  * The private data types have been moved from prefix-map.h to prefix-map.c.\n>  * A lot of int types were converted to more appropriate size_t in\n>    prefix-map.c.\n>  * A misleading parameter name list was renamed to the correct array.\n>  * The code comment above find_unique_prefixes() was (hopefully) improved.\n>  * The run_help() function's signature now reflects that most of the\n>    parameters are actually unused.\n>\n> Changes since v2:\n>\n>  * Rebased to master to avoid merge conflicts.\n>  * Renumbered the prefix-map test to avoid conflicts with two patch series\n>    that are currently in-flight in pu.\n>\n> Changes since v1:\n>\n>  * The config machinery was reworked completely, to not use a callback to\n>    git_config(), but instead to query the config via the repo_config_get_*()\n>    functions. This also prevents a future \"Huh???\" moment: the internal add\n>    --interactive API accepts a parameter of type struct repository *r, but\n>    the previous configuration did not use that to query the config (and\n>    could in the future be a repository other than the_repository).\n>\n>\n>  * As a consequence, the color sequences are no longer stored in file-local\n>    variables, but passed around via a struct.\n>\n>\n>  * Instead of using the magical constant -2 to quit the main loop, it is now\n>    defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is\n>    defined as -1 and used where appropriate).\n>\n>\n>  * Improved the add_prefix_item() function by avoiding buffer overruns, not\n>    reusing the struct that is used for lookup also for adding the new item,\n>    and by strengthening the bug check.\n>\n> Daniel Ferreira (2):\n>   diff: export diffstat interface\n>   built-in add -i: implement the `status` command\n>\n> Johannes Schindelin (4):\n>   Start to implement a built-in version of `git add --interactive`\n>   built-in add -i: implement the main loop\n>   built-in add -i: show unique prefixes of the commands\n>   built-in add -i: support `?` (prompt help)\n>\n> Slavica Đukić (3):\n>   built-in add -i: color the header in the `status` command\n>   built-in add -i: use color in the main loop\n>   built-in add -i: implement the `help` command\n>\n>  Documentation/config/add.txt |   5 +\n>  Makefile                     |   1 +\n>  add-interactive.c            | 651 +++++++++++++++++++++++++++++++++++\n>  add-interactive.h            |   8 +\n>  builtin/add.c                |  12 +\n>  diff.c                       |  39 +--\n>  diff.h                       |  20 ++\n>  t/README                     |   4 +\n>  t/t3701-add-interactive.sh   |  25 ++\n>  9 files changed, 742 insertions(+), 23 deletions(-)\n>  create mode 100644 add-interactive.c\n>  create mode 100644 add-interactive.h\n>\n>\n> base-commit: 566a1439f6f56c2171b8853ddbca0ad3f5098770\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v6\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v6\n> Pull-Request: https://github.com/gitgitgadget/git/pull/170\n>\n> Range-diff vs v5:\n>\n>   1:  ff59d2d0b3 !  1:  5d9962d434 Start to implement a built-in version of `git add --interactive`\n>      @@ -2,31 +2,31 @@\n>\n>           Start to implement a built-in version of `git add --interactive`\n>\n>      -    This is hardly the first conversion of a Git command that is implemented\n>      -    as a script to a built-in. So far, the most successful strategy for such\n>      -    conversions has been to add a built-in helper and call that for more and\n>      -    more functionality from the script, as more and more parts are\n>      -    converted.\n>      +    Unlike previous conversions to C, where we started with a built-in\n>      +    helper, we start this conversion by adding an interception in the\n>      +    `run_add_interactive()` function when the new opt-in\n>      +    `add.interactive.useBuiltin` config knob is turned on (or the\n>      +    corresponding environment variable `GIT_TEST_ADD_I_USE_BUILTIN`), and\n>      +    calling the new internal API function `run_add_i()` that is implemented\n>      +    directly in libgit.a.\n>\n>      -    With the interactive add, we choose a different strategy. The sole\n>      -    reason for this is that on Windows (where such a conversion has the most\n>      -    benefits in terms of speed and robustness) we face the very specific\n>      -    problem that a `system()` call in Perl seems to close `stdin` in the\n>      -    parent process when the spawned process consumes even one character from\n>      -    `stdin`. And that just does not work for us here, as it would stop the\n>      -    main loop as soon as any interactive command was performed by the\n>      -    helper. Which is almost all of the commands in `git add -i`.\n>      -\n>      -    It is almost as if Perl told us once again that it does not want us to\n>      -    use it on Windows.\n>      +    At this point, the built-in version of `git add -i` only states that it\n>      +    cannot do anything yet. In subsequent patches/patch series, the\n>      +    `run_add_i()` function will gain more and more functionality, until it\n>      +    is feature complete. The whole arc of the conversion can be found in the\n>      +    PRs #170-175 at https://github.com/gitgitgadget/git.\n>\n>      -    Instead, we follow the opposite route where we start with a bare-bones\n>      -    version of the built-in interactive add, guarded by the new\n>      -    `add.interactive.useBuiltin` config variable, and then add more and more\n>      -    functionality to it, until it is feature complete.\n>      +    The \"--helper approach\" can unfortunately not be used here: on Windows\n>      +    we face the very specific problem that a `system()` call in\n>      +    Perl seems to close `stdin` in the parent process when the spawned\n>      +    process consumes even one character from `stdin`. Which prevents us from\n>      +    implementing the main loop in C and still trying to hand off to the Perl\n>      +    script.\n>\n>      -    At this point, the built-in version of `git add -i` only states that it\n>      -    cannot do anything yet ;-)\n>      +    The very real downside of the approach we have to take here is that the\n>      +    test suite won't pass with `GIT_TEST_ADD_I_USE_BUILTIN=true` until the\n>      +    conversion is complete (the `--helper` approach would have let it pass,\n>      +    even at each of the incremental conversion steps).\n>\n>           Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n>      @@ -99,12 +99,14 @@\n>        \tstruct argv_array argv = ARGV_ARRAY_INIT;\n>       +\tint use_builtin_add_i =\n>       +\t\tgit_env_bool(\"GIT_TEST_ADD_I_USE_BUILTIN\", -1);\n>      -+\tif (use_builtin_add_i < 0)\n>      -+\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n>      -+\t\t\t\t    &use_builtin_add_i);\n>       +\n>      -+\tif (use_builtin_add_i == 1 && !patch_mode)\n>      -+\t\treturn !!run_add_i(the_repository, pathspec);\n>      ++\tif (!patch_mode) {\n>      ++\t\tif (use_builtin_add_i < 0)\n>      ++\t\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n>      ++\t\t\t\t\t    &use_builtin_add_i);\n>      ++\t\tif (use_builtin_add_i == 1)\n>      ++\t\t\treturn !!run_add_i(the_repository, pathspec);\n>      ++\t}\n>\n>        \targv_array_push(&argv, \"add--interactive\");\n>        \tif (patch_mode)\n>   2:  2fc8cc3546 =  2:  f42d7b1310 diff: export diffstat interface\n>   3:  6aaa0de4f4 =  3:  4836191271 built-in add -i: implement the `status` command\n>   4:  e405f07110 =  4:  d61cf9daeb built-in add -i: color the header in the `status` command\n>   5:  25590fbbbe !  5:  b0c04e6ec6 built-in add -i: implement the main loop\n>      @@ -7,33 +7,34 @@\n>           function that we conveniently introduced for use by the `status`\n>           command.\n>\n>      -    Apart from the \"and choose\" part, there are more differences between the\n>      -    way the `status` command calls the `list_and_choose()` function in the\n>      -    Perl version of `git add -i` compared to the other callers of said\n>      -    function. The most important ones:\n>      +    In contrast to the Perl version, in the built-in interactive `add`, we\n>      +    will keep the `list()` function (which only displays items) and the\n>      +    `list_and_choose()` function (which uses `list()` to display the items,\n>      +    and only takes care of the \"and choose\" part) separate.\n>\n>      -    - The list is not only shown, but the user is also asked to make a\n>      -      choice, possibly selecting multiple entries.\n>      +    The `list_and_choose()` function, as implemented in\n>      +    `git-add--interactive.perl` knows a few more tricks than the function we\n>      +    introduce in this patch:\n>\n>      -    - The list of items is prefixed with a marker indicating what items have\n>      -      been selected, if multi-selection is allowed.\n>      +    - There is a flag to let the user select multiple items.\n>\n>      -    - Initially, for each item a unique prefix (if there exists any within\n>      -      the given parameters) is determined, and shown in the list, and\n>      +    - In multi-select mode, the list of items is prefixed with a marker\n>      +      indicating what items have been selected.\n>      +\n>      +    - Initially, for each item a unique prefix is determined (if there\n>      +      exists any within the given parameters), and shown in the list, and\n>             accepted as a shortcut for the selection.\n>\n>      -    These features will be implemented later, except the part where the user\n>      -    can choose a command. At this stage, though, the built-in `git add -i`\n>      -    still only supports the `status` command, with the remaining commands to\n>      -    follow over the course of the next commits.\n>      +    These features will be implemented in the C version later.\n>\n>      -    In addition, we also modify `list()` to support displaying the commands\n>      -    in columns, even if there is currently only one.\n>      +    This patch does not add any new main loop command, of course, the\n>      +    built-in `git add -i` still only supports the `status` command. The\n>      +    remaining commands to follow over the course of the next commits.\n>\n>      -    The Perl script `git-add--interactive.perl` mixed the purposes of the\n>      -    \"list\" and the \"and choose\" part into the same function. In the C\n>      -    version, we will keep them separate instead, calling the `list()`\n>      -    function from the `list_and_choose()` function.\n>      +    To accommodate for listing the commands in columns, preparing for the\n>      +    commands that will be implemented over the course of the next\n>      +    patches/patch series, we teach the `list()` function to do precisely\n>      +    that.\n>\n>           Note that we only have a prompt ending in a single \">\" at this stage;\n>           later commits will add commands that display a double \">>\" to indicate\n>      @@ -101,7 +102,7 @@\n>       +\tssize_t res = LIST_AND_CHOOSE_ERROR;\n>       +\n>       +\tfor (;;) {\n>      -+\t\tchar *p, *endp;\n>      ++\t\tchar *p;\n>       +\n>       +\t\tstrbuf_reset(&input);\n>       +\n>      @@ -133,6 +134,7 @@\n>       +\t\t\t}\n>       +\n>       +\t\t\tif (isdigit(*p)) {\n>      ++\t\t\t\tchar *endp;\n>       +\t\t\t\tindex = strtoul(p, &endp, 10) - 1;\n>       +\t\t\t\tif (endp != p + sep)\n>       +\t\t\t\t\tindex = -1;\n>   6:  57fdc01463 !  6:  b6459be5eb built-in add -i: show unique prefixes of the commands\n>      @@ -177,7 +177,7 @@\n>       +\tfind_unique_prefixes(items);\n>       +\n>        \tfor (;;) {\n>      - \t\tchar *p, *endp;\n>      + \t\tchar *p;\n>\n>        \t\tstrbuf_reset(&input);\n>\n>   7:  77ad5f333a =  7:  bdf9058d9e built-in add -i: support `?` (prompt help)\n>   8:  3d0b172a7f =  8:  eafeedc49b built-in add -i: use color in the main loop\n>   9:  85e508ef11 =  9:  7fda76255a built-in add -i: implement the `help` command\n>\n> --\n> gitgitgadget\n>\n"},{"id":"386123","messageId":"xmqqzhgzan9l.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"nycvar.QRO.7.76.6.1911131326300.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-11-13T14:01:26Z","receivedAt":"2019-11-13T14:01:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> As I do not see those past '--helper' ones necessarily successes, we\n>> must agree to disagree here.\n>\n> Right. But if I recall, you never even saw the need for the conversions\n> in the first place. Maybe you still don't?\n\nYou probably are forgetting the fact that I was very supportive for\nthe rewrite of checkout, commit and format-patch (the last one being\nmy favorite) in C from scripted Porcelain.  \n\nNone of these were '--helper' style conversion and I would consider\nthem much more successful than the recent ones, some of which still\nsuffer from impedance mismatch bugs (e.g. some parts of the C\nimplementation work on the in-core index, while other parts working\non the on the on-disk index, letting them become out of sync and\nintroducing bugs).\n"},{"id":"386152","messageId":"xmqqimnn9p9p.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"5d9962d4344fa182b37cd8d969da01bc603414be.1573648866.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 1/9] Start to implement a built-in version of `git add --interactive`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-11-14T02:15:46Z","receivedAt":"2019-11-14T02:15:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> The very real downside of the approach we have to take here is that the\n> test suite won't pass with `GIT_TEST_ADD_I_USE_BUILTIN=true` until the\n> conversion is complete (the `--helper` approach would have let it pass,\n> even at each of the incremental conversion steps).\n\nThat actually is an issue of the quality of the tests you add for\nthis series.  If they are organized along the arc of run_add_i()\ngaining features, the invocation of \"git add -i\" in the tests can\nselectively use USE_BUILTIN=true to verify what has been rewritten\nso far.\n\nIn any case, I think I've seen the patches in this part of the\nmetaseries enough and I think they are quickly stabilizing.  Let's\nsee if others can find and raise issues and otherwise start cooking\nin 'next' sometime next week.\n\nThanks.\n"},{"id":"386184","messageId":"nycvar.QRO.7.76.6.1911141602350.46@tvgsbejvaqbjf.bet","threadId":"50913","inReplyTo":"xmqqimnn9p9p.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v6 1/9] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-11-14T15:07:28Z","receivedAt":"2019-11-14T15:07:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Thu, 14 Nov 2019, Junio C Hamano wrote:\n\n> any case, I think I've seen the patches in this part of the metaseries\n> enough and I think they are quickly stabilizing.  Let's see if others\n> can find and raise issues and otherwise start cooking in 'next'\n> sometime next week.\n\nWhile reviewing the next patch series in this arc, I *just* noticed a\nbuffer overrun: in the main loop, `path + sep` might point to the\ntrailing `NUL`, but we assign `p += sep + 1;` at the end (which is only\ncorrect when `path + sep` points at whitespace).\n\nThe fix is already pushed up into gitgitgadget/git#170, and the relevant\npart of the range-diff reads like this:\n\n    @@ add-interactive.c: static void list(struct add_i_state *s, struct string_list *l\n     +\t\t\t\t\tindex = -1;\n     +\t\t\t}\n     +\n    -+\t\t\tp[sep] = '\\0';\n    ++\t\t\tif (p[sep])\n    ++\t\t\t\tp[sep++] = '\\0';\n     +\t\t\tif (index < 0 || index >= items->nr)\n     +\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n     +\t\t\telse {\n    @@ add-interactive.c: static void list(struct add_i_state *s, struct string_list *l\n     +\t\t\t\tbreak;\n     +\t\t\t}\n     +\n    -+\t\t\tp += sep + 1;\n    ++\t\t\tp += sep;\n     +\t\t}\n     +\n     +\t\tif (res != LIST_AND_CHOOSE_ERROR)\n\nI plan on waiting for the PR build to finish, and maybe wait until\ntomorrow just in case any further suggestion rolls in, then submit the\nhopefully final iteration.\n\nAnd yes, I think it is a good time to start cooking this in `next`, I,\nfor one, am prone to overlook anything crucial in those patches because\nI have stared at them so often.\n\nCiao,\nDscho\n"},{"id":"386250","messageId":"xmqqpnht92or.fsf@gitster-ct.c.googlers.com","threadId":"50913","inReplyTo":"nycvar.QRO.7.76.6.1911141602350.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v6 1/9] Start to implement a built-in version of `git add --interactive`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-11-15T04:35:48Z","receivedAt":"2019-11-15T04:35:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> I plan on waiting for the PR build to finish, and maybe wait until\n> tomorrow just in case any further suggestion rolls in, then submit the\n> hopefully final iteration.\n\nThanks.\n"},{"id":"386293","messageId":"5d9962d4344fa182b37cd8d969da01bc603414be.1573816280.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v7.git.1573816280.gitgitgadget@gmail.com","subject":"[PATCH v7 1/9] Start to implement a built-in version of `git add --interactive`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-15T11:11:12Z","receivedAt":"2019-11-15T11:11:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nUnlike previous conversions to C, where we started with a built-in\nhelper, we start this conversion by adding an interception in the\n`run_add_interactive()` function when the new opt-in\n`add.interactive.useBuiltin` config knob is turned on (or the\ncorresponding environment variable `GIT_TEST_ADD_I_USE_BUILTIN`), and\ncalling the new internal API function `run_add_i()` that is implemented\ndirectly in libgit.a.\n\nAt this point, the built-in version of `git add -i` only states that it\ncannot do anything yet. In subsequent patches/patch series, the\n`run_add_i()` function will gain more and more functionality, until it\nis feature complete. The whole arc of the conversion can be found in the\nPRs #170-175 at https://github.com/gitgitgadget/git.\n\nThe \"--helper approach\" can unfortunately not be used here: on Windows\nwe face the very specific problem that a `system()` call in\nPerl seems to close `stdin` in the parent process when the spawned\nprocess consumes even one character from `stdin`. Which prevents us from\nimplementing the main loop in C and still trying to hand off to the Perl\nscript.\n\nThe very real downside of the approach we have to take here is that the\ntest suite won't pass with `GIT_TEST_ADD_I_USE_BUILTIN=true` until the\nconversion is complete (the `--helper` approach would have let it pass,\neven at each of the incremental conversion steps).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/config/add.txt |  5 +++++\n Makefile                     |  1 +\n add-interactive.c            |  7 +++++++\n add-interactive.h            |  8 ++++++++\n builtin/add.c                | 12 ++++++++++++\n t/README                     |  4 ++++\n 6 files changed, 37 insertions(+)\n create mode 100644 add-interactive.c\n create mode 100644 add-interactive.h\n\ndiff --git a/Documentation/config/add.txt b/Documentation/config/add.txt\nindex 4d753f006e..c9f748f81c 100644\n--- a/Documentation/config/add.txt\n+++ b/Documentation/config/add.txt\n@@ -5,3 +5,8 @@ add.ignore-errors (deprecated)::\n \toption of linkgit:git-add[1].  `add.ignore-errors` is deprecated,\n \tas it does not follow the usual naming convention for configuration\n \tvariables.\n+\n+add.interactive.useBuiltin::\n+\t[EXPERIMENTAL] Set to `true` to use the experimental built-in\n+\timplementation of the interactive version of linkgit:git-add[1]\n+\tinstead of the Perl script version. Is `false` by default.\ndiff --git a/Makefile b/Makefile\nindex 58b92af54b..6c4a1e0ee5 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -823,6 +823,7 @@ LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentat\n \t-name '*.h' -print)))\n \n LIB_OBJS += abspath.o\n+LIB_OBJS += add-interactive.o\n LIB_OBJS += advice.o\n LIB_OBJS += alias.o\n LIB_OBJS += alloc.o\ndiff --git a/add-interactive.c b/add-interactive.c\nnew file mode 100644\nindex 0000000000..482e458dc6\n--- /dev/null\n+++ b/add-interactive.c\n@@ -0,0 +1,7 @@\n+#include \"cache.h\"\n+#include \"add-interactive.h\"\n+\n+int run_add_i(struct repository *r, const struct pathspec *ps)\n+{\n+\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n+}\ndiff --git a/add-interactive.h b/add-interactive.h\nnew file mode 100644\nindex 0000000000..7043b8741d\n--- /dev/null\n+++ b/add-interactive.h\n@@ -0,0 +1,8 @@\n+#ifndef ADD_INTERACTIVE_H\n+#define ADD_INTERACTIVE_H\n+\n+struct repository;\n+struct pathspec;\n+int run_add_i(struct repository *r, const struct pathspec *ps);\n+\n+#endif\ndiff --git a/builtin/add.c b/builtin/add.c\nindex dd18e5c9b6..d4686d5218 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -20,6 +20,7 @@\n #include \"bulk-checkin.h\"\n #include \"argv-array.h\"\n #include \"submodule.h\"\n+#include \"add-interactive.h\"\n \n static const char * const builtin_add_usage[] = {\n \tN_(\"git add [<options>] [--] <pathspec>...\"),\n@@ -185,6 +186,16 @@ int run_add_interactive(const char *revision, const char *patch_mode,\n {\n \tint status, i;\n \tstruct argv_array argv = ARGV_ARRAY_INIT;\n+\tint use_builtin_add_i =\n+\t\tgit_env_bool(\"GIT_TEST_ADD_I_USE_BUILTIN\", -1);\n+\n+\tif (!patch_mode) {\n+\t\tif (use_builtin_add_i < 0)\n+\t\t\tgit_config_get_bool(\"add.interactive.usebuiltin\",\n+\t\t\t\t\t    &use_builtin_add_i);\n+\t\tif (use_builtin_add_i == 1)\n+\t\t\treturn !!run_add_i(the_repository, pathspec);\n+\t}\n \n \targv_array_push(&argv, \"add--interactive\");\n \tif (patch_mode)\n@@ -319,6 +330,7 @@ static int add_config(const char *var, const char *value, void *cb)\n \t\tignore_add_errors = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \ndiff --git a/t/README b/t/README\nindex 60d5b77bcc..5132ec83f8 100644\n--- a/t/README\n+++ b/t/README\n@@ -397,6 +397,10 @@ GIT_TEST_STASH_USE_BUILTIN=<boolean>, when false, disables the\n built-in version of git-stash. See 'stash.useBuiltin' in\n git-config(1).\n \n+GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the\n+built-in version of git add -i. See 'add.interactive.useBuiltin' in\n+git-config(1).\n+\n GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading\n of the index for the whole test suite by bypassing the default number of\n cache entries and thread minimums. Setting this to 1 will make the\n-- \ngitgitgadget\n\n"},{"id":"386294","messageId":"d61cf9daeb7e265715a0bd5b922fa124c65ead81.1573816280.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v7.git.1573816280.gitgitgadget@gmail.com","subject":"[PATCH v7 4/9] built-in add -i: color the header in the `status` command","fromName":"Slavica Đukić via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-15T11:11:15Z","receivedAt":"2019-11-15T11:11:29Z","isPatch":true,"sender":{"key":"name:Slavica Đukić","avatar":null},"body":"From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>\n\nFor simplicity, we only implemented the `status` command without colors.\nThis patch starts adding color, matching what the Perl script\n`git-add--interactive.perl` does.\n\nOriginal-Patch-By: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Đukić <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 41 +++++++++++++++++++++++++++++++++++++----\n 1 file changed, 37 insertions(+), 4 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex aa35184d87..174e07ce83 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -1,5 +1,7 @@\n #include \"cache.h\"\n #include \"add-interactive.h\"\n+#include \"color.h\"\n+#include \"config.h\"\n #include \"diffcore.h\"\n #include \"revision.h\"\n #include \"refs.h\"\n@@ -7,11 +9,40 @@\n \n struct add_i_state {\n \tstruct repository *r;\n+\tint use_color;\n+\tchar header_color[COLOR_MAXLEN];\n };\n \n+static void init_color(struct repository *r, struct add_i_state *s,\n+\t\t       const char *slot_name, char *dst,\n+\t\t       const char *default_color)\n+{\n+\tchar *key = xstrfmt(\"color.interactive.%s\", slot_name);\n+\tconst char *value;\n+\n+\tif (!s->use_color)\n+\t\tdst[0] = '\\0';\n+\telse if (repo_config_get_value(r, key, &value) ||\n+\t\t color_parse(value, dst))\n+\t\tstrlcpy(dst, default_color, COLOR_MAXLEN);\n+\n+\tfree(key);\n+}\n+\n static void init_add_i_state(struct add_i_state *s, struct repository *r)\n {\n-       s->r = r;\n+\tconst char *value;\n+\n+\ts->r = r;\n+\n+\tif (repo_config_get_value(r, \"color.interactive\", &value))\n+\t\ts->use_color = -1;\n+\telse\n+\t\ts->use_color =\n+\t\t\tgit_config_colorbool(\"color.interactive\", value);\n+\ts->use_color = want_color(s->use_color);\n+\n+\tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n }\n \n struct list_options {\n@@ -20,7 +51,8 @@ struct list_options {\n \tvoid *print_item_data;\n };\n \n-static void list(struct string_list *list, struct list_options *opts)\n+static void list(struct add_i_state *s, struct string_list *list,\n+\t\t struct list_options *opts)\n {\n \tint i;\n \n@@ -28,7 +60,8 @@ static void list(struct string_list *list, struct list_options *opts)\n \t\treturn;\n \n \tif (opts->header)\n-\t\tprintf(\"%s\\n\", opts->header);\n+\t\tcolor_fprintf_ln(stdout, s->header_color,\n+\t\t\t\t \"%s\", opts->header);\n \n \tfor (i = 0; i < list->nr; i++) {\n \t\topts->print_item(i, list->items + i, opts->print_item_data);\n@@ -213,7 +246,7 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \tif (get_modified_files(s->r, files, ps) < 0)\n \t\treturn -1;\n \n-\tlist(files, opts);\n+\tlist(s, files, opts);\n \tputchar('\\n');\n \n \treturn 0;\n-- \ngitgitgadget\n\n"},{"id":"386295","messageId":"pull.170.v7.git.1573816280.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v6.git.1573648866.gitgitgadget@gmail.com","subject":"[PATCH v7 0/9] git add -i: add a rudimentary version in C (supporting only status and help so far)","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-15T11:11:11Z","receivedAt":"2019-11-15T11:11:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is the first leg on the long journey to a fully built-in git add -i \n(next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3\n[https://github.com/gitgitgadget/git/pull/172], 4\n[https://github.com/gitgitgadget/git/pull/173], 5\n[https://github.com/gitgitgadget/git/pull/174], and 6\n[https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are\nnot necessarily up to date, and will be re-targeted to the appropriate\nbranches in https://github.com/gitster/git as soon as Junio picks them up.\n\nThis here patch series reflects the part that was submitted a couple of\ntimes (see https://github.com/gitgitgadget/git/pull/103) during the\nOutreachy project by Slavica Ðukic that continued the journey based on an\ninitial patch series by Daniel Ferreira.\n\nIt only implements the status and the help part, in the interest of making\nthe review remotely more reviewable.\n\nAs I am a heavy user of git add -p myself and use a patched version for\nseveral months already (it is so nice to not suffer over one second startup\nuntil the MSYS2 Perl finally shows me anything, instead it feels\ninstantaneous), I integrated these patch series into Git for Windows\nalready, as an opt-in feature guarded by the config variable \nadd.interactive.useBuiltin (and Git for Windows' installer knows to detect\nthis version and offer the option in the graphical user interface).\n\nChanges since v6:\n\n * Fixed a potential buffer overrun when parsing numbers/number ranges.\n\nChanges since v5:\n\n * Reworded two commit messages.\n * Clarified code that does not affect patch_mode.\n * Restricted scope of the very local variable endp.\n\nChanges since v4:\n\n * Rebased onto current master to make use of Thomas Gummerer's \n   repo_refresh_and_write_index() as well as to avoid merge conflicts with\n   Eric Wong's work on struct hashmap.\n * Instead of rolling a dedicated data struct to simulate a Trie, we now use \n   string-list extensively (an unsorted copy and a sorted one, the latter to\n   determine unique prefixes). This had massive ramifications on the rest of\n   the patches... For example, the struct command_item structure no longer\n   contains the name field, but is intended to be a util in a string_list.\n * Changed the commit messages and author lines to reflect Slavica's name\n   correctly.\n * Touched up a couple commit messages.\n\nChanges since v3:\n\n * Rebased to v2.23.0 to reduce friction.\n * free_diffstat_info() is now made public as well, and used, to avoid a\n   memory leak.\n * Prepared the patches for ew/hashmap (which is strict about the hashmap\n   entries' type in hashmap_entry_init() and friends).\n * The private data types have been moved from prefix-map.h to prefix-map.c.\n * A lot of int types were converted to more appropriate size_t in \n   prefix-map.c.\n * A misleading parameter name list was renamed to the correct array.\n * The code comment above find_unique_prefixes() was (hopefully) improved.\n * The run_help() function's signature now reflects that most of the\n   parameters are actually unused.\n\nChanges since v2:\n\n * Rebased to master to avoid merge conflicts.\n * Renumbered the prefix-map test to avoid conflicts with two patch series\n   that are currently in-flight in pu.\n\nChanges since v1:\n\n * The config machinery was reworked completely, to not use a callback to \n   git_config(), but instead to query the config via the repo_config_get_*() \n   functions. This also prevents a future \"Huh???\" moment: the internal add\n   --interactive API accepts a parameter of type struct repository *r, but\n   the previous configuration did not use that to query the config (and\n   could in the future be a repository other than the_repository).\n   \n   \n * As a consequence, the color sequences are no longer stored in file-local\n   variables, but passed around via a struct.\n   \n   \n * Instead of using the magical constant -2 to quit the main loop, it is now\n   defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is\n   defined as -1 and used where appropriate).\n   \n   \n * Improved the add_prefix_item() function by avoiding buffer overruns, not\n   reusing the struct that is used for lookup also for adding the new item,\n   and by strengthening the bug check.\n\nDaniel Ferreira (2):\n  diff: export diffstat interface\n  built-in add -i: implement the `status` command\n\nJohannes Schindelin (4):\n  Start to implement a built-in version of `git add --interactive`\n  built-in add -i: implement the main loop\n  built-in add -i: show unique prefixes of the commands\n  built-in add -i: support `?` (prompt help)\n\nSlavica Đukić (3):\n  built-in add -i: color the header in the `status` command\n  built-in add -i: use color in the main loop\n  built-in add -i: implement the `help` command\n\n Documentation/config/add.txt |   5 +\n Makefile                     |   1 +\n add-interactive.c            | 652 +++++++++++++++++++++++++++++++++++\n add-interactive.h            |   8 +\n builtin/add.c                |  12 +\n diff.c                       |  39 +--\n diff.h                       |  20 ++\n t/README                     |   4 +\n t/t3701-add-interactive.sh   |  25 ++\n 9 files changed, 743 insertions(+), 23 deletions(-)\n create mode 100644 add-interactive.c\n create mode 100644 add-interactive.h\n\n\nbase-commit: 566a1439f6f56c2171b8853ddbca0ad3f5098770\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v7\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v7\nPull-Request: https://github.com/gitgitgadget/git/pull/170\n\nRange-diff vs v6:\n\n  1:  5d9962d434 =  1:  5d9962d434 Start to implement a built-in version of `git add --interactive`\n  2:  f42d7b1310 =  2:  f42d7b1310 diff: export diffstat interface\n  3:  4836191271 =  3:  4836191271 built-in add -i: implement the `status` command\n  4:  d61cf9daeb =  4:  d61cf9daeb built-in add -i: color the header in the `status` command\n  5:  b0c04e6ec6 !  5:  601c371089 built-in add -i: implement the main loop\n     @@ -140,7 +140,8 @@\n      +\t\t\t\t\tindex = -1;\n      +\t\t\t}\n      +\n     -+\t\t\tp[sep] = '\\0';\n     ++\t\t\tif (p[sep])\n     ++\t\t\t\tp[sep++] = '\\0';\n      +\t\t\tif (index < 0 || index >= items->nr)\n      +\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n      +\t\t\telse {\n     @@ -148,7 +149,7 @@\n      +\t\t\t\tbreak;\n      +\t\t\t}\n      +\n     -+\t\t\tp += sep + 1;\n     ++\t\t\tp += sep;\n      +\t\t}\n      +\n      +\t\tif (res != LIST_AND_CHOOSE_ERROR)\n  6:  b6459be5eb !  6:  978bb25b81 built-in add -i: show unique prefixes of the commands\n     @@ -187,9 +187,9 @@\n       \t\tprintf(\"%s%s\", opts->prompt, \"> \");\n       \t\tfflush(stdout);\n      @@\n     - \t\t\t}\n       \n     - \t\t\tp[sep] = '\\0';\n     + \t\t\tif (p[sep])\n     + \t\t\t\tp[sep++] = '\\0';\n      -\t\t\tif (index < 0 || index >= items->nr)\n      +\t\t\tif (index < 0)\n      +\t\t\t\tindex = find_unique(p, items);\n  7:  bdf9058d9e =  7:  d7d447026c built-in add -i: support `?` (prompt help)\n  8:  eafeedc49b =  8:  bcf4aa7c5e built-in add -i: use color in the main loop\n  9:  7fda76255a =  9:  ccc6fb4558 built-in add -i: implement the `help` command\n\n-- \ngitgitgadget\n"},{"id":"386296","messageId":"d7d447026cd5f00c0b7054da5dcc6334ea8be2b0.1573816280.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v7.git.1573816280.gitgitgadget@gmail.com","subject":"[PATCH v7 7/9] built-in add -i: support `?` (prompt help)","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-15T11:11:18Z","receivedAt":"2019-11-15T11:11:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWith this change, we print out the same colored help text that the\nPerl-based `git add -i` prints in the main loop when question mark is\nentered.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 22 +++++++++++++++++++++-\n 1 file changed, 21 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex c5d95d4796..0a03b9017d 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -11,6 +11,7 @@ struct add_i_state {\n \tstruct repository *r;\n \tint use_color;\n \tchar header_color[COLOR_MAXLEN];\n+\tchar help_color[COLOR_MAXLEN];\n };\n \n static void init_color(struct repository *r, struct add_i_state *s,\n@@ -43,6 +44,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)\n \ts->use_color = want_color(s->use_color);\n \n \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n+\tinit_color(r, s, \"help\", s->help_color, GIT_COLOR_BOLD_RED);\n }\n \n /*\n@@ -210,6 +212,7 @@ struct list_and_choose_options {\n \tstruct list_options list_opts;\n \n \tconst char *prompt;\n+\tvoid (*print_help)(struct add_i_state *s);\n };\n \n #define LIST_AND_CHOOSE_ERROR (-1)\n@@ -250,6 +253,11 @@ static ssize_t list_and_choose(struct add_i_state *s,\n \t\tif (!input.len)\n \t\t\tbreak;\n \n+\t\tif (!strcmp(input.buf, \"?\")) {\n+\t\t\topts->print_help(s);\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tp = input.buf;\n \t\tfor (;;) {\n \t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n@@ -515,12 +523,24 @@ static void print_command_item(int i, struct string_list_item *item,\n \t\t       item->string + util->prefix_length);\n }\n \n+static void command_prompt_help(struct add_i_state *s)\n+{\n+\tconst char *help_color = s->help_color;\n+\tcolor_fprintf_ln(stdout, help_color, \"%s\", _(\"Prompt help:\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"1          - %s\",\n+\t\t\t _(\"select a numbered item\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"foo        - %s\",\n+\t\t\t _(\"select item based on unique prefix\"));\n+\tcolor_fprintf_ln(stdout, help_color, \"           - %s\",\n+\t\t\t _(\"(empty) select nothing\"));\n+}\n+\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n \tstruct list_and_choose_options main_loop_opts = {\n \t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n-\t\tN_(\"What now\")\n+\t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct {\n \t\tconst char *string;\n-- \ngitgitgadget\n\n"},{"id":"386297","messageId":"601c37108930afe48e25290f6e54b660a25faaf1.1573816280.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v7.git.1573816280.gitgitgadget@gmail.com","subject":"[PATCH v7 5/9] built-in add -i: implement the main loop","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-15T11:11:16Z","receivedAt":"2019-11-15T11:11:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe reason why we did not start with the main loop to begin with is that\nit is the first user of `list_and_choose()`, which uses the `list()`\nfunction that we conveniently introduced for use by the `status`\ncommand.\n\nIn contrast to the Perl version, in the built-in interactive `add`, we\nwill keep the `list()` function (which only displays items) and the\n`list_and_choose()` function (which uses `list()` to display the items,\nand only takes care of the \"and choose\" part) separate.\n\nThe `list_and_choose()` function, as implemented in\n`git-add--interactive.perl` knows a few more tricks than the function we\nintroduce in this patch:\n\n- There is a flag to let the user select multiple items.\n\n- In multi-select mode, the list of items is prefixed with a marker\n  indicating what items have been selected.\n\n- Initially, for each item a unique prefix is determined (if there\n  exists any within the given parameters), and shown in the list, and\n  accepted as a shortcut for the selection.\n\nThese features will be implemented in the C version later.\n\nThis patch does not add any new main loop command, of course, the\nbuilt-in `git add -i` still only supports the `status` command. The\nremaining commands to follow over the course of the next commits.\n\nTo accommodate for listing the commands in columns, preparing for the\ncommands that will be implemented over the course of the next\npatches/patch series, we teach the `list()` function to do precisely\nthat.\n\nNote that we only have a prompt ending in a single \">\" at this stage;\nlater commits will add commands that display a double \">>\" to indicate\nthat the user is in a different loop than the main one.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 137 +++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 135 insertions(+), 2 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 174e07ce83..76d9824de0 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -46,6 +46,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)\n }\n \n struct list_options {\n+\tint columns;\n \tconst char *header;\n \tvoid (*print_item)(int i, struct string_list_item *item, void *print_item_data);\n \tvoid *print_item_data;\n@@ -54,7 +55,7 @@ struct list_options {\n static void list(struct add_i_state *s, struct string_list *list,\n \t\t struct list_options *opts)\n {\n-\tint i;\n+\tint i, last_lf = 0;\n \n \tif (!list->nr)\n \t\treturn;\n@@ -65,8 +66,98 @@ static void list(struct add_i_state *s, struct string_list *list,\n \n \tfor (i = 0; i < list->nr; i++) {\n \t\topts->print_item(i, list->items + i, opts->print_item_data);\n+\n+\t\tif ((opts->columns) && ((i + 1) % (opts->columns))) {\n+\t\t\tputchar('\\t');\n+\t\t\tlast_lf = 0;\n+\t\t}\n+\t\telse {\n+\t\t\tputchar('\\n');\n+\t\t\tlast_lf = 1;\n+\t\t}\n+\t}\n+\n+\tif (!last_lf)\n \t\tputchar('\\n');\n+}\n+struct list_and_choose_options {\n+\tstruct list_options list_opts;\n+\n+\tconst char *prompt;\n+};\n+\n+#define LIST_AND_CHOOSE_ERROR (-1)\n+#define LIST_AND_CHOOSE_QUIT  (-2)\n+\n+/*\n+ * Returns the selected index.\n+ *\n+ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n+ * `LIST_AND_CHOOSE_QUIT` is returned.\n+ */\n+static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,\n+\t\t\t       struct list_and_choose_options *opts)\n+{\n+\tstruct strbuf input = STRBUF_INIT;\n+\tssize_t res = LIST_AND_CHOOSE_ERROR;\n+\n+\tfor (;;) {\n+\t\tchar *p;\n+\n+\t\tstrbuf_reset(&input);\n+\n+\t\tlist(s, items, &opts->list_opts);\n+\n+\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n+\t\tfflush(stdout);\n+\n+\t\tif (strbuf_getline(&input, stdin) == EOF) {\n+\t\t\tputchar('\\n');\n+\t\t\tres = LIST_AND_CHOOSE_QUIT;\n+\t\t\tbreak;\n+\t\t}\n+\t\tstrbuf_trim(&input);\n+\n+\t\tif (!input.len)\n+\t\t\tbreak;\n+\n+\t\tp = input.buf;\n+\t\tfor (;;) {\n+\t\t\tsize_t sep = strcspn(p, \" \\t\\r\\n,\");\n+\t\t\tssize_t index = -1;\n+\n+\t\t\tif (!sep) {\n+\t\t\t\tif (!*p)\n+\t\t\t\t\tbreak;\n+\t\t\t\tp++;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (isdigit(*p)) {\n+\t\t\t\tchar *endp;\n+\t\t\t\tindex = strtoul(p, &endp, 10) - 1;\n+\t\t\t\tif (endp != p + sep)\n+\t\t\t\t\tindex = -1;\n+\t\t\t}\n+\n+\t\t\tif (p[sep])\n+\t\t\t\tp[sep++] = '\\0';\n+\t\t\tif (index < 0 || index >= items->nr)\n+\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n+\t\t\telse {\n+\t\t\t\tres = index;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tp += sep;\n+\t\t}\n+\n+\t\tif (res != LIST_AND_CHOOSE_ERROR)\n+\t\t\tbreak;\n \t}\n+\n+\tstrbuf_release(&input);\n+\treturn res;\n }\n \n struct adddel {\n@@ -252,20 +343,48 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \treturn 0;\n }\n \n+typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,\n+\t\t\t struct string_list *files,\n+\t\t\t struct list_options *opts);\n+\n+static void print_command_item(int i, struct string_list_item *item,\n+\t\t\t       void *print_command_item_data)\n+{\n+\tprintf(\" %2d: %s\", i + 1, item->string);\n+}\n+\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n+\tstruct list_and_choose_options main_loop_opts = {\n+\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n+\t\tN_(\"What now\")\n+\t};\n+\tstruct {\n+\t\tconst char *string;\n+\t\tcommand_t command;\n+\t} command_list[] = {\n+\t\t{ \"status\", run_status },\n+\t};\n+\tstruct string_list commands = STRING_LIST_INIT_NODUP;\n+\n \tstruct print_file_item_data print_file_item_data = {\n \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n \t};\n \tstruct list_options opts = {\n-\t\tNULL, print_file_item, &print_file_item_data\n+\t\t0, NULL, print_file_item, &print_file_item_data\n \t};\n \tstruct strbuf header = STRBUF_INIT;\n \tstruct string_list files = STRING_LIST_INIT_DUP;\n+\tssize_t i;\n \tint res = 0;\n \n+\tfor (i = 0; i < ARRAY_SIZE(command_list); i++)\n+\t\tstring_list_append(&commands, command_list[i].string)\n+\t\t\t->util = command_list[i].command;\n+\n \tinit_add_i_state(&s, r);\n+\n \tstrbuf_addstr(&header, \"      \");\n \tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n@@ -279,11 +398,25 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \n \tres = run_status(&s, ps, &files, &opts);\n \n+\tfor (;;) {\n+\t\ti = list_and_choose(&s, &commands, &main_loop_opts);\n+\t\tif (i == LIST_AND_CHOOSE_QUIT) {\n+\t\t\tprintf(_(\"Bye.\\n\"));\n+\t\t\tres = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (i != LIST_AND_CHOOSE_ERROR) {\n+\t\t\tcommand_t command = commands.items[i].util;\n+\t\t\tres = command(&s, ps, &files, &opts);\n+\t\t}\n+\t}\n+\n \tstring_list_clear(&files, 1);\n \tstrbuf_release(&print_file_item_data.buf);\n \tstrbuf_release(&print_file_item_data.index);\n \tstrbuf_release(&print_file_item_data.worktree);\n \tstrbuf_release(&header);\n+\tstring_list_clear(&commands, 0);\n \n \treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"386300","messageId":"f42d7b13107743fc1aa9cc42ee188fdeb10c702b.1573816280.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v7.git.1573816280.gitgitgadget@gmail.com","subject":"[PATCH v7 2/9] diff: export diffstat interface","fromName":"Daniel Ferreira via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-15T11:11:13Z","receivedAt":"2019-11-15T11:11:31Z","isPatch":true,"sender":{"key":"bnmvco@gmail.com","avatar":"https://avatars.githubusercontent.com/u/735397?v=4"},"body":"From: Daniel Ferreira <bnmvco@gmail.com>\n\nMake the diffstat interface (namely, the diffstat_t struct and\ncompute_diffstat) no longer be internal to diff.c and allow it to be used\nby other parts of git.\n\nThis is helpful for code that may want to easily extract information\nfrom files using the diff machinery, while flushing it differently from\nhow the show_* functions used by diff_flush() do it. One example is the\nbuiltin implementation of git-add--interactive's status.\n\nSigned-off-by: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Đukić <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n diff.c | 39 ++++++++++++++++-----------------------\n diff.h | 20 ++++++++++++++++++++\n 2 files changed, 36 insertions(+), 23 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex afe4400a60..5703a9b78f 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -2495,22 +2495,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b)\n \t}\n }\n \n-struct diffstat_t {\n-\tint nr;\n-\tint alloc;\n-\tstruct diffstat_file {\n-\t\tchar *from_name;\n-\t\tchar *name;\n-\t\tchar *print_name;\n-\t\tconst char *comments;\n-\t\tunsigned is_unmerged:1;\n-\t\tunsigned is_binary:1;\n-\t\tunsigned is_renamed:1;\n-\t\tunsigned is_interesting:1;\n-\t\tuintmax_t added, deleted;\n-\t} **files;\n-};\n-\n static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,\n \t\t\t\t\t  const char *name_a,\n \t\t\t\t\t  const char *name_b)\n@@ -3157,7 +3141,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o\n \tgather_dirstat(options, &dir, changed, \"\", 0);\n }\n \n-static void free_diffstat_info(struct diffstat_t *diffstat)\n+void free_diffstat_info(struct diffstat_t *diffstat)\n {\n \tint i;\n \tfor (i = 0; i < diffstat->nr; i++) {\n@@ -6283,12 +6267,7 @@ void diff_flush(struct diff_options *options)\n \t    dirstat_by_line) {\n \t\tstruct diffstat_t diffstat;\n \n-\t\tmemset(&diffstat, 0, sizeof(struct diffstat_t));\n-\t\tfor (i = 0; i < q->nr; i++) {\n-\t\t\tstruct diff_filepair *p = q->queue[i];\n-\t\t\tif (check_pair_status(p))\n-\t\t\t\tdiff_flush_stat(p, options, &diffstat);\n-\t\t}\n+\t\tcompute_diffstat(options, &diffstat, q);\n \t\tif (output_format & DIFF_FORMAT_NUMSTAT)\n \t\t\tshow_numstat(&diffstat, options);\n \t\tif (output_format & DIFF_FORMAT_DIFFSTAT)\n@@ -6621,6 +6600,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)\n \treturn ignored;\n }\n \n+void compute_diffstat(struct diff_options *options,\n+\t\t      struct diffstat_t *diffstat,\n+\t\t      struct diff_queue_struct *q)\n+{\n+\tint i;\n+\n+\tmemset(diffstat, 0, sizeof(struct diffstat_t));\n+\tfor (i = 0; i < q->nr; i++) {\n+\t\tstruct diff_filepair *p = q->queue[i];\n+\t\tif (check_pair_status(p))\n+\t\t\tdiff_flush_stat(p, options, diffstat);\n+\t}\n+}\n+\n void diff_addremove(struct diff_options *options,\n \t\t    int addremove, unsigned mode,\n \t\t    const struct object_id *oid,\ndiff --git a/diff.h b/diff.h\nindex 7f8f024feb..d986ddc3b5 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -245,6 +245,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);\n void diff_emit_submodule_pipethrough(struct diff_options *o,\n \t\t\t\t     const char *line, int len);\n \n+struct diffstat_t {\n+\tint nr;\n+\tint alloc;\n+\tstruct diffstat_file {\n+\t\tchar *from_name;\n+\t\tchar *name;\n+\t\tchar *print_name;\n+\t\tconst char *comments;\n+\t\tunsigned is_unmerged:1;\n+\t\tunsigned is_binary:1;\n+\t\tunsigned is_renamed:1;\n+\t\tunsigned is_interesting:1;\n+\t\tuintmax_t added, deleted;\n+\t} **files;\n+};\n+\n enum color_diff {\n \tDIFF_RESET = 0,\n \tDIFF_CONTEXT = 1,\n@@ -334,6 +350,10 @@ void diff_change(struct diff_options *,\n \n struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);\n \n+void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,\n+\t\t      struct diff_queue_struct *q);\n+void free_diffstat_info(struct diffstat_t *diffstat);\n+\n #define DIFF_SETUP_REVERSE      \t1\n #define DIFF_SETUP_USE_SIZE_CACHE\t4\n \n-- \ngitgitgadget\n\n"},{"id":"386299","messageId":"978bb25b81b9f0bc90ccd8be8034aa3960bc2ed6.1573816280.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v7.git.1573816280.gitgitgadget@gmail.com","subject":"[PATCH v7 6/9] built-in add -i: show unique prefixes of the commands","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-15T11:11:17Z","receivedAt":"2019-11-15T11:11:34Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nJust like in the Perl script `git-add--interactive.perl`, for each\ncommand a unique prefix is determined (if there exists any within the\ngiven parameters), and shown in the list, and accepted as a shortcut for\nthe command.\n\nTo determine the unique prefixes, as well as to look up the command in\nquestion, we use a copy of the list and sort it.\n\nWhile this might seem like overkill for a single command, it will make\nmuch more sense when all the commands are implemented, and when we reuse\nthe same logic to present a list of files to edit, with convenient\nunique prefixes.\n\nAt the start of the development of this patch series, a dedicated data\nstructure was introduced that imitated the Trie that the Perl version\nimplements. However, this was deemed overkill, and we now simply sort\nthe list before determining the length of the unique prefixes by looking\nat each item's neighbor. As a bonus, we now use the same sorted list to\nperform a binary search using the user-provided prefix as search key.\n\nOriginal-patch-by: Slavica Đukić <slawica92@hotmail.com>\nHelped-by: SZEDER Gábor <szeder.dev@gmail.com>\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n---\n add-interactive.c | 188 +++++++++++++++++++++++++++++++++++++++++++---\n 1 file changed, 177 insertions(+), 11 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 76d9824de0..c5d95d4796 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -45,6 +45,132 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)\n \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n }\n \n+/*\n+ * A \"prefix item list\" is a list of items that are identified by a string, and\n+ * a unique prefix (if any) is determined for each item.\n+ *\n+ * It is implemented in the form of a pair of `string_list`s, the first one\n+ * duplicating the strings, with the `util` field pointing at a structure whose\n+ * first field must be `size_t prefix_length`.\n+ *\n+ * That `prefix_length` field will be computed by `find_unique_prefixes()`; It\n+ * will be set to zero if no valid, unique prefix could be found.\n+ *\n+ * The second `string_list` is called `sorted` and does _not_ duplicate the\n+ * strings but simply reuses the first one's, with the `util` field pointing at\n+ * the `string_item_list` of the first `string_list`. It  will be populated and\n+ * sorted by `find_unique_prefixes()`.\n+ */\n+struct prefix_item_list {\n+\tstruct string_list items;\n+\tstruct string_list sorted;\n+\tsize_t min_length, max_length;\n+};\n+#define PREFIX_ITEM_LIST_INIT \\\n+\t{ STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, 1, 4 }\n+\n+static void prefix_item_list_clear(struct prefix_item_list *list)\n+{\n+\tstring_list_clear(&list->items, 1);\n+\tstring_list_clear(&list->sorted, 0);\n+}\n+\n+static void extend_prefix_length(struct string_list_item *p,\n+\t\t\t\t const char *other_string, size_t max_length)\n+{\n+\tsize_t *len = p->util;\n+\n+\tif (!*len || memcmp(p->string, other_string, *len))\n+\t\treturn;\n+\n+\tfor (;;) {\n+\t\tchar c = p->string[*len];\n+\n+\t\t/*\n+\t\t * Is `p` a strict prefix of `other`? Or have we exhausted the\n+\t\t * maximal length of the prefix? Or is the current character a\n+\t\t * multi-byte UTF-8 one? If so, there is no valid, unique\n+\t\t * prefix.\n+\t\t */\n+\t\tif (!c || ++*len > max_length || !isascii(c)) {\n+\t\t\t*len = 0;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (c != other_string[*len - 1])\n+\t\t\tbreak;\n+\t}\n+}\n+\n+static void find_unique_prefixes(struct prefix_item_list *list)\n+{\n+\tsize_t i;\n+\n+\tif (list->sorted.nr == list->items.nr)\n+\t\treturn;\n+\n+\tstring_list_clear(&list->sorted, 0);\n+\t/* Avoid reallocating incrementally */\n+\tlist->sorted.items = xmalloc(st_mult(sizeof(*list->sorted.items),\n+\t\t\t\t\t     list->items.nr));\n+\tlist->sorted.nr = list->sorted.alloc = list->items.nr;\n+\n+\tfor (i = 0; i < list->items.nr; i++) {\n+\t\tlist->sorted.items[i].string = list->items.items[i].string;\n+\t\tlist->sorted.items[i].util = list->items.items + i;\n+\t}\n+\n+\tstring_list_sort(&list->sorted);\n+\n+\tfor (i = 0; i < list->sorted.nr; i++) {\n+\t\tstruct string_list_item *sorted_item = list->sorted.items + i;\n+\t\tstruct string_list_item *item = sorted_item->util;\n+\t\tsize_t *len = item->util;\n+\n+\t\t*len = 0;\n+\t\twhile (*len < list->min_length) {\n+\t\t\tchar c = item->string[(*len)++];\n+\n+\t\t\tif (!c || !isascii(c)) {\n+\t\t\t\t*len = 0;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (i > 0)\n+\t\t\textend_prefix_length(item, sorted_item[-1].string,\n+\t\t\t\t\t     list->max_length);\n+\t\tif (i + 1 < list->sorted.nr)\n+\t\t\textend_prefix_length(item, sorted_item[1].string,\n+\t\t\t\t\t     list->max_length);\n+\t}\n+}\n+\n+static ssize_t find_unique(const char *string, struct prefix_item_list *list)\n+{\n+\tint index = string_list_find_insert_index(&list->sorted, string, 1);\n+\tstruct string_list_item *item;\n+\n+\tif (list->items.nr != list->sorted.nr)\n+\t\tBUG(\"prefix_item_list in inconsistent state (%\"PRIuMAX\n+\t\t    \" vs %\"PRIuMAX\")\",\n+\t\t    (uintmax_t)list->items.nr, (uintmax_t)list->sorted.nr);\n+\n+\tif (index < 0)\n+\t\titem = list->sorted.items[-1 - index].util;\n+\telse if (index > 0 &&\n+\t\t starts_with(list->sorted.items[index - 1].string, string))\n+\t\treturn -1;\n+\telse if (index + 1 < list->sorted.nr &&\n+\t\t starts_with(list->sorted.items[index + 1].string, string))\n+\t\treturn -1;\n+\telse if (index < list->sorted.nr)\n+\t\titem = list->sorted.items[index].util;\n+\telse\n+\t\treturn -1;\n+\treturn item - list->items.items;\n+}\n+\n struct list_options {\n \tint columns;\n \tconst char *header;\n@@ -95,18 +221,21 @@ struct list_and_choose_options {\n  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,\n  * `LIST_AND_CHOOSE_QUIT` is returned.\n  */\n-static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,\n+static ssize_t list_and_choose(struct add_i_state *s,\n+\t\t\t       struct prefix_item_list *items,\n \t\t\t       struct list_and_choose_options *opts)\n {\n \tstruct strbuf input = STRBUF_INIT;\n \tssize_t res = LIST_AND_CHOOSE_ERROR;\n \n+\tfind_unique_prefixes(items);\n+\n \tfor (;;) {\n \t\tchar *p;\n \n \t\tstrbuf_reset(&input);\n \n-\t\tlist(s, items, &opts->list_opts);\n+\t\tlist(s, &items->items, &opts->list_opts);\n \n \t\tprintf(\"%s%s\", opts->prompt, \"> \");\n \t\tfflush(stdout);\n@@ -142,7 +271,10 @@ static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,\n \n \t\t\tif (p[sep])\n \t\t\t\tp[sep++] = '\\0';\n-\t\t\tif (index < 0 || index >= items->nr)\n+\t\t\tif (index < 0)\n+\t\t\t\tindex = find_unique(p, items);\n+\n+\t\t\tif (index < 0 || index >= items->items.nr)\n \t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n \t\t\telse {\n \t\t\t\tres = index;\n@@ -308,6 +440,23 @@ static void render_adddel(struct strbuf *buf,\n \t\tstrbuf_addstr(buf, no_changes);\n }\n \n+/* filters out prefixes which have special meaning to list_and_choose() */\n+static int is_valid_prefix(const char *prefix, size_t prefix_len)\n+{\n+\treturn prefix_len && prefix &&\n+\t\t/*\n+\t\t * We expect `prefix` to be NUL terminated, therefore this\n+\t\t * `strcspn()` call is okay, even if it might do much more\n+\t\t * work than strictly necessary.\n+\t\t */\n+\t\tstrcspn(prefix, \" \\t\\r\\n,\") >= prefix_len &&\t/* separators */\n+\t\t*prefix != '-' &&\t\t\t\t/* deselection */\n+\t\t!isdigit(*prefix) &&\t\t\t\t/* selection */\n+\t\t(prefix_len != 1 ||\n+\t\t (*prefix != '*' &&\t\t\t\t/* \"all\" wildcard */\n+\t\t  *prefix != '?'));\t\t\t\t/* prompt help */\n+}\n+\n struct print_file_item_data {\n \tconst char *modified_fmt;\n \tstruct strbuf buf, index, worktree;\n@@ -347,10 +496,23 @@ typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,\n \t\t\t struct string_list *files,\n \t\t\t struct list_options *opts);\n \n+struct command_item {\n+\tsize_t prefix_length;\n+\tcommand_t command;\n+};\n+\n static void print_command_item(int i, struct string_list_item *item,\n \t\t\t       void *print_command_item_data)\n {\n-\tprintf(\" %2d: %s\", i + 1, item->string);\n+\tstruct command_item *util = item->util;\n+\n+\tif (!util->prefix_length ||\n+\t    !is_valid_prefix(item->string, util->prefix_length))\n+\t\tprintf(\" %2d: %s\", i + 1, item->string);\n+\telse\n+\t\tprintf(\" %2d: [%.*s]%s\", i + 1,\n+\t\t       (int)util->prefix_length, item->string,\n+\t\t       item->string + util->prefix_length);\n }\n \n int run_add_i(struct repository *r, const struct pathspec *ps)\n@@ -366,7 +528,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t} command_list[] = {\n \t\t{ \"status\", run_status },\n \t};\n-\tstruct string_list commands = STRING_LIST_INIT_NODUP;\n+\tstruct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;\n \n \tstruct print_file_item_data print_file_item_data = {\n \t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n@@ -379,9 +541,12 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tssize_t i;\n \tint res = 0;\n \n-\tfor (i = 0; i < ARRAY_SIZE(command_list); i++)\n-\t\tstring_list_append(&commands, command_list[i].string)\n-\t\t\t->util = command_list[i].command;\n+\tfor (i = 0; i < ARRAY_SIZE(command_list); i++) {\n+\t\tstruct command_item *util = xcalloc(sizeof(*util), 1);\n+\t\tutil->command = command_list[i].command;\n+\t\tstring_list_append(&commands.items, command_list[i].string)\n+\t\t\t->util = util;\n+\t}\n \n \tinit_add_i_state(&s, r);\n \n@@ -406,8 +571,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\t\tbreak;\n \t\t}\n \t\tif (i != LIST_AND_CHOOSE_ERROR) {\n-\t\t\tcommand_t command = commands.items[i].util;\n-\t\t\tres = command(&s, ps, &files, &opts);\n+\t\t\tstruct command_item *util =\n+\t\t\t\tcommands.items.items[i].util;\n+\t\t\tres = util->command(&s, ps, &files, &opts);\n \t\t}\n \t}\n \n@@ -416,7 +582,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \tstrbuf_release(&print_file_item_data.index);\n \tstrbuf_release(&print_file_item_data.worktree);\n \tstrbuf_release(&header);\n-\tstring_list_clear(&commands, 0);\n+\tprefix_item_list_clear(&commands);\n \n \treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"386298","messageId":"4836191271fb260df837d431c55a1d8316edbdb4.1573816280.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v7.git.1573816280.gitgitgadget@gmail.com","subject":"[PATCH v7 3/9] built-in add -i: implement the `status` command","fromName":"Daniel Ferreira via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-15T11:11:14Z","receivedAt":"2019-11-15T11:11:35Z","isPatch":true,"sender":{"key":"bnmvco@gmail.com","avatar":"https://avatars.githubusercontent.com/u/735397?v=4"},"body":"From: Daniel Ferreira <bnmvco@gmail.com>\n\nThis implements the `status` command of `git add -i`. The data\nstructures introduced in this commit will be extended later, as needed.\n\nAt this point, we re-implement only part of the `list_and_choose()`\nfunction of the Perl script `git-add--interactive.perl` and call it\n`list()`. It does not yet color anything, or do columns, or allow user\ninput.\n\nOver the course of the next commits, we will introduce a\n`list_and_choose()` function that uses `list()` to display the list of\noptions and let the user choose one or more of the displayed items. This\nwill be used to implement the main loop of the built-in `git add -i`, at\nwhich point the new `status` command can actually be used.\n\nSigned-off-by: Daniel Ferreira <bnmvco@gmail.com>\nSigned-off-by: Slavica Đukić <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 251 +++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 250 insertions(+), 1 deletion(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 482e458dc6..aa35184d87 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -1,7 +1,256 @@\n #include \"cache.h\"\n #include \"add-interactive.h\"\n+#include \"diffcore.h\"\n+#include \"revision.h\"\n+#include \"refs.h\"\n+#include \"string-list.h\"\n+\n+struct add_i_state {\n+\tstruct repository *r;\n+};\n+\n+static void init_add_i_state(struct add_i_state *s, struct repository *r)\n+{\n+       s->r = r;\n+}\n+\n+struct list_options {\n+\tconst char *header;\n+\tvoid (*print_item)(int i, struct string_list_item *item, void *print_item_data);\n+\tvoid *print_item_data;\n+};\n+\n+static void list(struct string_list *list, struct list_options *opts)\n+{\n+\tint i;\n+\n+\tif (!list->nr)\n+\t\treturn;\n+\n+\tif (opts->header)\n+\t\tprintf(\"%s\\n\", opts->header);\n+\n+\tfor (i = 0; i < list->nr; i++) {\n+\t\topts->print_item(i, list->items + i, opts->print_item_data);\n+\t\tputchar('\\n');\n+\t}\n+}\n+\n+struct adddel {\n+\tuintmax_t add, del;\n+\tunsigned seen:1, binary:1;\n+};\n+\n+struct file_item {\n+\tstruct adddel index, worktree;\n+};\n+\n+static void add_file_item(struct string_list *files, const char *name)\n+{\n+\tstruct file_item *item = xcalloc(sizeof(*item), 1);\n+\n+\tstring_list_append(files, name)->util = item;\n+}\n+\n+struct pathname_entry {\n+\tstruct hashmap_entry ent;\n+\tconst char *name;\n+\tstruct file_item *item;\n+};\n+\n+static int pathname_entry_cmp(const void *unused_cmp_data,\n+\t\t\t      const struct hashmap_entry *he1,\n+\t\t\t      const struct hashmap_entry *he2,\n+\t\t\t      const void *name)\n+{\n+\tconst struct pathname_entry *e1 =\n+\t\tcontainer_of(he1, const struct pathname_entry, ent);\n+\tconst struct pathname_entry *e2 =\n+\t\tcontainer_of(he2, const struct pathname_entry, ent);\n+\n+\treturn strcmp(e1->name, name ? (const char *)name : e2->name);\n+}\n+\n+struct collection_status {\n+\tenum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase;\n+\n+\tconst char *reference;\n+\n+\tstruct string_list *files;\n+\tstruct hashmap file_map;\n+};\n+\n+static void collect_changes_cb(struct diff_queue_struct *q,\n+\t\t\t       struct diff_options *options,\n+\t\t\t       void *data)\n+{\n+\tstruct collection_status *s = data;\n+\tstruct diffstat_t stat = { 0 };\n+\tint i;\n+\n+\tif (!q->nr)\n+\t\treturn;\n+\n+\tcompute_diffstat(options, &stat, q);\n+\n+\tfor (i = 0; i < stat.nr; i++) {\n+\t\tconst char *name = stat.files[i]->name;\n+\t\tint hash = strhash(name);\n+\t\tstruct pathname_entry *entry;\n+\t\tstruct file_item *file_item;\n+\t\tstruct adddel *adddel;\n+\n+\t\tentry = hashmap_get_entry_from_hash(&s->file_map, hash, name,\n+\t\t\t\t\t\t    struct pathname_entry, ent);\n+\t\tif (!entry) {\n+\t\t\tadd_file_item(s->files, name);\n+\n+\t\t\tentry = xcalloc(sizeof(*entry), 1);\n+\t\t\thashmap_entry_init(&entry->ent, hash);\n+\t\t\tentry->name = s->files->items[s->files->nr - 1].string;\n+\t\t\tentry->item = s->files->items[s->files->nr - 1].util;\n+\t\t\thashmap_add(&s->file_map, &entry->ent);\n+\t\t}\n+\n+\t\tfile_item = entry->item;\n+\t\tadddel = s->phase == FROM_INDEX ?\n+\t\t\t&file_item->index : &file_item->worktree;\n+\t\tadddel->seen = 1;\n+\t\tadddel->add = stat.files[i]->added;\n+\t\tadddel->del = stat.files[i]->deleted;\n+\t\tif (stat.files[i]->is_binary)\n+\t\t\tadddel->binary = 1;\n+\t}\n+\tfree_diffstat_info(&stat);\n+}\n+\n+static int get_modified_files(struct repository *r, struct string_list *files,\n+\t\t\t      const struct pathspec *ps)\n+{\n+\tstruct object_id head_oid;\n+\tint is_initial = !resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING,\n+\t\t\t\t\t     &head_oid, NULL);\n+\tstruct collection_status s = { FROM_WORKTREE };\n+\n+\tif (discard_index(r->index) < 0 ||\n+\t    repo_read_index_preload(r, ps, 0) < 0)\n+\t\treturn error(_(\"could not read index\"));\n+\n+\tstring_list_clear(files, 1);\n+\ts.files = files;\n+\thashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);\n+\n+\tfor (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {\n+\t\tstruct rev_info rev;\n+\t\tstruct setup_revision_opt opt = { 0 };\n+\n+\t\topt.def = is_initial ?\n+\t\t\tempty_tree_oid_hex() : oid_to_hex(&head_oid);\n+\n+\t\tinit_revisions(&rev, NULL);\n+\t\tsetup_revisions(0, NULL, &rev, &opt);\n+\n+\t\trev.diffopt.output_format = DIFF_FORMAT_CALLBACK;\n+\t\trev.diffopt.format_callback = collect_changes_cb;\n+\t\trev.diffopt.format_callback_data = &s;\n+\n+\t\tif (ps)\n+\t\t\tcopy_pathspec(&rev.prune_data, ps);\n+\n+\t\tif (s.phase == FROM_INDEX)\n+\t\t\trun_diff_index(&rev, 1);\n+\t\telse {\n+\t\t\trev.diffopt.flags.ignore_dirty_submodules = 1;\n+\t\t\trun_diff_files(&rev, 0);\n+\t\t}\n+\t}\n+\thashmap_free_entries(&s.file_map, struct pathname_entry, ent);\n+\n+\t/* While the diffs are ordered already, we ran *two* diffs... */\n+\tstring_list_sort(files);\n+\n+\treturn 0;\n+}\n+\n+static void render_adddel(struct strbuf *buf,\n+\t\t\t\tstruct adddel *ad, const char *no_changes)\n+{\n+\tif (ad->binary)\n+\t\tstrbuf_addstr(buf, _(\"binary\"));\n+\telse if (ad->seen)\n+\t\tstrbuf_addf(buf, \"+%\"PRIuMAX\"/-%\"PRIuMAX,\n+\t\t\t    (uintmax_t)ad->add, (uintmax_t)ad->del);\n+\telse\n+\t\tstrbuf_addstr(buf, no_changes);\n+}\n+\n+struct print_file_item_data {\n+\tconst char *modified_fmt;\n+\tstruct strbuf buf, index, worktree;\n+};\n+\n+static void print_file_item(int i, struct string_list_item *item,\n+\t\t\t    void *print_file_item_data)\n+{\n+\tstruct file_item *c = item->util;\n+\tstruct print_file_item_data *d = print_file_item_data;\n+\n+\tstrbuf_reset(&d->index);\n+\tstrbuf_reset(&d->worktree);\n+\tstrbuf_reset(&d->buf);\n+\n+\trender_adddel(&d->worktree, &c->worktree, _(\"nothing\"));\n+\trender_adddel(&d->index, &c->index, _(\"unchanged\"));\n+\tstrbuf_addf(&d->buf, d->modified_fmt,\n+\t\t    d->index.buf, d->worktree.buf, item->string);\n+\n+\tprintf(\" %2d: %s\", i + 1, d->buf.buf);\n+}\n+\n+static int run_status(struct add_i_state *s, const struct pathspec *ps,\n+\t\t      struct string_list *files, struct list_options *opts)\n+{\n+\tif (get_modified_files(s->r, files, ps) < 0)\n+\t\treturn -1;\n+\n+\tlist(files, opts);\n+\tputchar('\\n');\n+\n+\treturn 0;\n+}\n \n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n-\tdie(_(\"No commands are available in the built-in `git add -i` yet!\"));\n+\tstruct add_i_state s = { NULL };\n+\tstruct print_file_item_data print_file_item_data = {\n+\t\t\"%12s %12s %s\", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n+\t};\n+\tstruct list_options opts = {\n+\t\tNULL, print_file_item, &print_file_item_data\n+\t};\n+\tstruct strbuf header = STRBUF_INIT;\n+\tstruct string_list files = STRING_LIST_INIT_DUP;\n+\tint res = 0;\n+\n+\tinit_add_i_state(&s, r);\n+\tstrbuf_addstr(&header, \"      \");\n+\tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n+\t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n+\topts.header = header.buf;\n+\n+\tif (discard_index(r->index) < 0 ||\n+\t    repo_read_index(r) < 0 ||\n+\t    repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,\n+\t\t\t\t\t NULL, NULL, NULL) < 0)\n+\t\twarning(_(\"could not refresh index\"));\n+\n+\tres = run_status(&s, ps, &files, &opts);\n+\n+\tstring_list_clear(&files, 1);\n+\tstrbuf_release(&print_file_item_data.buf);\n+\tstrbuf_release(&print_file_item_data.index);\n+\tstrbuf_release(&print_file_item_data.worktree);\n+\tstrbuf_release(&header);\n+\n+\treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"386301","messageId":"bcf4aa7c5e2e64f1c6a57d4a5860474818de7b79.1573816280.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v7.git.1573816280.gitgitgadget@gmail.com","subject":"[PATCH v7 8/9] built-in add -i: use color in the main loop","fromName":"Slavica Đukić via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-15T11:11:19Z","receivedAt":"2019-11-15T11:11:38Z","isPatch":true,"sender":{"key":"name:Slavica Đukić","avatar":null},"body":"From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>\n\nThe error messages as well as the unique prefixes are colored in `git\nadd -i` by default; We need to do the same in the built-in version.\n\nSigned-off-by: Slavica Đukić <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c | 35 +++++++++++++++++++++++++++++------\n 1 file changed, 29 insertions(+), 6 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 0a03b9017d..170a5800e3 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -12,6 +12,9 @@ struct add_i_state {\n \tint use_color;\n \tchar header_color[COLOR_MAXLEN];\n \tchar help_color[COLOR_MAXLEN];\n+\tchar prompt_color[COLOR_MAXLEN];\n+\tchar error_color[COLOR_MAXLEN];\n+\tchar reset_color[COLOR_MAXLEN];\n };\n \n static void init_color(struct repository *r, struct add_i_state *s,\n@@ -45,6 +48,9 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)\n \n \tinit_color(r, s, \"header\", s->header_color, GIT_COLOR_BOLD);\n \tinit_color(r, s, \"help\", s->help_color, GIT_COLOR_BOLD_RED);\n+\tinit_color(r, s, \"prompt\", s->prompt_color, GIT_COLOR_BOLD_BLUE);\n+\tinit_color(r, s, \"error\", s->error_color, GIT_COLOR_BOLD_RED);\n+\tinit_color(r, s, \"reset\", s->reset_color, GIT_COLOR_RESET);\n }\n \n /*\n@@ -240,7 +246,8 @@ static ssize_t list_and_choose(struct add_i_state *s,\n \n \t\tlist(s, &items->items, &opts->list_opts);\n \n-\t\tprintf(\"%s%s\", opts->prompt, \"> \");\n+\t\tcolor_fprintf(stdout, s->prompt_color, \"%s\", opts->prompt);\n+\t\tfputs(\"> \", stdout);\n \t\tfflush(stdout);\n \n \t\tif (strbuf_getline(&input, stdin) == EOF) {\n@@ -283,7 +290,8 @@ static ssize_t list_and_choose(struct add_i_state *s,\n \t\t\t\tindex = find_unique(p, items);\n \n \t\t\tif (index < 0 || index >= items->items.nr)\n-\t\t\t\tprintf(_(\"Huh (%s)?\\n\"), p);\n+\t\t\t\tcolor_fprintf_ln(stdout, s->error_color,\n+\t\t\t\t\t\t _(\"Huh (%s)?\"), p);\n \t\t\telse {\n \t\t\t\tres = index;\n \t\t\t\tbreak;\n@@ -509,18 +517,23 @@ struct command_item {\n \tcommand_t command;\n };\n \n+struct print_command_item_data {\n+\tconst char *color, *reset;\n+};\n+\n static void print_command_item(int i, struct string_list_item *item,\n \t\t\t       void *print_command_item_data)\n {\n+\tstruct print_command_item_data *d = print_command_item_data;\n \tstruct command_item *util = item->util;\n \n \tif (!util->prefix_length ||\n \t    !is_valid_prefix(item->string, util->prefix_length))\n \t\tprintf(\" %2d: %s\", i + 1, item->string);\n \telse\n-\t\tprintf(\" %2d: [%.*s]%s\", i + 1,\n-\t\t       (int)util->prefix_length, item->string,\n-\t\t       item->string + util->prefix_length);\n+\t\tprintf(\" %2d: %s%.*s%s%s\", i + 1,\n+\t\t       d->color, (int)util->prefix_length, item->string,\n+\t\t       d->reset, item->string + util->prefix_length);\n }\n \n static void command_prompt_help(struct add_i_state *s)\n@@ -538,8 +551,9 @@ static void command_prompt_help(struct add_i_state *s)\n int run_add_i(struct repository *r, const struct pathspec *ps)\n {\n \tstruct add_i_state s = { NULL };\n+\tstruct print_command_item_data data = { \"[\", \"]\" };\n \tstruct list_and_choose_options main_loop_opts = {\n-\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, NULL },\n+\t\t{ 4, N_(\"*** Commands ***\"), print_command_item, &data },\n \t\tN_(\"What now\"), command_prompt_help\n \t};\n \tstruct {\n@@ -570,6 +584,15 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \n \tinit_add_i_state(&s, r);\n \n+\t/*\n+\t * When color was asked for, use the prompt color for\n+\t * highlighting, otherwise use square brackets.\n+\t */\n+\tif (s.use_color) {\n+\t\tdata.color = s.prompt_color;\n+\t\tdata.reset = s.reset_color;\n+\t}\n+\n \tstrbuf_addstr(&header, \"      \");\n \tstrbuf_addf(&header, print_file_item_data.modified_fmt,\n \t\t    _(\"staged\"), _(\"unstaged\"), _(\"path\"));\n-- \ngitgitgadget\n\n"},{"id":"386302","messageId":"ccc6fb45580e1c07b143d1a55d9518c13ac3c032.1573816280.git.gitgitgadget@gmail.com","threadId":"50913","inReplyTo":"pull.170.v7.git.1573816280.gitgitgadget@gmail.com","subject":"[PATCH v7 9/9] built-in add -i: implement the `help` command","fromName":"Slavica Đukić via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-11-15T11:11:20Z","receivedAt":"2019-11-15T11:11:38Z","isPatch":true,"sender":{"key":"name:Slavica Đukić","avatar":null},"body":"From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <slawica92@hotmail.com>\n\nThis imitates the code to show the help text from the Perl script\n`git-add--interactive.perl` in the built-in version.\n\nTo make sure that it renders exactly like the Perl version of `git add\n-i`, we also add a test case for that to `t3701-add-interactive.sh`.\n\nSigned-off-by: Slavica Đukić <slawica92@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n add-interactive.c          | 21 +++++++++++++++++++++\n t/t3701-add-interactive.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 46 insertions(+)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 170a5800e3..d6cb98cd40 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -508,6 +508,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,\n \treturn 0;\n }\n \n+static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,\n+\t\t    struct string_list *unused_files,\n+\t\t    struct list_options *unused_opts)\n+{\n+\tcolor_fprintf_ln(stdout, s->help_color, \"status        - %s\",\n+\t\t\t _(\"show paths with changes\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"update        - %s\",\n+\t\t\t _(\"add working tree state to the staged set of changes\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"revert        - %s\",\n+\t\t\t _(\"revert staged set of changes back to the HEAD version\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"patch         - %s\",\n+\t\t\t _(\"pick hunks and update selectively\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"diff          - %s\",\n+\t\t\t _(\"view diff between HEAD and index\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"add untracked - %s\",\n+\t\t\t _(\"add contents of untracked files to the staged set of changes\"));\n+\n+\treturn 0;\n+}\n+\n typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,\n \t\t\t struct string_list *files,\n \t\t\t struct list_options *opts);\n@@ -561,6 +581,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\tcommand_t command;\n \t} command_list[] = {\n \t\t{ \"status\", run_status },\n+\t\t{ \"help\", run_help },\n \t};\n \tstruct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;\n \ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex d50e165ca8..d4f9386621 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' '\n \ttest_write_lines a b a b a a b a b a >expect &&\n \ttest_cmp expect a\n '\n+\n+test_expect_success 'show help from add--helper' '\n+\tgit reset --hard &&\n+\tcat >expect <<-EOF &&\n+\n+\t<BOLD>*** Commands ***<RESET>\n+\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>\n+\t<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>\n+\t<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>\n+\t<BOLD;RED>patch         - pick hunks and update selectively<RESET>\n+\t<BOLD;RED>diff          - view diff between HEAD and index<RESET>\n+\t<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>\n+\t<BOLD>*** Commands ***<RESET>\n+\t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t<BOLD;BLUE>What now<RESET>>$SP\n+\tBye.\n+\tEOF\n+\ttest_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&\n+\ttest_decode_color <actual.colored >actual &&\n+\ttest_i18ncmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n"}]}