git add -i: add a rudimentary version in C (supporting only status and help so far)
Subsystems (2)
PATCH v6 2/9 diff: export diffstat interface
In reply to Johannes Schindelin via GitGitGadgetFrom: Daniel Ferreira <[email protected]>
Make the diffstat interface (namely, the diffstat_t struct and compute_diffstat) no longer be internal to diff.c and allow it to be used by other parts of git.
This is helpful for code that may want to easily extract information from files using the diff machinery, while flushing it differently from how the show_* functions used by diff_flush() do it. One example is the builtin implementation of git-add--interactive's status.
--- diff.c | 39 ++++++++++++++++----------------------- diff.h | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 23 deletions(-)
diff.c +16 -23
@@ -2495,22 +2495,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b) } } -struct diffstat_t { -int nr; -int alloc; -struct diffstat_file { -char *from_name; -char *name; -char *print_name; -const char *comments; -unsigned is_unmerged:1 ; -unsigned is_binary:1 ; -unsigned is_renamed:1 ; -unsigned is_interesting:1 ; -uintmax_t added, deleted; - } **files; -}; -static struct diffstat_file *diffstat_add (struct diffstat_t *diffstat,const char *name_a,const char *name_b)
@@ -3157,7 +3141,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *ogather_dirstat (options, &dir, changed,"" ,0 ); } -static void free_diffstat_info (struct diffstat_t *diffstat) +void free_diffstat_info (struct diffstat_t *diffstat) {int i;for (i =0 ; i < diffstat->nr; i++) {
@@ -6283,12 +6267,7 @@ void diff_flush(struct diff_options *options) dirstat_by_line) {struct diffstat_t diffstat; -memset (&diffstat,0 ,sizeof (struct diffstat_t )); -for (i =0 ; i < q->nr; i++) { -struct diff_filepair *p = q->queue[i]; -if (check_pair_status (p)) -diff_flush_stat (p, options, &diffstat); - } +compute_diffstat (options, &diffstat, q);if (output_format &DIFF_FORMAT_NUMSTAT )show_numstat (&diffstat, options);if (output_format &DIFF_FORMAT_DIFFSTAT )
@@ -6621,6 +6600,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)return ignored; } +void compute_diffstat (struct diff_options *options, +struct diffstat_t *diffstat, +struct diff_queue_struct *q) +{ +int i; + +memset (diffstat,0 ,sizeof (struct diffstat_t )); +for (i =0 ; i < q->nr; i++) { +struct diff_filepair *p = q->queue[i]; +if (check_pair_status (p)) +diff_flush_stat (p, options, diffstat); + } +} + void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const struct object_id *oid,
diff.h +20 -0
@@ -245,6 +245,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);void diff_emit_submodule_pipethrough (struct diff_options *o,const char *line,int len); +struct diffstat_t { +int nr; +int alloc; +struct diffstat_file { +char *from_name; +char *name; +char *print_name; +const char *comments; +unsigned is_unmerged:1 ; +unsigned is_binary:1 ; +unsigned is_renamed:1 ; +unsigned is_interesting:1 ; +uintmax_t added, deleted; + } **files; +}; +enum color_diff {DIFF_RESET =0 ,DIFF_CONTEXT =1 ,
@@ -334,6 +350,10 @@ void diff_change(struct diff_options *,struct diff_filepair *diff_unmerge (struct diff_options *,const char *path); +void compute_diffstat (struct diff_options *options,struct diffstat_t *diffstat, +struct diff_queue_struct *q); +void free_diffstat_info (struct diffstat_t *diffstat); +#define DIFF_SETUP_REVERSE 1#define DIFF_SETUP_USE_SIZE_CACHE 4
Signature · 1 line
gitgitgadget
v6 cover git add -i: add a rudimentary version in C (supporting only status and help so far)
In reply to Johannes Schindelin via GitGitGadgetThis is the first leg on the long journey to a fully built-in git add -i (next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3
[https://github.com/gitgitgadget/git/pull/172], 4
[https://github.com/gitgitgadget/git/pull/173], 5
[https://github.com/gitgitgadget/git/pull/174], and 6
[https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are not necessarily up to date, and will be re-targeted to the appropriate branches in https://github.com/gitster/git as soon as Junio picks them up.
This here patch series reflects the part that was submitted a couple of times (see https://github.com/gitgitgadget/git/pull/103) during the Outreachy project by Slavica Ðukic that continued the journey based on an initial patch series by Daniel Ferreira.
It only implements the status and the help part, in the interest of making the review remotely more reviewable.
As I am a heavy user of git add -p myself and use a patched version for several months already (it is so nice to not suffer over one second startup until the MSYS2 Perl finally shows me anything, instead it feels instantaneous), I integrated these patch series into Git for Windows already, as an opt-in feature guarded by the config variable add.interactive.useBuiltin (and Git for Windows' installer knows to detect this version and offer the option in the graphical user interface).
Changes since v4:
* Rebased onto current master to make use of Thomas Gummerer's repo_refresh_and_write_index() as well as to avoid merge conflicts with Eric Wong's work on struct hashmap.
* Instead of rolling a dedicated data struct to simulate a Trie, we now use string-list extensively (an unsorted copy and a sorted one, the latter to determine unique prefixes). This had massive ramifications on the rest of the patches... For example, the struct command_item structure no longer contains the name field, but is intended to be a util in a string_list.
* Changed the commit messages and author lines to reflect Slavica's name correctly.
* Touched up a couple commit messages.
Changes since v3:
* Rebased to v2.23.0 to reduce friction.
* free_diffstat_info() is now made public as well, and used, to avoid a memory leak.
* Prepared the patches for ew/hashmap (which is strict about the hashmap entries' type in hashmap_entry_init() and friends).
* The private data types have been moved from prefix-map.h to prefix-map.c.
* A lot of int types were converted to more appropriate size_t in prefix-map.c.
* A misleading parameter name list was renamed to the correct array.
* The code comment above find_unique_prefixes() was (hopefully) improved.
* The run_help() function's signature now reflects that most of the parameters are actually unused.
Changes since v2:
* Rebased to master to avoid merge conflicts.
* Renumbered the prefix-map test to avoid conflicts with two patch series that are currently in-flight in pu.
Changes since v1:
* The config machinery was reworked completely, to not use a callback to git_config(), but instead to query the config via the repo_config_get_*() functions. This also prevents a future "Huh???" moment: the internal add --interactive API accepts a parameter of type struct repository *r, but the previous configuration did not use that to query the config (and could in the future be a repository other than the_repository).
* As a consequence, the color sequences are no longer stored in file-local variables, but passed around via a struct.
* Instead of using the magical constant -2 to quit the main loop, it is now defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is defined as -1 and used where appropriate).
* Improved the add_prefix_item() function by avoiding buffer overruns, not reusing the struct that is used for lookup also for adding the new item, and by strengthening the bug check.
Daniel Ferreira (2):
diff: export diffstat interface
built-in add -i: implement the status command
Johannes Schindelin (4):
Start to implement a built-in version of git add --interactive built-in add -i: implement the main loop
built-in add -i: show unique prefixes of the commands built-in add -i: support ? (prompt help)
Slavica Đukić (3):
built-in add -i: color the header in the status command built-in add -i: use color in the main loop built-in add -i: implement the help command
Documentation/config/add.txt | 5 + Makefile | 1 + add-interactive.c | 651 +++++++++++++++++++++++++++++++++++ add-interactive.h | 8 + builtin/add.c | 12 + diff.c | 39 +-- diff.h | 20 ++ t/README | 4 + t/t3701-add-interactive.sh | 25 ++ 9 files changed, 742 insertions(+), 23 deletions(-) create mode 100644 add-interactive.c create mode 100644 add-interactive.h
base-commit: 566a1439f6f56c2171b8853ddbca0ad3f5098770
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v6
Pull-Request: https://github.com/gitgitgadget/git/pull/170
Range-diff vs v5:
1: ff59d2d0b3 ! 1: 5d9962d434 Start to implement a built-in version of `git add --interactive`
@@ -2,31 +2,31 @@ Start to implement a built-in version of git add --interactive
- This is hardly the first conversion of a Git command that is implemented
- as a script to a built-in. So far, the most successful strategy for such
- conversions has been to add a built-in helper and call that for more and
- more functionality from the script, as more and more parts are
- converted.
+ Unlike previous conversions to C, where we started with a built-in
+ helper, we start this conversion by adding an interception in the
+ run_add_interactive() function when the new opt-in
+ add.interactive.useBuiltin config knob is turned on (or the
+ corresponding environment variable GIT_TEST_ADD_I_USE_BUILTIN), and
+ calling the new internal API function run_add_i() that is implemented
+ directly in libgit.a.
- With the interactive add, we choose a different strategy. The sole
- reason for this is that on Windows (where such a conversion has the most
- benefits in terms of speed and robustness) we face the very specific
- problem that a system() call in Perl seems to close stdin in the
- parent process when the spawned process consumes even one character from
- stdin. And that just does not work for us here, as it would stop the
- main loop as soon as any interactive command was performed by the
- helper. Which is almost all of the commands in git add -i.
-
- It is almost as if Perl told us once again that it does not want us to
- use it on Windows.
+ At this point, the built-in version of git add -i only states that it
+ cannot do anything yet. In subsequent patches/patch series, the
+ run_add_i() function will gain more and more functionality, until it
+ is feature complete. The whole arc of the conversion can be found in the
+ PRs #170-175 at https://github.com/gitgitgadget/git.
- Instead, we follow the opposite route where we start with a bare-bones
- version of the built-in interactive add, guarded by the new
- add.interactive.useBuiltin config variable, and then add more and more
- functionality to it, until it is feature complete.
+ The "--helper approach" can unfortunately not be used here: on Windows
+ we face the very specific problem that a system() call in
+ Perl seems to close stdin in the parent process when the spawned
+ process consumes even one character from stdin. Which prevents us from
+ implementing the main loop in C and still trying to hand off to the Perl
+ script.
- At this point, the built-in version of git add -i only states that it
- cannot do anything yet ;-)
+ The very real downside of the approach we have to take here is that the
+ test suite won't pass with GIT_TEST_ADD_I_USE_BUILTIN=true until the
+ conversion is complete (the --helper approach would have let it pass,
+ even at each of the incremental conversion steps).
@@ -99,12 +99,14 @@
struct argv_array argv = ARGV_ARRAY_INIT;
+ int use_builtin_add_i =
+ git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
-+ if (use_builtin_add_i < 0)
-+ git_config_get_bool("add.interactive.usebuiltin",
-+ &use_builtin_add_i);
+
-+ if (use_builtin_add_i == 1 && !patch_mode)
-+ return !!run_add_i(the_repository, pathspec);
++ if (!patch_mode) {
++ if (use_builtin_add_i < 0)
++ git_config_get_bool("add.interactive.usebuiltin",
++ &use_builtin_add_i);
++ if (use_builtin_add_i == 1)
++ return !!run_add_i(the_repository, pathspec);
++ }
argv_array_push(&argv, "add--interactive"); if (patch_mode)
2: 2fc8cc3546 = 2: f42d7b1310 diff: export diffstat interface 3: 6aaa0de4f4 = 3: 4836191271 built-in add -i: implement the `status` command 4: e405f07110 = 4: d61cf9daeb built-in add -i: color the header in the `status` command 5: 25590fbbbe ! 5: b0c04e6ec6 built-in add -i: implement the main loop
@@ -7,33 +7,34 @@
function that we conveniently introduced for use by the status command.
- Apart from the "and choose" part, there are more differences between the
- way the status command calls the list_and_choose() function in the
- Perl version of git add -i compared to the other callers of said
- function. The most important ones:
+ In contrast to the Perl version, in the built-in interactive add, we
+ will keep the list() function (which only displays items) and the
+ list_and_choose() function (which uses list() to display the items,
+ and only takes care of the "and choose" part) separate.
- - The list is not only shown, but the user is also asked to make a
- choice, possibly selecting multiple entries.
+ The list_and_choose() function, as implemented in
+ git-add--interactive.perl knows a few more tricks than the function we
+ introduce in this patch:
- - The list of items is prefixed with a marker indicating what items have
- been selected, if multi-selection is allowed.
+ - There is a flag to let the user select multiple items.
- - Initially, for each item a unique prefix (if there exists any within
- the given parameters) is determined, and shown in the list, and
+ - In multi-select mode, the list of items is prefixed with a marker
+ indicating what items have been selected.
+
+ - Initially, for each item a unique prefix is determined (if there
+ exists any within the given parameters), and shown in the list, and
accepted as a shortcut for the selection.
- These features will be implemented later, except the part where the user
- can choose a command. At this stage, though, the built-in git add -i
- still only supports the status command, with the remaining commands to
- follow over the course of the next commits.
+ These features will be implemented in the C version later.
- In addition, we also modify list() to support displaying the commands
- in columns, even if there is currently only one.
+ This patch does not add any new main loop command, of course, the
+ built-in git add -i still only supports the status command. The
+ remaining commands to follow over the course of the next commits.
- The Perl script git-add--interactive.perl mixed the purposes of the
- "list" and the "and choose" part into the same function. In the C
- version, we will keep them separate instead, calling the list()
- function from the list_and_choose() function.
+ To accommodate for listing the commands in columns, preparing for the
+ commands that will be implemented over the course of the next
+ patches/patch series, we teach the list() function to do precisely
+ that.
Note that we only have a prompt ending in a single ">" at this stage; later commits will add commands that display a double ">>" to indicate
@@ -101,7 +102,7 @@
+ ssize_t res = LIST_AND_CHOOSE_ERROR;
+
+ for (;;) {
-+ char *p, *endp;
++ char *p;
+
+ strbuf_reset(&input);
+
@@ -133,6 +134,7 @@
+ }
+
+ if (isdigit(*p)) {
++ char *endp;
+ index = strtoul(p, &endp, 10) - 1;
+ if (endp != p + sep)
+ index = -1;
6: 57fdc01463 ! 6: b6459be5eb built-in add -i: show unique prefixes of the commands
@@ -177,7 +177,7 @@
+ find_unique_prefixes(items);
+
for (;;) {
- char *p, *endp;
+ char *p;
strbuf_reset(&input);7: 77ad5f333a = 7: bdf9058d9e built-in add -i: support `?` (prompt help) 8: 3d0b172a7f = 8: eafeedc49b built-in add -i: use color in the main loop 9: 85e508ef11 = 9: 7fda76255a built-in add -i: implement the `help` command
Signature · 1 line
gitgitgadget
PATCH v6 1/9 Start to implement a built-in version of `git add --interactive`
From: Johannes Schindelin <[email protected]>
Unlike previous conversions to C, where we started with a built-in helper, we start this conversion by adding an interception in the run_add_interactive() function when the new opt-in add.interactive.useBuiltin config knob is turned on (or the corresponding environment variable GIT_TEST_ADD_I_USE_BUILTIN), and calling the new internal API function run_add_i() that is implemented directly in libgit.a.
At this point, the built-in version of git add -i only states that it cannot do anything yet. In subsequent patches/patch series, the run_add_i() function will gain more and more functionality, until it is feature complete. The whole arc of the conversion can be found in the PRs #170-175 at https://github.com/gitgitgadget/git.
The "--helper approach" can unfortunately not be used here: on Windows we face the very specific problem that a system() call in Perl seems to close stdin in the parent process when the spawned process consumes even one character from stdin. Which prevents us from implementing the main loop in C and still trying to hand off to the Perl script.
The very real downside of the approach we have to take here is that the test suite won't pass with GIT_TEST_ADD_I_USE_BUILTIN=true until the conversion is complete (the --helper approach would have let it pass, even at each of the incremental conversion steps).
--- Documentation/config/add.txt | 5 +++++ Makefile | 1 + add-interactive.c | 7 +++++++ add-interactive.h | 8 ++++++++ builtin/add.c | 12 ++++++++++++ t/README | 4 ++++ 6 files changed, 37 insertions(+) create mode 100644 add-interactive.c create mode 100644 add-interactive.h
Documentation/config/add.txt +5 -0
@@ -5,3 +5,8 @@ add.ignore-errors (deprecated):: option of linkgit:git-add[1]. `add.ignore-errors` is deprecated, as it does not follow the usual naming convention for configuration variables. + +add.interactive.useBuiltin:: + [EXPERIMENTAL] Set to `true` to use the experimental built-in + implementation of the interactive version of linkgit:git-add[1] + instead of the Perl script version. Is `false` by default.
Makefile +1 -0
@@ -823,6 +823,7 @@ LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentat -name '*.h' -print))) LIB_OBJS += abspath.o +LIB_OBJS += add-interactive.o LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.o
add-interactive.c new file +7 -0
@@ -0,0 +1,7 @@ +#include "cache.h" +#include "add-interactive.h" + +int run_add_i (struct repository *r,const struct pathspec *ps) +{ +die (_ ("No commands are available in the built-in `git add -i` yet!" )); +}
add-interactive.h new file +8 -0
@@ -0,0 +1,8 @@ +#ifndef ADD_INTERACTIVE_H +#define ADD_INTERACTIVE_H + +struct repository ; +struct pathspec ; +int run_add_i (struct repository *r,const struct pathspec *ps); + +#endif
builtin/add.c +12 -0
@@ -20,6 +20,7 @@#include "bulk-checkin.h" #include "argv-array.h" #include "submodule.h" +#include "add-interactive.h" static const char *const builtin_add_usage[] = {N_ ("git add [<options>] [--] <pathspec>..." ),
@@ -185,6 +186,16 @@ int run_add_interactive(const char *revision, const char *patch_mode, {int status, i;struct argv_array argv =ARGV_ARRAY_INIT ; +int use_builtin_add_i = +git_env_bool ("GIT_TEST_ADD_I_USE_BUILTIN" ,-1 ); + +if (!patch_mode) { +if (use_builtin_add_i <0 ) +git_config_get_bool ("add.interactive.usebuiltin" , + &use_builtin_add_i); +if (use_builtin_add_i ==1 ) +return !!run_add_i (the_repository, pathspec); + }argv_array_push (&argv,"add--interactive" );if (patch_mode)
@@ -319,6 +330,7 @@ static int add_config(const char *var, const char *value, void *cb) ignore_add_errors =git_config_bool (var, value);return 0 ; } +return git_default_config (var, value, cb); }
t/README +4 -0
@@ -397,6 +397,10 @@ GIT_TEST_STASH_USE_BUILTIN=<boolean>, when false, disables the built-in version of git-stash. See 'stash.useBuiltin' in git-config(1). +GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the +built-in version of git add -i. See 'add.interactive.useBuiltin' in +git-config(1). + GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading of the index for the whole test suite by bypassing the default number of cache entries and thread minimums. Setting this to 1 will make the
Signature · 1 line
gitgitgadget
PATCH v6 3/9 built-in add -i: implement the `status` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Daniel Ferreira <[email protected]>
This implements the status command of git add -i. The data structures introduced in this commit will be extended later, as needed.
At this point, we re-implement only part of the list_and_choose() function of the Perl script git-add--interactive.perl and call it list(). It does not yet color anything, or do columns, or allow user input.
Over the course of the next commits, we will introduce a list_and_choose() function that uses list() to display the list of options and let the user choose one or more of the displayed items. This will be used to implement the main loop of the built-in git add -i, at which point the new status command can actually be used.
--- add-interactive.c | 251 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 250 insertions(+), 1 deletion(-)
add-interactive.c +250 -1
@@ -1,7 +1,256 @@#include "cache.h" #include "add-interactive.h" +#include "diffcore.h" +#include "revision.h" +#include "refs.h" +#include "string-list.h" + +struct add_i_state { +struct repository *r; +}; + +static void init_add_i_state (struct add_i_state *s,struct repository *r) +{ + s->r = r; +} + +struct list_options { +const char *header; +void (*print_item)(int i,struct string_list_item *item,void *print_item_data); +void *print_item_data; +}; + +static void list (struct string_list *list,struct list_options *opts) +{ +int i; + +if (!list->nr) +return ; + +if (opts->header) +printf ("%s\n" , opts->header); + +for (i =0 ; i < list->nr; i++) { + opts->print_item (i, list->items + i, opts->print_item_data); +putchar ('\n' ); + } +} + +struct adddel { +uintmax_t add, del; +unsigned seen:1 , binary:1 ; +}; + +struct file_item { +struct adddel index, worktree; +}; + +static void add_file_item (struct string_list *files,const char *name) +{ +struct file_item *item =xcalloc (sizeof (*item),1 ); + +string_list_append (files, name)->util = item; +} + +struct pathname_entry { +struct hashmap_entry ent; +const char *name; +struct file_item *item; +}; + +static int pathname_entry_cmp (const void *unused_cmp_data, +const struct hashmap_entry *he1, +const struct hashmap_entry *he2, +const void *name) +{ +const struct pathname_entry *e1 = + container_of(he1, const struct pathname_entry, ent); +const struct pathname_entry *e2 = + container_of(he2, const struct pathname_entry, ent); + +return strcmp (e1->name, name ? (const char *)name : e2->name); +} + +struct collection_status { +enum {FROM_WORKTREE =0 ,FROM_INDEX =1 } phase; + +const char *reference; + +struct string_list *files; +struct hashmap file_map; +}; + +static void collect_changes_cb (struct diff_queue_struct *q, +struct diff_options *options, +void *data) +{ +struct collection_status *s = data; +struct diffstat_t stat = {0 }; +int i; + +if (!q->nr) +return ; + +compute_diffstat (options, &stat, q); + +for (i =0 ; i < stat.nr; i++) { +const char *name = stat.files[i]->name; +int hash =strhash (name); +struct pathname_entry *entry; +struct file_item *file_item; +struct adddel *adddel; + + entry =hashmap_get_entry_from_hash (&s->file_map, hash, name, + struct pathname_entry, ent); +if (!entry) { +add_file_item (s->files, name); + + entry =xcalloc (sizeof (*entry),1 ); +hashmap_entry_init (&entry->ent, hash); + entry->name = s->files->items[s->files->nr -1 ].string; + entry->item = s->files->items[s->files->nr -1 ].util; +hashmap_add (&s->file_map, &entry->ent); + } + + file_item = entry->item; + adddel = s->phase ==FROM_INDEX ? + &file_item->index : &file_item->worktree; + adddel->seen =1 ; + adddel->add = stat.files[i]->added; + adddel->del = stat.files[i]->deleted; +if (stat.files[i]->is_binary) + adddel->binary =1 ; + } +free_diffstat_info (&stat); +} + +static int get_modified_files (struct repository *r,struct string_list *files, +const struct pathspec *ps) +{ +struct object_id head_oid; +int is_initial = !resolve_ref_unsafe ("HEAD" ,RESOLVE_REF_READING , + &head_oid,NULL ); +struct collection_status s = {FROM_WORKTREE }; + +if (discard_index (r->index) <0 || +repo_read_index_preload (r, ps,0 ) <0 ) +return error (_ ("could not read index" )); + +string_list_clear (files,1 ); + s.files = files; +hashmap_init (&s.file_map, pathname_entry_cmp,NULL ,0 ); + +for (s.phase =FROM_WORKTREE ; s.phase <=FROM_INDEX ; s.phase++) { +struct rev_info rev; +struct setup_revision_opt opt = {0 }; + + opt.def = is_initial ? +empty_tree_oid_hex () :oid_to_hex (&head_oid); + +init_revisions (&rev,NULL ); +setup_revisions (0 ,NULL , &rev, &opt); + + rev.diffopt.output_format =DIFF_FORMAT_CALLBACK ; + rev.diffopt.format_callback = collect_changes_cb; + rev.diffopt.format_callback_data = &s; + +if (ps) +copy_pathspec (&rev.prune_data, ps); + +if (s.phase ==FROM_INDEX ) +run_diff_index (&rev,1 ); +else { + rev.diffopt.flags.ignore_dirty_submodules =1 ; +run_diff_files (&rev,0 ); + } + } + hashmap_free_entries(&s.file_map, struct pathname_entry, ent); + +/* While the diffs are ordered already, we ran *two* diffs... */ +string_list_sort (files); + +return 0 ; +} + +static void render_adddel (struct strbuf *buf, +struct adddel *ad,const char *no_changes) +{ +if (ad->binary) +strbuf_addstr (buf,_ ("binary" )); +else if (ad->seen) +strbuf_addf (buf,"+%" PRIuMAX"/-%" PRIuMAX, + (uintmax_t )ad->add, (uintmax_t )ad->del); +else +strbuf_addstr (buf, no_changes); +} + +struct print_file_item_data { +const char *modified_fmt; +struct strbuf buf, index, worktree; +}; + +static void print_file_item (int i,struct string_list_item *item, +void *print_file_item_data) +{ +struct file_item *c = item->util; +struct print_file_item_data *d = print_file_item_data; + +strbuf_reset (&d->index); +strbuf_reset (&d->worktree); +strbuf_reset (&d->buf); + +render_adddel (&d->worktree, &c->worktree,_ ("nothing" )); +render_adddel (&d->index, &c->index,_ ("unchanged" )); +strbuf_addf (&d->buf, d->modified_fmt, + d->index.buf, d->worktree.buf, item->string); + +printf (" %2d: %s" , i +1 , d->buf.buf); +} + +static int run_status (struct add_i_state *s,const struct pathspec *ps, +struct string_list *files,struct list_options *opts) +{ +if (get_modified_files (s->r, files, ps) <0 ) +return -1 ; + +list (files, opts); +putchar ('\n' ); + +return 0 ; +}int run_add_i (struct repository *r,const struct pathspec *ps) { -die (_ ("No commands are available in the built-in `git add -i` yet!" )); +struct add_i_state s = {NULL }; +struct print_file_item_data print_file_item_data = { +"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT + }; +struct list_options opts = { +NULL , print_file_item, &print_file_item_data + }; +struct strbuf header =STRBUF_INIT ; +struct string_list files =STRING_LIST_INIT_DUP ; +int res =0 ; + +init_add_i_state (&s, r); +strbuf_addstr (&header," " ); +strbuf_addf (&header, print_file_item_data.modified_fmt, +_ ("staged" ),_ ("unstaged" ),_ ("path" )); + opts.header = header.buf; + +if (discard_index (r->index) <0 || +repo_read_index (r) <0 || +repo_refresh_and_write_index (r,REFRESH_QUIET ,0 ,1 , +NULL ,NULL ,NULL ) <0 ) +warning (_ ("could not refresh index" )); + + res =run_status (&s, ps, &files, &opts); + +string_list_clear (&files,1 ); +strbuf_release (&print_file_item_data.buf); +strbuf_release (&print_file_item_data.index); +strbuf_release (&print_file_item_data.worktree); +strbuf_release (&header); + +return res; }
Signature · 1 line
gitgitgadget
PATCH v6 6/9 built-in add -i: show unique prefixes of the commands
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Just like in the Perl script git-add--interactive.perl, for each command a unique prefix is determined (if there exists any within the given parameters), and shown in the list, and accepted as a shortcut for the command.
To determine the unique prefixes, as well as to look up the command in question, we use a copy of the list and sort it.
While this might seem like overkill for a single command, it will make much more sense when all the commands are implemented, and when we reuse the same logic to present a list of files to edit, with convenient unique prefixes.
At the start of the development of this patch series, a dedicated data structure was introduced that imitated the Trie that the Perl version implements. However, this was deemed overkill, and we now simply sort the list before determining the length of the unique prefixes by looking at each item's neighbor. As a bonus, we now use the same sorted list to perform a binary search using the user-provided prefix as search key.
--- add-interactive.c | 188 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 177 insertions(+), 11 deletions(-)
add-interactive.c +177 -11
@@ -45,6 +45,132 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD ); } +/* +* A "prefix item list" is a list of items that are identified by a string, and +* a unique prefix (if any) is determined for each item. +* +* It is implemented in the form of a pair of `string_list`s, the first one +* duplicating the strings, with the `util` field pointing at a structure whose +* first field must be `size_t prefix_length`. +* +* That `prefix_length` field will be computed by `find_unique_prefixes()`; It +* will be set to zero if no valid, unique prefix could be found. +* +* The second `string_list` is called `sorted` and does _not_ duplicate the +* strings but simply reuses the first one's, with the `util` field pointing at +* the `string_item_list` of the first `string_list`. It will be populated and +* sorted by `find_unique_prefixes()`. +*/ +struct prefix_item_list { +struct string_list items; +struct string_list sorted; +size_t min_length, max_length; +}; +#define PREFIX_ITEM_LIST_INIT \ + { STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, 1, 4 } + +static void prefix_item_list_clear (struct prefix_item_list *list) +{ +string_list_clear (&list->items,1 ); +string_list_clear (&list->sorted,0 ); +} + +static void extend_prefix_length (struct string_list_item *p, +const char *other_string,size_t max_length) +{ +size_t *len = p->util; + +if (!*len ||memcmp (p->string, other_string, *len)) +return ; + +for (;;) { +char c = p->string[*len]; + +/* +* Is `p` a strict prefix of `other`? Or have we exhausted the +* maximal length of the prefix? Or is the current character a +* multi-byte UTF-8 one? If so, there is no valid, unique +* prefix. +*/ +if (!c || ++*len > max_length || !isascii (c)) { + *len =0 ; +break ; + } + +if (c != other_string[*len -1 ]) +break ; + } +} + +static void find_unique_prefixes (struct prefix_item_list *list) +{ +size_t i; + +if (list->sorted.nr == list->items.nr) +return ; + +string_list_clear (&list->sorted,0 ); +/* Avoid reallocating incrementally */ + list->sorted.items =xmalloc (st_mult (sizeof (*list->sorted.items), + list->items.nr)); + list->sorted.nr = list->sorted.alloc = list->items.nr; + +for (i =0 ; i < list->items.nr; i++) { + list->sorted.items[i].string = list->items.items[i].string; + list->sorted.items[i].util = list->items.items + i; + } + +string_list_sort (&list->sorted); + +for (i =0 ; i < list->sorted.nr; i++) { +struct string_list_item *sorted_item = list->sorted.items + i; +struct string_list_item *item = sorted_item->util; +size_t *len = item->util; + + *len =0 ; +while (*len < list->min_length) { +char c = item->string[(*len)++]; + +if (!c || !isascii (c)) { + *len =0 ; +break ; + } + } + +if (i >0 ) +extend_prefix_length (item, sorted_item[-1 ].string, + list->max_length); +if (i +1 < list->sorted.nr) +extend_prefix_length (item, sorted_item[1 ].string, + list->max_length); + } +} + +static ssize_t find_unique (const char *string,struct prefix_item_list *list) +{ +int index =string_list_find_insert_index (&list->sorted, string,1 ); +struct string_list_item *item; + +if (list->items.nr != list->sorted.nr) +BUG ("prefix_item_list in inconsistent state (%" PRIuMAX +" vs %" PRIuMAX")" , + (uintmax_t )list->items.nr, (uintmax_t )list->sorted.nr); + +if (index <0 ) + item = list->sorted.items[-1 - index].util; +else if (index >0 && +starts_with (list->sorted.items[index -1 ].string, string)) +return -1 ; +else if (index +1 < list->sorted.nr && +starts_with (list->sorted.items[index +1 ].string, string)) +return -1 ; +else if (index < list->sorted.nr) + item = list->sorted.items[index].util; +else +return -1 ; +return item - list->items.items; +} +struct list_options {int columns;const char *header;
@@ -95,18 +221,21 @@ struct list_and_choose_options {* If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF, * `LIST_AND_CHOOSE_QUIT` is returned. */ -static ssize_t list_and_choose (struct add_i_state *s,struct string_list *items, +static ssize_t list_and_choose (struct add_i_state *s, +struct prefix_item_list *items,struct list_and_choose_options *opts) {struct strbuf input =STRBUF_INIT ;ssize_t res =LIST_AND_CHOOSE_ERROR ; +find_unique_prefixes (items); +for (;;) {char *p;strbuf_reset (&input); -list (s, items, &opts->list_opts); +list (s, &items->items, &opts->list_opts);printf ("%s%s" , opts->prompt,"> " );fflush (stdout);
@@ -141,7 +270,10 @@ static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items, } p[sep] ='\0' ; -if (index <0 || index >= items->nr) +if (index <0 ) + index =find_unique (p, items); + +if (index <0 || index >= items->items.nr)printf (_ ("Huh (%s)?\n" ), p);else { res = index;
@@ -307,6 +439,23 @@ static void render_adddel(struct strbuf *buf,strbuf_addstr (buf, no_changes); } +/* filters out prefixes which have special meaning to list_and_choose() */ +static int is_valid_prefix (const char *prefix,size_t prefix_len) +{ +return prefix_len && prefix && +/* +* We expect `prefix` to be NUL terminated, therefore this +* `strcspn()` call is okay, even if it might do much more +* work than strictly necessary. +*/ +strcspn (prefix," \t\r\n," ) >= prefix_len &&/* separators */ + *prefix !='-' &&/* deselection */ + !isdigit (*prefix) &&/* selection */ + (prefix_len !=1 || + (*prefix !='*' &&/* "all" wildcard */ + *prefix !='?' ));/* prompt help */ +} +struct print_file_item_data {const char *modified_fmt;struct strbuf buf, index, worktree;
@@ -346,10 +495,23 @@ typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,struct string_list *files, struct list_options *opts); +struct command_item { +size_t prefix_length; +command_t command; +}; +static void print_command_item (int i,struct string_list_item *item,void *print_command_item_data) { -printf (" %2d: %s" , i +1 , item->string); +struct command_item *util = item->util; + +if (!util->prefix_length || + !is_valid_prefix (item->string, util->prefix_length)) +printf (" %2d: %s" , i +1 , item->string); +else +printf (" %2d: [%.*s]%s" , i +1 , + (int )util->prefix_length, item->string, + item->string + util->prefix_length); }int run_add_i (struct repository *r,const struct pathspec *ps)
@@ -365,7 +527,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps) } command_list[] = { { "status", run_status }, }; -struct string_list commands =STRING_LIST_INIT_NODUP ; +struct prefix_item_list commands =PREFIX_ITEM_LIST_INIT ;struct print_file_item_data print_file_item_data = { "%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
@@ -378,9 +540,12 @@ int run_add_i(struct repository *r, const struct pathspec *ps)ssize_t i;int res =0 ; -for (i =0 ; i <ARRAY_SIZE (command_list); i++) -string_list_append (&commands, command_list[i].string) - ->util = command_list[i].command; +for (i =0 ; i <ARRAY_SIZE (command_list); i++) { +struct command_item *util =xcalloc (sizeof (*util),1 ); + util->command = command_list[i].command; +string_list_append (&commands.items, command_list[i].string) + ->util = util; + }init_add_i_state (&s, r);
@@ -405,8 +570,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)break ; }if (i !=LIST_AND_CHOOSE_ERROR ) { -command_t command = commands.items[i].util; - res =command (&s, ps, &files, &opts); +struct command_item *util = + commands.items.items[i].util; + res = util->command (&s, ps, &files, &opts); } }
@@ -415,7 +581,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)strbuf_release (&print_file_item_data.index);strbuf_release (&print_file_item_data.worktree);strbuf_release (&header); -string_list_clear (&commands,0 ); +prefix_item_list_clear (&commands);return res; }
Signature · 1 line
gitgitgadget
PATCH v6 7/9 built-in add -i: support `?` (prompt help)
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
With this change, we print out the same colored help text that the Perl-based git add -i prints in the main loop when question mark is entered.
--- add-interactive.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-)
add-interactive.c +21 -1
@@ -11,6 +11,7 @@ struct add_i_state {struct repository *r;int use_color;char header_color[COLOR_MAXLEN ]; +char help_color[COLOR_MAXLEN ]; }; static void init_color(struct repository *r, struct add_i_state *s,
@@ -43,6 +44,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r) s->use_color =want_color (s->use_color);init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD ); +init_color (r, s,"help" , s->help_color,GIT_COLOR_BOLD_RED ); }/*
@@ -210,6 +212,7 @@ struct list_and_choose_options {struct list_options list_opts;const char *prompt; +void (*print_help)(struct add_i_state *s); };#define LIST_AND_CHOOSE_ERROR (-1)
@@ -250,6 +253,11 @@ static ssize_t list_and_choose(struct add_i_state *s,if (!input.len)break ; +if (!strcmp (input.buf,"?" )) { + opts->print_help (s); +continue ; + } + p = input.buf;for (;;) {size_t sep =strcspn (p," \t\r\n," );
@@ -514,12 +522,24 @@ static void print_command_item(int i, struct string_list_item *item, item->string + util->prefix_length); } +static void command_prompt_help (struct add_i_state *s) +{ +const char *help_color = s->help_color; +color_fprintf_ln (stdout, help_color,"%s" ,_ ("Prompt help:" )); +color_fprintf_ln (stdout, help_color,"1 - %s" , +_ ("select a numbered item" )); +color_fprintf_ln (stdout, help_color,"foo - %s" , +_ ("select item based on unique prefix" )); +color_fprintf_ln (stdout, help_color," - %s" , +_ ("(empty) select nothing" )); +} +int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL };struct list_and_choose_options main_loop_opts = { {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, -N_ ("What now" ) +N_ ("What now" ), command_prompt_help };struct {const char *string;
Signature · 1 line
gitgitgadget
PATCH v6 9/9 built-in add -i: implement the `help` command
In reply to Johannes Schindelin via GitGitGadgetFrom: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <[email protected]>
This imitates the code to show the help text from the Perl script git-add--interactive.perl in the built-in version.
To make sure that it renders exactly like the Perl version of git add -i, we also add a test case for that to t3701-add-interactive.sh.
--- add-interactive.c | 21 +++++++++++++++++++++ t/t3701-add-interactive.sh | 25 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+)
add-interactive.c +21 -0
@@ -507,6 +507,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return 0 ; } +static int run_help (struct add_i_state *s,const struct pathspec *unused_ps, +struct string_list *unused_files, +struct list_options *unused_opts) +{ +color_fprintf_ln (stdout, s->help_color,"status - %s" , +_ ("show paths with changes" )); +color_fprintf_ln (stdout, s->help_color,"update - %s" , +_ ("add working tree state to the staged set of changes" )); +color_fprintf_ln (stdout, s->help_color,"revert - %s" , +_ ("revert staged set of changes back to the HEAD version" )); +color_fprintf_ln (stdout, s->help_color,"patch - %s" , +_ ("pick hunks and update selectively" )); +color_fprintf_ln (stdout, s->help_color,"diff - %s" , +_ ("view diff between HEAD and index" )); +color_fprintf_ln (stdout, s->help_color,"add untracked - %s" , +_ ("add contents of untracked files to the staged set of changes" )); + +return 0 ; +} +typedef int (*command_t )(struct add_i_state *s,const struct pathspec *ps,struct string_list *files,struct list_options *opts);
@@ -560,6 +580,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)command_t command; } command_list[] = { { "status", run_status }, + { "help", run_help }, }; struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
t/t3701-add-interactive.sh +25 -0
@@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' ' test_write_lines a b a b a a b a b a >expect && test_cmp expect a ' + +test_expect_success 'show help from add--helper' ' + git reset --hard && + cat >expect <<-EOF && + + <BOLD>*** Commands ***<RESET> + 1: <BOLD;BLUE>s<RESET>tatus 2: <BOLD;BLUE>u<RESET>pdate 3: <BOLD;BLUE>r<RESET>evert 4: <BOLD;BLUE>a<RESET>dd untracked + 5: <BOLD;BLUE>p<RESET>atch 6: <BOLD;BLUE>d<RESET>iff 7: <BOLD;BLUE>q<RESET>uit 8: <BOLD;BLUE>h<RESET>elp + <BOLD;BLUE>What now<RESET>> <BOLD;RED>status - show paths with changes<RESET> + <BOLD;RED>update - add working tree state to the staged set of changes<RESET> + <BOLD;RED>revert - revert staged set of changes back to the HEAD version<RESET> + <BOLD;RED>patch - pick hunks and update selectively<RESET> + <BOLD;RED>diff - view diff between HEAD and index<RESET> + <BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET> + <BOLD>*** Commands ***<RESET> + 1: <BOLD;BLUE>s<RESET>tatus 2: <BOLD;BLUE>u<RESET>pdate 3: <BOLD;BLUE>r<RESET>evert 4: <BOLD;BLUE>a<RESET>dd untracked + 5: <BOLD;BLUE>p<RESET>atch 6: <BOLD;BLUE>d<RESET>iff 7: <BOLD;BLUE>q<RESET>uit 8: <BOLD;BLUE>h<RESET>elp + <BOLD;BLUE>What now<RESET>>$SP + Bye. + EOF + test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored && + test_decode_color <actual.colored >actual && + test_i18ncmp expect actual +' + test_done
Signature · 1 line
gitgitgadget
PATCH v6 5/9 built-in add -i: implement the main loop
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
The reason why we did not start with the main loop to begin with is that it is the first user of list_and_choose(), which uses the list() function that we conveniently introduced for use by the status command.
In contrast to the Perl version, in the built-in interactive add, we will keep the list() function (which only displays items) and the list_and_choose() function (which uses list() to display the items, and only takes care of the "and choose" part) separate.
The list_and_choose() function, as implemented in git-add--interactive.perl knows a few more tricks than the function we introduce in this patch:
- There is a flag to let the user select multiple items.
- In multi-select mode, the list of items is prefixed with a marker indicating what items have been selected.
- Initially, for each item a unique prefix is determined (if there exists any within the given parameters), and shown in the list, and accepted as a shortcut for the selection.
These features will be implemented in the C version later.
This patch does not add any new main loop command, of course, the built-in git add -i still only supports the status command. The remaining commands to follow over the course of the next commits.
To accommodate for listing the commands in columns, preparing for the commands that will be implemented over the course of the next patches/patch series, we teach the list() function to do precisely that.
Note that we only have a prompt ending in a single ">" at this stage; later commits will add commands that display a double ">>" to indicate that the user is in a different loop than the main one.
--- add-interactive.c | 136 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 2 deletions(-)
add-interactive.c +134 -2
@@ -46,6 +46,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r) }struct list_options { +int columns;const char *header;void (*print_item)(int i,struct string_list_item *item,void *print_item_data);void *print_item_data;
@@ -54,7 +55,7 @@ struct list_options {static void list (struct add_i_state *s,struct string_list *list,struct list_options *opts) { -int i; +int i, last_lf =0 ;if (!list->nr)return ;
@@ -65,8 +66,97 @@ static void list(struct add_i_state *s, struct string_list *list,for (i =0 ; i < list->nr; i++) { opts->print_item (i, list->items + i, opts->print_item_data); + +if ((opts->columns) && ((i +1 ) % (opts->columns))) { +putchar ('\t' ); + last_lf =0 ; + } +else { +putchar ('\n' ); + last_lf =1 ; + } + } + +if (!last_lf)putchar ('\n' ); +} +struct list_and_choose_options { +struct list_options list_opts; + +const char *prompt; +}; + +#define LIST_AND_CHOOSE_ERROR (-1) +#define LIST_AND_CHOOSE_QUIT (-2) + +/* +* Returns the selected index. +* +* If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF, +* `LIST_AND_CHOOSE_QUIT` is returned. +*/ +static ssize_t list_and_choose (struct add_i_state *s,struct string_list *items, +struct list_and_choose_options *opts) +{ +struct strbuf input =STRBUF_INIT ; +ssize_t res =LIST_AND_CHOOSE_ERROR ; + +for (;;) { +char *p; + +strbuf_reset (&input); + +list (s, items, &opts->list_opts); + +printf ("%s%s" , opts->prompt,"> " ); +fflush (stdout); + +if (strbuf_getline (&input, stdin) ==EOF ) { +putchar ('\n' ); + res =LIST_AND_CHOOSE_QUIT ; +break ; + } +strbuf_trim (&input); + +if (!input.len) +break ; + + p = input.buf; +for (;;) { +size_t sep =strcspn (p," \t\r\n," ); +ssize_t index =-1 ; + +if (!sep) { +if (!*p) +break ; + p++; +continue ; + } + +if (isdigit (*p)) { +char *endp; + index =strtoul (p, &endp,10 ) -1 ; +if (endp != p + sep) + index =-1 ; + } + + p[sep] ='\0' ; +if (index <0 || index >= items->nr) +printf (_ ("Huh (%s)?\n" ), p); +else { + res = index; +break ; + } + + p += sep +1 ; + } + +if (res !=LIST_AND_CHOOSE_ERROR ) +break ; } + +strbuf_release (&input); +return res; }struct adddel {
@@ -252,20 +342,48 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return 0 ; } +typedef int (*command_t )(struct add_i_state *s,const struct pathspec *ps, +struct string_list *files, +struct list_options *opts); + +static void print_command_item (int i,struct string_list_item *item, +void *print_command_item_data) +{ +printf (" %2d: %s" , i +1 , item->string); +} +int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL }; +struct list_and_choose_options main_loop_opts = { + {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, +N_ ("What now" ) + }; +struct { +const char *string; +command_t command; + } command_list[] = { + {"status" , run_status }, + }; +struct string_list commands =STRING_LIST_INIT_NODUP ; +struct print_file_item_data print_file_item_data = {"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT };struct list_options opts = { -NULL , print_file_item, &print_file_item_data +0 ,NULL , print_file_item, &print_file_item_data };struct strbuf header =STRBUF_INIT ;struct string_list files =STRING_LIST_INIT_DUP ; +ssize_t i;int res =0 ; +for (i =0 ; i <ARRAY_SIZE (command_list); i++) +string_list_append (&commands, command_list[i].string) + ->util = command_list[i].command; +init_add_i_state (&s, r); +strbuf_addstr (&header," " );strbuf_addf (&header, print_file_item_data.modified_fmt,_ ("staged" ),_ ("unstaged" ),_ ("path" ));
@@ -279,11 +397,25 @@ int run_add_i(struct repository *r, const struct pathspec *ps) res =run_status (&s, ps, &files, &opts); +for (;;) { + i =list_and_choose (&s, &commands, &main_loop_opts); +if (i ==LIST_AND_CHOOSE_QUIT ) { +printf (_ ("Bye.\n" )); + res =0 ; +break ; + } +if (i !=LIST_AND_CHOOSE_ERROR ) { +command_t command = commands.items[i].util; + res =command (&s, ps, &files, &opts); + } + } +string_list_clear (&files,1 );strbuf_release (&print_file_item_data.buf);strbuf_release (&print_file_item_data.index);strbuf_release (&print_file_item_data.worktree);strbuf_release (&header); +string_list_clear (&commands,0 );return res; }
Signature · 1 line
gitgitgadget
PATCH v6 8/9 built-in add -i: use color in the main loop
In reply to Johannes Schindelin via GitGitGadgetFrom: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <[email protected]>
The error messages as well as the unique prefixes are colored in git add -i by default; We need to do the same in the built-in version.
--- add-interactive.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-)
add-interactive.c +29 -6
@@ -12,6 +12,9 @@ struct add_i_state {int use_color;char header_color[COLOR_MAXLEN ];char help_color[COLOR_MAXLEN ]; +char prompt_color[COLOR_MAXLEN ]; +char error_color[COLOR_MAXLEN ]; +char reset_color[COLOR_MAXLEN ]; }; static void init_color(struct repository *r, struct add_i_state *s,
@@ -45,6 +48,9 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD );init_color (r, s,"help" , s->help_color,GIT_COLOR_BOLD_RED ); +init_color (r, s,"prompt" , s->prompt_color,GIT_COLOR_BOLD_BLUE ); +init_color (r, s,"error" , s->error_color,GIT_COLOR_BOLD_RED ); +init_color (r, s,"reset" , s->reset_color,GIT_COLOR_RESET ); }/*
@@ -240,7 +246,8 @@ static ssize_t list_and_choose(struct add_i_state *s,list (s, &items->items, &opts->list_opts); -printf ("%s%s" , opts->prompt,"> " ); +color_fprintf (stdout, s->prompt_color,"%s" , opts->prompt); +fputs ("> " , stdout);fflush (stdout);if (strbuf_getline (&input, stdin) ==EOF ) {
@@ -282,7 +289,8 @@ static ssize_t list_and_choose(struct add_i_state *s, index =find_unique (p, items);if (index <0 || index >= items->items.nr) -printf (_ ("Huh (%s)?\n" ), p); +color_fprintf_ln (stdout, s->error_color, +_ ("Huh (%s)?" ), p);else { res = index;break ;
@@ -508,18 +516,23 @@ struct command_item {command_t command; }; +struct print_command_item_data { +const char *color, *reset; +}; +static void print_command_item (int i,struct string_list_item *item,void *print_command_item_data) { +struct print_command_item_data *d = print_command_item_data;struct command_item *util = item->util;if (!util->prefix_length || !is_valid_prefix (item->string, util->prefix_length))printf (" %2d: %s" , i +1 , item->string);else -printf (" %2d: [%.*s]%s" , i +1 , - (int )util->prefix_length, item->string, - item->string + util->prefix_length); +printf (" %2d: %s%.*s%s%s" , i +1 , + d->color, (int )util->prefix_length, item->string, + d->reset, item->string + util->prefix_length); }static void command_prompt_help (struct add_i_state *s)
@@ -537,8 +550,9 @@ static void command_prompt_help(struct add_i_state *s)int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL }; +struct print_command_item_data data = {"[" ,"]" };struct list_and_choose_options main_loop_opts = { - {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, + {4 ,N_ ("*** Commands ***" ), print_command_item, &data },N_ ("What now" ), command_prompt_help };struct {
@@ -569,6 +583,15 @@ int run_add_i(struct repository *r, const struct pathspec *ps)init_add_i_state (&s, r); +/* +* When color was asked for, use the prompt color for +* highlighting, otherwise use square brackets. +*/ +if (s.use_color) { + data.color = s.prompt_color; + data.reset = s.reset_color; + } +strbuf_addstr (&header," " );strbuf_addf (&header, print_file_item_data.modified_fmt,_ ("staged" ),_ ("unstaged" ),_ ("path" ));
Signature · 1 line
gitgitgadget
PATCH v6 4/9 built-in add -i: color the header in the `status` command
In reply to Johannes Schindelin via GitGitGadgetFrom: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <[email protected]>
For simplicity, we only implemented the status command without colors. This patch starts adding color, matching what the Perl script git-add--interactive.perl does.
--- add-interactive.c | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-)
add-interactive.c +37 -4
@@ -1,5 +1,7 @@#include "cache.h" #include "add-interactive.h" +#include "color.h" +#include "config.h" #include "diffcore.h" #include "revision.h" #include "refs.h"
@@ -7,11 +9,40 @@struct add_i_state {struct repository *r; +int use_color; +char header_color[COLOR_MAXLEN ]; }; +static void init_color (struct repository *r,struct add_i_state *s, +const char *slot_name,char *dst, +const char *default_color) +{ +char *key =xstrfmt ("color.interactive.%s" , slot_name); +const char *value; + +if (!s->use_color) + dst[0 ] ='\0' ; +else if (repo_config_get_value (r, key, &value) || +color_parse (value, dst)) +strlcpy (dst, default_color,COLOR_MAXLEN ); + +free (key); +} +static void init_add_i_state (struct add_i_state *s,struct repository *r) { - s->r = r; +const char *value; + + s->r = r; + +if (repo_config_get_value (r,"color.interactive" , &value)) + s->use_color =-1 ; +else + s->use_color = +git_config_colorbool ("color.interactive" , value); + s->use_color =want_color (s->use_color); + +init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD ); }struct list_options {
@@ -20,7 +51,8 @@ struct list_options {void *print_item_data; }; -static void list (struct string_list *list,struct list_options *opts) +static void list (struct add_i_state *s,struct string_list *list, +struct list_options *opts) {int i;
@@ -28,7 +60,8 @@ static void list(struct string_list *list, struct list_options *opts)return ;if (opts->header) -printf ("%s\n" , opts->header); +color_fprintf_ln (stdout, s->header_color, +"%s" , opts->header);for (i =0 ; i < list->nr; i++) { opts->print_item (i, list->items + i, opts->print_item_data);
@@ -213,7 +246,7 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,if (get_modified_files (s->r, files, ps) <0 )return -1 ; -list (files, opts); +list (s, files, opts);putchar ('\n' );return 0 ;
Signature · 1 line
gitgitgadget
Hi,
Quoted 17 lines · Johannes Schindelin via GitGitGadget
This is the first leg on the long journey to a fully built-in git add -i (next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3
[https://github.com/gitgitgadget/git/pull/172], 4
[https://github.com/gitgitgadget/git/pull/173], 5
[https://github.com/gitgitgadget/git/pull/174], and 6
[https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are not necessarily up to date, and will be re-targeted to the appropriate branches in https://github.com/gitster/git as soon as Junio picks them up.This here patch series reflects the part that was submitted a couple of times (see https://github.com/gitgitgadget/git/pull/103) during the Outreachy project by Slavica Ðukic that continued the journey based on an initial patch series by Daniel Ferreira.
It only implements the status and the help part, in the interest of making the review remotely more reviewable.
Quoted 7 lines
As I am a heavy user of git add -p myself and use a patched version for several months already (it is so nice to not suffer over one second startup until the MSYS2 Perl finally shows me anything, instead it feels instantaneous), I integrated these patch series into Git for Windows already, as an opt-in feature guarded by the config variable add.interactive.useBuiltin (and Git for Windows' installer knows to detect this version and offer the option in the graphical user interface).
And of course I forgot to summarize the changes since v5:
- Reworded two commit messages.
- Clarified code that does not affect patch_mode.
- Restricted scope of the very local variable endp.
Ciao,
Johannes
Quoted 262 lines
Changes since v4:
* Rebased onto current master to make use of Thomas Gummerer's
repo_refresh_and_write_index()as well as to avoid merge conflicts with Eric Wong's work on struct hashmap.
* Instead of rolling a dedicated data struct to simulate a Trie, we now use string-list extensively (an unsorted copy and a sorted one, the latter to determine unique prefixes). This had massive ramifications on the rest of the patches... For example, the structcommand_itemstructure no longer contains the name field, but is intended to be a util in astring_list.
* Changed the commit messages and author lines to reflect Slavica's name correctly.
* Touched up a couple commit messages.Changes since v3:
* Rebased to v2.23.0 to reduce friction.
*free_diffstat_info()is now made public as well, and used, to avoid a memory leak.
* Prepared the patches for ew/hashmap (which is strict about the hashmap entries' type inhashmap_entry_init()and friends).
* The private data types have been moved fromprefix-map.htoprefix-map.c.
* A lot of int types were converted to more appropriatesize_tinprefix-map.c.
* A misleading parameter name list was renamed to the correct array.
* The code comment abovefind_unique_prefixes()was (hopefully) improved.
* Therun_help()function's signature now reflects that most of the parameters are actually unused.Changes since v2:
* Rebased to master to avoid merge conflicts.
* Renumbered the prefix-map test to avoid conflicts with two patch series that are currently in-flight in pu.Changes since v1:
* The config machinery was reworked completely, to not use a callback to
git_config(), but instead to query the config via the repo_config_get_*() functions. This also prevents a future "Huh???" moment: the internal add --interactive API accepts a parameter of type struct repository *r, but the previous configuration did not use that to query the config (and could in the future be a repository other thanthe_repository).* As a consequence, the color sequences are no longer stored in file-local variables, but passed around via a struct.
* Instead of using the magical constant -2 to quit the main loop, it is now defined as
LIST_AND_CHOOSE_QUIT(and likewise,LIST_AND_CHOOSE_ERRORis defined as -1 and used where appropriate).* Improved the
add_prefix_item()function by avoiding buffer overruns, not reusing the struct that is used for lookup also for adding the new item, and by strengthening the bug check.Daniel Ferreira (2):
diff: export diffstat interface
built-in add -i: implement thestatuscommandJohannes Schindelin (4):
Start to implement a built-in version ofgit add --interactivebuilt-in add -i: implement the main loop
built-in add -i: show unique prefixes of the commands built-in add -i: support?(prompt help)Slavica Đukić (3):
built-in add -i: color the header in thestatuscommand built-in add -i: use color in the main loop built-in add -i: implement thehelpcommandDocumentation/config/add.txt | 5 + Makefile | 1 + add-interactive.c | 651 +++++++++++++++++++++++++++++++++++ add-interactive.h | 8 + builtin/add.c | 12 + diff.c | 39 +-- diff.h | 20 ++ t/README | 4 + t/t3701-add-interactive.sh | 25 ++ 9 files changed, 742 insertions(+), 23 deletions(-) create mode 100644 add-interactive.c create mode 100644 add-interactive.hbase-commit:
566a1439f6f56c2171b8853ddbca0ad3f5098770
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v6
Pull-Request: https://github.com/gitgitgadget/git/pull/170Range-diff vs v5:
1: ff59d2d0b3 ! 1: 5d9962d434 Start to implement a built-in version of `git add --interactive` @@ -2,31 +2,31 @@Start to implement a built-in version of
git add --interactive- This is hardly the first conversion of a Git command that is implemented
- as a script to a built-in. So far, the most successful strategy for such
- conversions has been to add a built-in helper and call that for more and
- more functionality from the script, as more and more parts are
- converted.
+ Unlike previous conversions to C, where we started with a built-in
+ helper, we start this conversion by adding an interception in the
+run_add_interactive()function when the new opt-in
+add.interactive.useBuiltinconfig knob is turned on (or the
+ corresponding environment variableGIT_TEST_ADD_I_USE_BUILTIN), and
+ calling the new internal API functionrun_add_i()that is implemented
+ directly in libgit.a.- With the interactive add, we choose a different strategy. The sole
- reason for this is that on Windows (where such a conversion has the most
- benefits in terms of speed and robustness) we face the very specific
- problem that asystem()call in Perl seems to closestdinin the
- parent process when the spawned process consumes even one character from
-stdin. And that just does not work for us here, as it would stop the
- main loop as soon as any interactive command was performed by the
- helper. Which is almost all of the commands ingit add -i.
-
- It is almost as if Perl told us once again that it does not want us to
- use it on Windows.
+ At this point, the built-in version ofgit add -ionly states that it
+ cannot do anything yet. In subsequent patches/patch series, the
+run_add_i()function will gain more and more functionality, until it
+ is feature complete. The whole arc of the conversion can be found in the
+ PRs #170-175 at https://github.com/gitgitgadget/git.- Instead, we follow the opposite route where we start with a bare-bones
- version of the built-in interactive add, guarded by the new
-add.interactive.useBuiltinconfig variable, and then add more and more
- functionality to it, until it is feature complete.
+ The "--helper approach" can unfortunately not be used here: on Windows
+ we face the very specific problem that asystem()call in
+ Perl seems to closestdinin the parent process when the spawned
+ process consumes even one character fromstdin. Which prevents us from
+ implementing the main loop in C and still trying to hand off to the Perl
+ script.- At this point, the built-in version of
git add -ionly states that it
- cannot do anything yet ;-)
+ The very real downside of the approach we have to take here is that the
+ test suite won't pass withGIT_TEST_ADD_I_USE_BUILTIN=trueuntil the
+ conversion is complete (the--helperapproach would have let it pass,
+ even at each of the incremental conversion steps).Signed-off-by Johannes Schindelin <[email protected]>@@ -99,12 +99,14 @@
structargv_arrayargv =ARGV_ARRAY_INIT;
+ intuse_builtin_add_i=
+git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
-+ if (use_builtin_add_i< 0)
-+ git_config_get_bool("add.interactive.usebuiltin",
-+&use_builtin_add_i);
+
-+ if (use_builtin_add_i== 1 && !patch_mode)
-+ return !!run_add_i(the_repository, pathspec);
++ if (!patch_mode) {
++ if (use_builtin_add_i< 0)
++ git_config_get_bool("add.interactive.usebuiltin",
++&use_builtin_add_i);
++ if (use_builtin_add_i== 1)
++ return !!run_add_i(the_repository, pathspec);
++ }
argv_array_push(&argv, "add--interactive"); if (patch_mode)2: 2fc8cc3546 = 2: f42d7b1310 diff: export diffstat interface 3: 6aaa0de4f4 = 3: 4836191271 built-in add -i: implement the `status` command 4: e405f07110 = 4: d61cf9daeb built-in add -i: color the header in the `status` command 5: 25590fbbbe ! 5: b0c04e6ec6 built-in add -i: implement the main loop@@ -7,33 +7,34 @@
function that we conveniently introduced for use by thestatuscommand.- Apart from the "and choose" part, there are more differences between the
- way thestatuscommand calls thelist_and_choose()function in the
- Perl version ofgit add -icompared to the other callers of said
- function. The most important ones:
+ In contrast to the Perl version, in the built-in interactiveadd, we
+ will keep thelist()function (which only displays items) and the
+list_and_choose()function (which useslist()to display the items,
+ and only takes care of the "and choose" part) separate.- - The list is not only shown, but the user is also asked to make a
- choice, possibly selecting multiple entries.
+ Thelist_and_choose()function, as implemented in
+git-add--interactive.perlknows a few more tricks than the function we
+ introduce in this patch:- - The list of items is prefixed with a marker indicating what items have
- been selected, if multi-selection is allowed.
+ - There is a flag to let the user select multiple items.- - Initially, for each item a unique prefix (if there exists any within
- the given parameters) is determined, and shown in the list, and
+ - In multi-select mode, the list of items is prefixed with a marker
+ indicating what items have been selected.
+
+ - Initially, for each item a unique prefix is determined (if there
+ exists any within the given parameters), and shown in the list, and
accepted as a shortcut for the selection.- These features will be implemented later, except the part where the user
- can choose a command. At this stage, though, the built-ingit add -i
- still only supports thestatuscommand, with the remaining commands to
- follow over the course of the next commits.
+ These features will be implemented in the C version later.- In addition, we also modify
list()to support displaying the commands
- in columns, even if there is currently only one.
+ This patch does not add any new main loop command, of course, the
+ built-ingit add -istill only supports thestatuscommand. The
+ remaining commands to follow over the course of the next commits.- The Perl script
git-add--interactive.perlmixed the purposes of the
- "list" and the "and choose" part into the same function. In the C
- version, we will keep them separate instead, calling thelist()
- function from thelist_and_choose()function.
+ To accommodate for listing the commands in columns, preparing for the
+ commands that will be implemented over the course of the next
+ patches/patch series, we teach thelist()function to do precisely
+ that.Note that we only have a prompt ending in a single ">" at this stage; later commits will add commands that display a double ">>" to indicate
@@ -101,7 +102,7 @@
+ssize_tres =LIST_AND_CHOOSE_ERROR;
+
+ for (;;) {
-+ char *p, *endp;
++ char *p;
+
+strbuf_reset(&input);
+
@@ -133,6 +134,7 @@
+ }
+
+ if (isdigit(*p)) {
++ char *endp;
+ index =strtoul(p, &endp, 10)- 1;
+ if (endp != p + sep)
+ index = -1;
6:57fdc01463! 6:b6459be5ebbuilt-in add -i: show unique prefixes of the commands@@ -177,7 +177,7 @@ + find_unique_prefixes(items); + for (;;) { - char *p, *endp; + char *p; strbuf_reset(&input);7: 77ad5f333a = 7: bdf9058d9e built-in add -i: support `?` (prompt help) 8: 3d0b172a7f = 8: eafeedc49b built-in add -i: use color in the main loop 9: 85e508ef11 = 9: 7fda76255a built-in add -i: implement the `help` command--
gitgitgadget
Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
In reply to Johannes SchindelinQuoted 5 lines · Johannes Schindelin
Quoted 2 lines
As I do not see those past '--helper' ones necessarily successes, we must agree to disagree here.
Right. But if I recall, you never even saw the need for the conversions in the first place. Maybe you still don't?
You probably are forgetting the fact that I was very supportive for the rewrite of checkout, commit and format-patch (the last one being my favorite) in C from scripted Porcelain.
None of these were '--helper' style conversion and I would consider them much more successful than the recent ones, some of which still suffer from impedance mismatch bugs (e.g. some parts of the C implementation work on the in-core index, while other parts working on the on the on-disk index, letting them become out of sync and introducing bugs).
Re: [PATCH v6 1/9] Start to implement a built-in version of `git add --interactive`
In reply to Johannes Schindelin via GitGitGadget"Johannes Schindelin via GitGitGadget" <[email protected]>
Quoted 4 lines · writes:
The very real downside of the approach we have to take here is that the test suite won't pass with
GIT_TEST_ADD_I_USE_BUILTIN=trueuntil the conversion is complete (the--helperapproach would have let it pass, even at each of the incremental conversion steps).
That actually is an issue of the quality of the tests you add for this series. If they are organized along the arc of run_add_i() gaining features, the invocation of "git add -i" in the tests can selectively use USE_BUILTIN=true to verify what has been rewritten so far.
In any case, I think I've seen the patches in this part of the metaseries enough and I think they are quickly stabilizing. Let's see if others can find and raise issues and otherwise start cooking in 'next' sometime next week.
Thanks.
Re: [PATCH v6 1/9] Start to implement a built-in version of `git add --interactive`
Hi Junio,
Quoted 4 lines · Junio C Hamano
any case, I think I've seen the patches in this part of the metaseries enough and I think they are quickly stabilizing. Let's see if others can find and raise issues and otherwise start cooking in 'next' sometime next week.
While reviewing the next patch series in this arc, I *just* noticed a buffer overrun: in the main loop, path + sep might point to the trailing NUL, but we assign p += sep + 1; at the end (which is only correct when path + sep points at whitespace).
The fix is already pushed up into gitgitgadget/git#170, and the relevant part of the range-diff reads like this:
@@ add-interactive.c: static void list(struct add_i_state *s, struct string_list *l
+ index = -1;
+ }
+
-+ p[sep] = '\0';
++ if (p[sep])
++ p[sep++] = '\0';
+ if (index < 0 || index >= items->nr)
+ printf(_("Huh (%s)?\n"), p);
+ else {
@@ add-interactive.c: static void list(struct add_i_state *s, struct string_list *l
+ break;
+ }
+
-+ p += sep + 1;
++ p += sep;
+ }
+
+ if (res != LIST_AND_CHOOSE_ERROR)I plan on waiting for the PR build to finish, and maybe wait until tomorrow just in case any further suggestion rolls in, then submit the hopefully final iteration.
And yes, I think it is a good time to start cooking this in next, I, for one, am prone to overlook anything crucial in those patches because I have stared at them so often.
Ciao,
Dscho
Re: [PATCH v6 1/9] Start to implement a built-in version of `git add --interactive`
Quoted 3 lines · Johannes Schindelin
I plan on waiting for the PR build to finish, and maybe wait until tomorrow just in case any further suggestion rolls in, then submit the hopefully final iteration.
Thanks.
PATCH v7 1/9 Start to implement a built-in version of `git add --interactive`
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Unlike previous conversions to C, where we started with a built-in helper, we start this conversion by adding an interception in the run_add_interactive() function when the new opt-in add.interactive.useBuiltin config knob is turned on (or the corresponding environment variable GIT_TEST_ADD_I_USE_BUILTIN), and calling the new internal API function run_add_i() that is implemented directly in libgit.a.
At this point, the built-in version of git add -i only states that it cannot do anything yet. In subsequent patches/patch series, the run_add_i() function will gain more and more functionality, until it is feature complete. The whole arc of the conversion can be found in the PRs #170-175 at https://github.com/gitgitgadget/git.
The "--helper approach" can unfortunately not be used here: on Windows we face the very specific problem that a system() call in Perl seems to close stdin in the parent process when the spawned process consumes even one character from stdin. Which prevents us from implementing the main loop in C and still trying to hand off to the Perl script.
The very real downside of the approach we have to take here is that the test suite won't pass with GIT_TEST_ADD_I_USE_BUILTIN=true until the conversion is complete (the --helper approach would have let it pass, even at each of the incremental conversion steps).
--- Documentation/config/add.txt | 5 +++++ Makefile | 1 + add-interactive.c | 7 +++++++ add-interactive.h | 8 ++++++++ builtin/add.c | 12 ++++++++++++ t/README | 4 ++++ 6 files changed, 37 insertions(+) create mode 100644 add-interactive.c create mode 100644 add-interactive.h
Documentation/config/add.txt +5 -0
@@ -5,3 +5,8 @@ add.ignore-errors (deprecated):: option of linkgit:git-add[1]. `add.ignore-errors` is deprecated, as it does not follow the usual naming convention for configuration variables. + +add.interactive.useBuiltin:: + [EXPERIMENTAL] Set to `true` to use the experimental built-in + implementation of the interactive version of linkgit:git-add[1] + instead of the Perl script version. Is `false` by default.
Makefile +1 -0
@@ -823,6 +823,7 @@ LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentat -name '*.h' -print))) LIB_OBJS += abspath.o +LIB_OBJS += add-interactive.o LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.o
add-interactive.c new file +7 -0
@@ -0,0 +1,7 @@ +#include "cache.h" +#include "add-interactive.h" + +int run_add_i (struct repository *r,const struct pathspec *ps) +{ +die (_ ("No commands are available in the built-in `git add -i` yet!" )); +}
add-interactive.h new file +8 -0
@@ -0,0 +1,8 @@ +#ifndef ADD_INTERACTIVE_H +#define ADD_INTERACTIVE_H + +struct repository ; +struct pathspec ; +int run_add_i (struct repository *r,const struct pathspec *ps); + +#endif
builtin/add.c +12 -0
@@ -20,6 +20,7 @@#include "bulk-checkin.h" #include "argv-array.h" #include "submodule.h" +#include "add-interactive.h" static const char *const builtin_add_usage[] = {N_ ("git add [<options>] [--] <pathspec>..." ),
@@ -185,6 +186,16 @@ int run_add_interactive(const char *revision, const char *patch_mode, {int status, i;struct argv_array argv =ARGV_ARRAY_INIT ; +int use_builtin_add_i = +git_env_bool ("GIT_TEST_ADD_I_USE_BUILTIN" ,-1 ); + +if (!patch_mode) { +if (use_builtin_add_i <0 ) +git_config_get_bool ("add.interactive.usebuiltin" , + &use_builtin_add_i); +if (use_builtin_add_i ==1 ) +return !!run_add_i (the_repository, pathspec); + }argv_array_push (&argv,"add--interactive" );if (patch_mode)
@@ -319,6 +330,7 @@ static int add_config(const char *var, const char *value, void *cb) ignore_add_errors =git_config_bool (var, value);return 0 ; } +return git_default_config (var, value, cb); }
t/README +4 -0
@@ -397,6 +397,10 @@ GIT_TEST_STASH_USE_BUILTIN=<boolean>, when false, disables the built-in version of git-stash. See 'stash.useBuiltin' in git-config(1). +GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the +built-in version of git add -i. See 'add.interactive.useBuiltin' in +git-config(1). + GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading of the index for the whole test suite by bypassing the default number of cache entries and thread minimums. Setting this to 1 will make the
Signature · 1 line
gitgitgadget
v7 cover git add -i: add a rudimentary version in C (supporting only status and help so far)
In reply to Johannes Schindelin via GitGitGadgetThis is the first leg on the long journey to a fully built-in git add -i (next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3
[https://github.com/gitgitgadget/git/pull/172], 4
[https://github.com/gitgitgadget/git/pull/173], 5
[https://github.com/gitgitgadget/git/pull/174], and 6
[https://github.com/gitgitgadget/git/pull/175]). Note: the latter PRs are not necessarily up to date, and will be re-targeted to the appropriate branches in https://github.com/gitster/git as soon as Junio picks them up.
This here patch series reflects the part that was submitted a couple of times (see https://github.com/gitgitgadget/git/pull/103) during the Outreachy project by Slavica Ðukic that continued the journey based on an initial patch series by Daniel Ferreira.
It only implements the status and the help part, in the interest of making the review remotely more reviewable.
As I am a heavy user of git add -p myself and use a patched version for several months already (it is so nice to not suffer over one second startup until the MSYS2 Perl finally shows me anything, instead it feels instantaneous), I integrated these patch series into Git for Windows already, as an opt-in feature guarded by the config variable add.interactive.useBuiltin (and Git for Windows' installer knows to detect this version and offer the option in the graphical user interface).
Changes since v6:
* Fixed a potential buffer overrun when parsing numbers/number ranges.
Changes since v5:
* Reworded two commit messages.
* Clarified code that does not affect patch_mode.
* Restricted scope of the very local variable endp.
Changes since v4:
* Rebased onto current master to make use of Thomas Gummerer's repo_refresh_and_write_index() as well as to avoid merge conflicts with Eric Wong's work on struct hashmap.
* Instead of rolling a dedicated data struct to simulate a Trie, we now use string-list extensively (an unsorted copy and a sorted one, the latter to determine unique prefixes). This had massive ramifications on the rest of the patches... For example, the struct command_item structure no longer contains the name field, but is intended to be a util in a string_list.
* Changed the commit messages and author lines to reflect Slavica's name correctly.
* Touched up a couple commit messages.
Changes since v3:
* Rebased to v2.23.0 to reduce friction.
* free_diffstat_info() is now made public as well, and used, to avoid a memory leak.
* Prepared the patches for ew/hashmap (which is strict about the hashmap entries' type in hashmap_entry_init() and friends).
* The private data types have been moved from prefix-map.h to prefix-map.c.
* A lot of int types were converted to more appropriate size_t in prefix-map.c.
* A misleading parameter name list was renamed to the correct array.
* The code comment above find_unique_prefixes() was (hopefully) improved.
* The run_help() function's signature now reflects that most of the parameters are actually unused.
Changes since v2:
* Rebased to master to avoid merge conflicts.
* Renumbered the prefix-map test to avoid conflicts with two patch series that are currently in-flight in pu.
Changes since v1:
* The config machinery was reworked completely, to not use a callback to git_config(), but instead to query the config via the repo_config_get_*() functions. This also prevents a future "Huh???" moment: the internal add --interactive API accepts a parameter of type struct repository *r, but the previous configuration did not use that to query the config (and could in the future be a repository other than the_repository).
* As a consequence, the color sequences are no longer stored in file-local variables, but passed around via a struct.
* Instead of using the magical constant -2 to quit the main loop, it is now defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is defined as -1 and used where appropriate).
* Improved the add_prefix_item() function by avoiding buffer overruns, not reusing the struct that is used for lookup also for adding the new item, and by strengthening the bug check.
Daniel Ferreira (2):
diff: export diffstat interface
built-in add -i: implement the status command
Johannes Schindelin (4):
Start to implement a built-in version of git add --interactive built-in add -i: implement the main loop
built-in add -i: show unique prefixes of the commands built-in add -i: support ? (prompt help)
Slavica Đukić (3):
built-in add -i: color the header in the status command built-in add -i: use color in the main loop built-in add -i: implement the help command
Documentation/config/add.txt | 5 + Makefile | 1 + add-interactive.c | 652 +++++++++++++++++++++++++++++++++++ add-interactive.h | 8 + builtin/add.c | 12 + diff.c | 39 +-- diff.h | 20 ++ t/README | 4 + t/t3701-add-interactive.sh | 25 ++ 9 files changed, 743 insertions(+), 23 deletions(-) create mode 100644 add-interactive.c create mode 100644 add-interactive.h
base-commit: 566a1439f6f56c2171b8853ddbca0ad3f5098770
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v7
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v7
Pull-Request: https://github.com/gitgitgadget/git/pull/170
Range-diff vs v6:
1: 5d9962d434 = 1: 5d9962d434 Start to implement a built-in version of `git add --interactive`
2: f42d7b1310 = 2: f42d7b1310 diff: export diffstat interface
3: 4836191271 = 3: 4836191271 built-in add -i: implement the `status` command
4: d61cf9daeb = 4: d61cf9daeb built-in add -i: color the header in the `status` command
5: b0c04e6ec6 ! 5: 601c371089 built-in add -i: implement the main loop
@@ -140,7 +140,8 @@
+ index = -1;
+ }
+
-+ p[sep] = '\0';
++ if (p[sep])
++ p[sep++] = '\0';
+ if (index < 0 || index >= items->nr)
+ printf(_("Huh (%s)?\n"), p);
+ else {
@@ -148,7 +149,7 @@
+ break;
+ }
+
-+ p += sep + 1;
++ p += sep;
+ }
+
+ if (res != LIST_AND_CHOOSE_ERROR)
6: b6459be5eb ! 6: 978bb25b81 built-in add -i: show unique prefixes of the commands
@@ -187,9 +187,9 @@
printf("%s%s", opts->prompt, "> ");
fflush(stdout);
@@
- }
- p[sep] = '\0';
+ if (p[sep])
+ p[sep++] = '\0';
- if (index < 0 || index >= items->nr)
+ if (index < 0)
+ index = find_unique(p, items);
7: bdf9058d9e = 7: d7d447026c built-in add -i: support `?` (prompt help)
8: eafeedc49b = 8: bcf4aa7c5e built-in add -i: use color in the main loop
9: 7fda76255a = 9: ccc6fb4558 built-in add -i: implement the `help` commandSignature · 1 line
gitgitgadget
PATCH v7 4/9 built-in add -i: color the header in the `status` command
From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <[email protected]>
For simplicity, we only implemented the status command without colors. This patch starts adding color, matching what the Perl script git-add--interactive.perl does.
--- add-interactive.c | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-)
add-interactive.c +37 -4
@@ -1,5 +1,7 @@#include "cache.h" #include "add-interactive.h" +#include "color.h" +#include "config.h" #include "diffcore.h" #include "revision.h" #include "refs.h"
@@ -7,11 +9,40 @@struct add_i_state {struct repository *r; +int use_color; +char header_color[COLOR_MAXLEN ]; }; +static void init_color (struct repository *r,struct add_i_state *s, +const char *slot_name,char *dst, +const char *default_color) +{ +char *key =xstrfmt ("color.interactive.%s" , slot_name); +const char *value; + +if (!s->use_color) + dst[0 ] ='\0' ; +else if (repo_config_get_value (r, key, &value) || +color_parse (value, dst)) +strlcpy (dst, default_color,COLOR_MAXLEN ); + +free (key); +} +static void init_add_i_state (struct add_i_state *s,struct repository *r) { - s->r = r; +const char *value; + + s->r = r; + +if (repo_config_get_value (r,"color.interactive" , &value)) + s->use_color =-1 ; +else + s->use_color = +git_config_colorbool ("color.interactive" , value); + s->use_color =want_color (s->use_color); + +init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD ); }struct list_options {
@@ -20,7 +51,8 @@ struct list_options {void *print_item_data; }; -static void list (struct string_list *list,struct list_options *opts) +static void list (struct add_i_state *s,struct string_list *list, +struct list_options *opts) {int i;
@@ -28,7 +60,8 @@ static void list(struct string_list *list, struct list_options *opts)return ;if (opts->header) -printf ("%s\n" , opts->header); +color_fprintf_ln (stdout, s->header_color, +"%s" , opts->header);for (i =0 ; i < list->nr; i++) { opts->print_item (i, list->items + i, opts->print_item_data);
@@ -213,7 +246,7 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,if (get_modified_files (s->r, files, ps) <0 )return -1 ; -list (files, opts); +list (s, files, opts);putchar ('\n' );return 0 ;
Signature · 1 line
gitgitgadget
PATCH v7 5/9 built-in add -i: implement the main loop
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
The reason why we did not start with the main loop to begin with is that it is the first user of list_and_choose(), which uses the list() function that we conveniently introduced for use by the status command.
In contrast to the Perl version, in the built-in interactive add, we will keep the list() function (which only displays items) and the list_and_choose() function (which uses list() to display the items, and only takes care of the "and choose" part) separate.
The list_and_choose() function, as implemented in git-add--interactive.perl knows a few more tricks than the function we introduce in this patch:
- There is a flag to let the user select multiple items.
- In multi-select mode, the list of items is prefixed with a marker indicating what items have been selected.
- Initially, for each item a unique prefix is determined (if there exists any within the given parameters), and shown in the list, and accepted as a shortcut for the selection.
These features will be implemented in the C version later.
This patch does not add any new main loop command, of course, the built-in git add -i still only supports the status command. The remaining commands to follow over the course of the next commits.
To accommodate for listing the commands in columns, preparing for the commands that will be implemented over the course of the next patches/patch series, we teach the list() function to do precisely that.
Note that we only have a prompt ending in a single ">" at this stage; later commits will add commands that display a double ">>" to indicate that the user is in a different loop than the main one.
--- add-interactive.c | 137 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-)
add-interactive.c +135 -2
@@ -46,6 +46,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r) }struct list_options { +int columns;const char *header;void (*print_item)(int i,struct string_list_item *item,void *print_item_data);void *print_item_data;
@@ -54,7 +55,7 @@ struct list_options {static void list (struct add_i_state *s,struct string_list *list,struct list_options *opts) { -int i; +int i, last_lf =0 ;if (!list->nr)return ;
@@ -65,8 +66,98 @@ static void list(struct add_i_state *s, struct string_list *list,for (i =0 ; i < list->nr; i++) { opts->print_item (i, list->items + i, opts->print_item_data); + +if ((opts->columns) && ((i +1 ) % (opts->columns))) { +putchar ('\t' ); + last_lf =0 ; + } +else { +putchar ('\n' ); + last_lf =1 ; + } + } + +if (!last_lf)putchar ('\n' ); +} +struct list_and_choose_options { +struct list_options list_opts; + +const char *prompt; +}; + +#define LIST_AND_CHOOSE_ERROR (-1) +#define LIST_AND_CHOOSE_QUIT (-2) + +/* +* Returns the selected index. +* +* If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF, +* `LIST_AND_CHOOSE_QUIT` is returned. +*/ +static ssize_t list_and_choose (struct add_i_state *s,struct string_list *items, +struct list_and_choose_options *opts) +{ +struct strbuf input =STRBUF_INIT ; +ssize_t res =LIST_AND_CHOOSE_ERROR ; + +for (;;) { +char *p; + +strbuf_reset (&input); + +list (s, items, &opts->list_opts); + +printf ("%s%s" , opts->prompt,"> " ); +fflush (stdout); + +if (strbuf_getline (&input, stdin) ==EOF ) { +putchar ('\n' ); + res =LIST_AND_CHOOSE_QUIT ; +break ; + } +strbuf_trim (&input); + +if (!input.len) +break ; + + p = input.buf; +for (;;) { +size_t sep =strcspn (p," \t\r\n," ); +ssize_t index =-1 ; + +if (!sep) { +if (!*p) +break ; + p++; +continue ; + } + +if (isdigit (*p)) { +char *endp; + index =strtoul (p, &endp,10 ) -1 ; +if (endp != p + sep) + index =-1 ; + } + +if (p[sep]) + p[sep++] ='\0' ; +if (index <0 || index >= items->nr) +printf (_ ("Huh (%s)?\n" ), p); +else { + res = index; +break ; + } + + p += sep; + } + +if (res !=LIST_AND_CHOOSE_ERROR ) +break ; } + +strbuf_release (&input); +return res; }struct adddel {
@@ -252,20 +343,48 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return 0 ; } +typedef int (*command_t )(struct add_i_state *s,const struct pathspec *ps, +struct string_list *files, +struct list_options *opts); + +static void print_command_item (int i,struct string_list_item *item, +void *print_command_item_data) +{ +printf (" %2d: %s" , i +1 , item->string); +} +int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL }; +struct list_and_choose_options main_loop_opts = { + {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, +N_ ("What now" ) + }; +struct { +const char *string; +command_t command; + } command_list[] = { + {"status" , run_status }, + }; +struct string_list commands =STRING_LIST_INIT_NODUP ; +struct print_file_item_data print_file_item_data = {"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT };struct list_options opts = { -NULL , print_file_item, &print_file_item_data +0 ,NULL , print_file_item, &print_file_item_data };struct strbuf header =STRBUF_INIT ;struct string_list files =STRING_LIST_INIT_DUP ; +ssize_t i;int res =0 ; +for (i =0 ; i <ARRAY_SIZE (command_list); i++) +string_list_append (&commands, command_list[i].string) + ->util = command_list[i].command; +init_add_i_state (&s, r); +strbuf_addstr (&header," " );strbuf_addf (&header, print_file_item_data.modified_fmt,_ ("staged" ),_ ("unstaged" ),_ ("path" ));
@@ -279,11 +398,25 @@ int run_add_i(struct repository *r, const struct pathspec *ps) res =run_status (&s, ps, &files, &opts); +for (;;) { + i =list_and_choose (&s, &commands, &main_loop_opts); +if (i ==LIST_AND_CHOOSE_QUIT ) { +printf (_ ("Bye.\n" )); + res =0 ; +break ; + } +if (i !=LIST_AND_CHOOSE_ERROR ) { +command_t command = commands.items[i].util; + res =command (&s, ps, &files, &opts); + } + } +string_list_clear (&files,1 );strbuf_release (&print_file_item_data.buf);strbuf_release (&print_file_item_data.index);strbuf_release (&print_file_item_data.worktree);strbuf_release (&header); +string_list_clear (&commands,0 );return res; }
Signature · 1 line
gitgitgadget
PATCH v7 7/9 built-in add -i: support `?` (prompt help)
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
With this change, we print out the same colored help text that the Perl-based git add -i prints in the main loop when question mark is entered.
--- add-interactive.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-)
add-interactive.c +21 -1
@@ -11,6 +11,7 @@ struct add_i_state {struct repository *r;int use_color;char header_color[COLOR_MAXLEN ]; +char help_color[COLOR_MAXLEN ]; }; static void init_color(struct repository *r, struct add_i_state *s,
@@ -43,6 +44,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r) s->use_color =want_color (s->use_color);init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD ); +init_color (r, s,"help" , s->help_color,GIT_COLOR_BOLD_RED ); }/*
@@ -210,6 +212,7 @@ struct list_and_choose_options {struct list_options list_opts;const char *prompt; +void (*print_help)(struct add_i_state *s); };#define LIST_AND_CHOOSE_ERROR (-1)
@@ -250,6 +253,11 @@ static ssize_t list_and_choose(struct add_i_state *s,if (!input.len)break ; +if (!strcmp (input.buf,"?" )) { + opts->print_help (s); +continue ; + } + p = input.buf;for (;;) {size_t sep =strcspn (p," \t\r\n," );
@@ -515,12 +523,24 @@ static void print_command_item(int i, struct string_list_item *item, item->string + util->prefix_length); } +static void command_prompt_help (struct add_i_state *s) +{ +const char *help_color = s->help_color; +color_fprintf_ln (stdout, help_color,"%s" ,_ ("Prompt help:" )); +color_fprintf_ln (stdout, help_color,"1 - %s" , +_ ("select a numbered item" )); +color_fprintf_ln (stdout, help_color,"foo - %s" , +_ ("select item based on unique prefix" )); +color_fprintf_ln (stdout, help_color," - %s" , +_ ("(empty) select nothing" )); +} +int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL };struct list_and_choose_options main_loop_opts = { {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, -N_ ("What now" ) +N_ ("What now" ), command_prompt_help };struct {const char *string;
Signature · 1 line
gitgitgadget
PATCH v7 2/9 diff: export diffstat interface
In reply to Johannes Schindelin via GitGitGadgetFrom: Daniel Ferreira <[email protected]>
Make the diffstat interface (namely, the diffstat_t struct and compute_diffstat) no longer be internal to diff.c and allow it to be used by other parts of git.
This is helpful for code that may want to easily extract information from files using the diff machinery, while flushing it differently from how the show_* functions used by diff_flush() do it. One example is the builtin implementation of git-add--interactive's status.
--- diff.c | 39 ++++++++++++++++----------------------- diff.h | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 23 deletions(-)
diff.c +16 -23
@@ -2495,22 +2495,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b) } } -struct diffstat_t { -int nr; -int alloc; -struct diffstat_file { -char *from_name; -char *name; -char *print_name; -const char *comments; -unsigned is_unmerged:1 ; -unsigned is_binary:1 ; -unsigned is_renamed:1 ; -unsigned is_interesting:1 ; -uintmax_t added, deleted; - } **files; -}; -static struct diffstat_file *diffstat_add (struct diffstat_t *diffstat,const char *name_a,const char *name_b)
@@ -3157,7 +3141,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *ogather_dirstat (options, &dir, changed,"" ,0 ); } -static void free_diffstat_info (struct diffstat_t *diffstat) +void free_diffstat_info (struct diffstat_t *diffstat) {int i;for (i =0 ; i < diffstat->nr; i++) {
@@ -6283,12 +6267,7 @@ void diff_flush(struct diff_options *options) dirstat_by_line) {struct diffstat_t diffstat; -memset (&diffstat,0 ,sizeof (struct diffstat_t )); -for (i =0 ; i < q->nr; i++) { -struct diff_filepair *p = q->queue[i]; -if (check_pair_status (p)) -diff_flush_stat (p, options, &diffstat); - } +compute_diffstat (options, &diffstat, q);if (output_format &DIFF_FORMAT_NUMSTAT )show_numstat (&diffstat, options);if (output_format &DIFF_FORMAT_DIFFSTAT )
@@ -6621,6 +6600,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)return ignored; } +void compute_diffstat (struct diff_options *options, +struct diffstat_t *diffstat, +struct diff_queue_struct *q) +{ +int i; + +memset (diffstat,0 ,sizeof (struct diffstat_t )); +for (i =0 ; i < q->nr; i++) { +struct diff_filepair *p = q->queue[i]; +if (check_pair_status (p)) +diff_flush_stat (p, options, diffstat); + } +} + void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const struct object_id *oid,
diff.h +20 -0
@@ -245,6 +245,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);void diff_emit_submodule_pipethrough (struct diff_options *o,const char *line,int len); +struct diffstat_t { +int nr; +int alloc; +struct diffstat_file { +char *from_name; +char *name; +char *print_name; +const char *comments; +unsigned is_unmerged:1 ; +unsigned is_binary:1 ; +unsigned is_renamed:1 ; +unsigned is_interesting:1 ; +uintmax_t added, deleted; + } **files; +}; +enum color_diff {DIFF_RESET =0 ,DIFF_CONTEXT =1 ,
@@ -334,6 +350,10 @@ void diff_change(struct diff_options *,struct diff_filepair *diff_unmerge (struct diff_options *,const char *path); +void compute_diffstat (struct diff_options *options,struct diffstat_t *diffstat, +struct diff_queue_struct *q); +void free_diffstat_info (struct diffstat_t *diffstat); +#define DIFF_SETUP_REVERSE 1#define DIFF_SETUP_USE_SIZE_CACHE 4
Signature · 1 line
gitgitgadget
PATCH v7 6/9 built-in add -i: show unique prefixes of the commands
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Just like in the Perl script git-add--interactive.perl, for each command a unique prefix is determined (if there exists any within the given parameters), and shown in the list, and accepted as a shortcut for the command.
To determine the unique prefixes, as well as to look up the command in question, we use a copy of the list and sort it.
While this might seem like overkill for a single command, it will make much more sense when all the commands are implemented, and when we reuse the same logic to present a list of files to edit, with convenient unique prefixes.
At the start of the development of this patch series, a dedicated data structure was introduced that imitated the Trie that the Perl version implements. However, this was deemed overkill, and we now simply sort the list before determining the length of the unique prefixes by looking at each item's neighbor. As a bonus, we now use the same sorted list to perform a binary search using the user-provided prefix as search key.
--- add-interactive.c | 188 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 177 insertions(+), 11 deletions(-)
add-interactive.c +177 -11
@@ -45,6 +45,132 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD ); } +/* +* A "prefix item list" is a list of items that are identified by a string, and +* a unique prefix (if any) is determined for each item. +* +* It is implemented in the form of a pair of `string_list`s, the first one +* duplicating the strings, with the `util` field pointing at a structure whose +* first field must be `size_t prefix_length`. +* +* That `prefix_length` field will be computed by `find_unique_prefixes()`; It +* will be set to zero if no valid, unique prefix could be found. +* +* The second `string_list` is called `sorted` and does _not_ duplicate the +* strings but simply reuses the first one's, with the `util` field pointing at +* the `string_item_list` of the first `string_list`. It will be populated and +* sorted by `find_unique_prefixes()`. +*/ +struct prefix_item_list { +struct string_list items; +struct string_list sorted; +size_t min_length, max_length; +}; +#define PREFIX_ITEM_LIST_INIT \ + { STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, 1, 4 } + +static void prefix_item_list_clear (struct prefix_item_list *list) +{ +string_list_clear (&list->items,1 ); +string_list_clear (&list->sorted,0 ); +} + +static void extend_prefix_length (struct string_list_item *p, +const char *other_string,size_t max_length) +{ +size_t *len = p->util; + +if (!*len ||memcmp (p->string, other_string, *len)) +return ; + +for (;;) { +char c = p->string[*len]; + +/* +* Is `p` a strict prefix of `other`? Or have we exhausted the +* maximal length of the prefix? Or is the current character a +* multi-byte UTF-8 one? If so, there is no valid, unique +* prefix. +*/ +if (!c || ++*len > max_length || !isascii (c)) { + *len =0 ; +break ; + } + +if (c != other_string[*len -1 ]) +break ; + } +} + +static void find_unique_prefixes (struct prefix_item_list *list) +{ +size_t i; + +if (list->sorted.nr == list->items.nr) +return ; + +string_list_clear (&list->sorted,0 ); +/* Avoid reallocating incrementally */ + list->sorted.items =xmalloc (st_mult (sizeof (*list->sorted.items), + list->items.nr)); + list->sorted.nr = list->sorted.alloc = list->items.nr; + +for (i =0 ; i < list->items.nr; i++) { + list->sorted.items[i].string = list->items.items[i].string; + list->sorted.items[i].util = list->items.items + i; + } + +string_list_sort (&list->sorted); + +for (i =0 ; i < list->sorted.nr; i++) { +struct string_list_item *sorted_item = list->sorted.items + i; +struct string_list_item *item = sorted_item->util; +size_t *len = item->util; + + *len =0 ; +while (*len < list->min_length) { +char c = item->string[(*len)++]; + +if (!c || !isascii (c)) { + *len =0 ; +break ; + } + } + +if (i >0 ) +extend_prefix_length (item, sorted_item[-1 ].string, + list->max_length); +if (i +1 < list->sorted.nr) +extend_prefix_length (item, sorted_item[1 ].string, + list->max_length); + } +} + +static ssize_t find_unique (const char *string,struct prefix_item_list *list) +{ +int index =string_list_find_insert_index (&list->sorted, string,1 ); +struct string_list_item *item; + +if (list->items.nr != list->sorted.nr) +BUG ("prefix_item_list in inconsistent state (%" PRIuMAX +" vs %" PRIuMAX")" , + (uintmax_t )list->items.nr, (uintmax_t )list->sorted.nr); + +if (index <0 ) + item = list->sorted.items[-1 - index].util; +else if (index >0 && +starts_with (list->sorted.items[index -1 ].string, string)) +return -1 ; +else if (index +1 < list->sorted.nr && +starts_with (list->sorted.items[index +1 ].string, string)) +return -1 ; +else if (index < list->sorted.nr) + item = list->sorted.items[index].util; +else +return -1 ; +return item - list->items.items; +} +struct list_options {int columns;const char *header;
@@ -95,18 +221,21 @@ struct list_and_choose_options {* If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF, * `LIST_AND_CHOOSE_QUIT` is returned. */ -static ssize_t list_and_choose (struct add_i_state *s,struct string_list *items, +static ssize_t list_and_choose (struct add_i_state *s, +struct prefix_item_list *items,struct list_and_choose_options *opts) {struct strbuf input =STRBUF_INIT ;ssize_t res =LIST_AND_CHOOSE_ERROR ; +find_unique_prefixes (items); +for (;;) {char *p;strbuf_reset (&input); -list (s, items, &opts->list_opts); +list (s, &items->items, &opts->list_opts);printf ("%s%s" , opts->prompt,"> " );fflush (stdout);
@@ -142,7 +271,10 @@ static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,if (p[sep]) p[sep++] ='\0' ; -if (index <0 || index >= items->nr) +if (index <0 ) + index =find_unique (p, items); + +if (index <0 || index >= items->items.nr)printf (_ ("Huh (%s)?\n" ), p);else { res = index;
@@ -308,6 +440,23 @@ static void render_adddel(struct strbuf *buf,strbuf_addstr (buf, no_changes); } +/* filters out prefixes which have special meaning to list_and_choose() */ +static int is_valid_prefix (const char *prefix,size_t prefix_len) +{ +return prefix_len && prefix && +/* +* We expect `prefix` to be NUL terminated, therefore this +* `strcspn()` call is okay, even if it might do much more +* work than strictly necessary. +*/ +strcspn (prefix," \t\r\n," ) >= prefix_len &&/* separators */ + *prefix !='-' &&/* deselection */ + !isdigit (*prefix) &&/* selection */ + (prefix_len !=1 || + (*prefix !='*' &&/* "all" wildcard */ + *prefix !='?' ));/* prompt help */ +} +struct print_file_item_data {const char *modified_fmt;struct strbuf buf, index, worktree;
@@ -347,10 +496,23 @@ typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,struct string_list *files, struct list_options *opts); +struct command_item { +size_t prefix_length; +command_t command; +}; +static void print_command_item (int i,struct string_list_item *item,void *print_command_item_data) { -printf (" %2d: %s" , i +1 , item->string); +struct command_item *util = item->util; + +if (!util->prefix_length || + !is_valid_prefix (item->string, util->prefix_length)) +printf (" %2d: %s" , i +1 , item->string); +else +printf (" %2d: [%.*s]%s" , i +1 , + (int )util->prefix_length, item->string, + item->string + util->prefix_length); }int run_add_i (struct repository *r,const struct pathspec *ps)
@@ -366,7 +528,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps) } command_list[] = { { "status", run_status }, }; -struct string_list commands =STRING_LIST_INIT_NODUP ; +struct prefix_item_list commands =PREFIX_ITEM_LIST_INIT ;struct print_file_item_data print_file_item_data = { "%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
@@ -379,9 +541,12 @@ int run_add_i(struct repository *r, const struct pathspec *ps)ssize_t i;int res =0 ; -for (i =0 ; i <ARRAY_SIZE (command_list); i++) -string_list_append (&commands, command_list[i].string) - ->util = command_list[i].command; +for (i =0 ; i <ARRAY_SIZE (command_list); i++) { +struct command_item *util =xcalloc (sizeof (*util),1 ); + util->command = command_list[i].command; +string_list_append (&commands.items, command_list[i].string) + ->util = util; + }init_add_i_state (&s, r);
@@ -406,8 +571,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)break ; }if (i !=LIST_AND_CHOOSE_ERROR ) { -command_t command = commands.items[i].util; - res =command (&s, ps, &files, &opts); +struct command_item *util = + commands.items.items[i].util; + res = util->command (&s, ps, &files, &opts); } }
@@ -416,7 +582,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)strbuf_release (&print_file_item_data.index);strbuf_release (&print_file_item_data.worktree);strbuf_release (&header); -string_list_clear (&commands,0 ); +prefix_item_list_clear (&commands);return res; }
Signature · 1 line
gitgitgadget
PATCH v7 3/9 built-in add -i: implement the `status` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Daniel Ferreira <[email protected]>
This implements the status command of git add -i. The data structures introduced in this commit will be extended later, as needed.
At this point, we re-implement only part of the list_and_choose() function of the Perl script git-add--interactive.perl and call it list(). It does not yet color anything, or do columns, or allow user input.
Over the course of the next commits, we will introduce a list_and_choose() function that uses list() to display the list of options and let the user choose one or more of the displayed items. This will be used to implement the main loop of the built-in git add -i, at which point the new status command can actually be used.
--- add-interactive.c | 251 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 250 insertions(+), 1 deletion(-)
add-interactive.c +250 -1
@@ -1,7 +1,256 @@#include "cache.h" #include "add-interactive.h" +#include "diffcore.h" +#include "revision.h" +#include "refs.h" +#include "string-list.h" + +struct add_i_state { +struct repository *r; +}; + +static void init_add_i_state (struct add_i_state *s,struct repository *r) +{ + s->r = r; +} + +struct list_options { +const char *header; +void (*print_item)(int i,struct string_list_item *item,void *print_item_data); +void *print_item_data; +}; + +static void list (struct string_list *list,struct list_options *opts) +{ +int i; + +if (!list->nr) +return ; + +if (opts->header) +printf ("%s\n" , opts->header); + +for (i =0 ; i < list->nr; i++) { + opts->print_item (i, list->items + i, opts->print_item_data); +putchar ('\n' ); + } +} + +struct adddel { +uintmax_t add, del; +unsigned seen:1 , binary:1 ; +}; + +struct file_item { +struct adddel index, worktree; +}; + +static void add_file_item (struct string_list *files,const char *name) +{ +struct file_item *item =xcalloc (sizeof (*item),1 ); + +string_list_append (files, name)->util = item; +} + +struct pathname_entry { +struct hashmap_entry ent; +const char *name; +struct file_item *item; +}; + +static int pathname_entry_cmp (const void *unused_cmp_data, +const struct hashmap_entry *he1, +const struct hashmap_entry *he2, +const void *name) +{ +const struct pathname_entry *e1 = + container_of(he1, const struct pathname_entry, ent); +const struct pathname_entry *e2 = + container_of(he2, const struct pathname_entry, ent); + +return strcmp (e1->name, name ? (const char *)name : e2->name); +} + +struct collection_status { +enum {FROM_WORKTREE =0 ,FROM_INDEX =1 } phase; + +const char *reference; + +struct string_list *files; +struct hashmap file_map; +}; + +static void collect_changes_cb (struct diff_queue_struct *q, +struct diff_options *options, +void *data) +{ +struct collection_status *s = data; +struct diffstat_t stat = {0 }; +int i; + +if (!q->nr) +return ; + +compute_diffstat (options, &stat, q); + +for (i =0 ; i < stat.nr; i++) { +const char *name = stat.files[i]->name; +int hash =strhash (name); +struct pathname_entry *entry; +struct file_item *file_item; +struct adddel *adddel; + + entry =hashmap_get_entry_from_hash (&s->file_map, hash, name, + struct pathname_entry, ent); +if (!entry) { +add_file_item (s->files, name); + + entry =xcalloc (sizeof (*entry),1 ); +hashmap_entry_init (&entry->ent, hash); + entry->name = s->files->items[s->files->nr -1 ].string; + entry->item = s->files->items[s->files->nr -1 ].util; +hashmap_add (&s->file_map, &entry->ent); + } + + file_item = entry->item; + adddel = s->phase ==FROM_INDEX ? + &file_item->index : &file_item->worktree; + adddel->seen =1 ; + adddel->add = stat.files[i]->added; + adddel->del = stat.files[i]->deleted; +if (stat.files[i]->is_binary) + adddel->binary =1 ; + } +free_diffstat_info (&stat); +} + +static int get_modified_files (struct repository *r,struct string_list *files, +const struct pathspec *ps) +{ +struct object_id head_oid; +int is_initial = !resolve_ref_unsafe ("HEAD" ,RESOLVE_REF_READING , + &head_oid,NULL ); +struct collection_status s = {FROM_WORKTREE }; + +if (discard_index (r->index) <0 || +repo_read_index_preload (r, ps,0 ) <0 ) +return error (_ ("could not read index" )); + +string_list_clear (files,1 ); + s.files = files; +hashmap_init (&s.file_map, pathname_entry_cmp,NULL ,0 ); + +for (s.phase =FROM_WORKTREE ; s.phase <=FROM_INDEX ; s.phase++) { +struct rev_info rev; +struct setup_revision_opt opt = {0 }; + + opt.def = is_initial ? +empty_tree_oid_hex () :oid_to_hex (&head_oid); + +init_revisions (&rev,NULL ); +setup_revisions (0 ,NULL , &rev, &opt); + + rev.diffopt.output_format =DIFF_FORMAT_CALLBACK ; + rev.diffopt.format_callback = collect_changes_cb; + rev.diffopt.format_callback_data = &s; + +if (ps) +copy_pathspec (&rev.prune_data, ps); + +if (s.phase ==FROM_INDEX ) +run_diff_index (&rev,1 ); +else { + rev.diffopt.flags.ignore_dirty_submodules =1 ; +run_diff_files (&rev,0 ); + } + } + hashmap_free_entries(&s.file_map, struct pathname_entry, ent); + +/* While the diffs are ordered already, we ran *two* diffs... */ +string_list_sort (files); + +return 0 ; +} + +static void render_adddel (struct strbuf *buf, +struct adddel *ad,const char *no_changes) +{ +if (ad->binary) +strbuf_addstr (buf,_ ("binary" )); +else if (ad->seen) +strbuf_addf (buf,"+%" PRIuMAX"/-%" PRIuMAX, + (uintmax_t )ad->add, (uintmax_t )ad->del); +else +strbuf_addstr (buf, no_changes); +} + +struct print_file_item_data { +const char *modified_fmt; +struct strbuf buf, index, worktree; +}; + +static void print_file_item (int i,struct string_list_item *item, +void *print_file_item_data) +{ +struct file_item *c = item->util; +struct print_file_item_data *d = print_file_item_data; + +strbuf_reset (&d->index); +strbuf_reset (&d->worktree); +strbuf_reset (&d->buf); + +render_adddel (&d->worktree, &c->worktree,_ ("nothing" )); +render_adddel (&d->index, &c->index,_ ("unchanged" )); +strbuf_addf (&d->buf, d->modified_fmt, + d->index.buf, d->worktree.buf, item->string); + +printf (" %2d: %s" , i +1 , d->buf.buf); +} + +static int run_status (struct add_i_state *s,const struct pathspec *ps, +struct string_list *files,struct list_options *opts) +{ +if (get_modified_files (s->r, files, ps) <0 ) +return -1 ; + +list (files, opts); +putchar ('\n' ); + +return 0 ; +}int run_add_i (struct repository *r,const struct pathspec *ps) { -die (_ ("No commands are available in the built-in `git add -i` yet!" )); +struct add_i_state s = {NULL }; +struct print_file_item_data print_file_item_data = { +"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT + }; +struct list_options opts = { +NULL , print_file_item, &print_file_item_data + }; +struct strbuf header =STRBUF_INIT ; +struct string_list files =STRING_LIST_INIT_DUP ; +int res =0 ; + +init_add_i_state (&s, r); +strbuf_addstr (&header," " ); +strbuf_addf (&header, print_file_item_data.modified_fmt, +_ ("staged" ),_ ("unstaged" ),_ ("path" )); + opts.header = header.buf; + +if (discard_index (r->index) <0 || +repo_read_index (r) <0 || +repo_refresh_and_write_index (r,REFRESH_QUIET ,0 ,1 , +NULL ,NULL ,NULL ) <0 ) +warning (_ ("could not refresh index" )); + + res =run_status (&s, ps, &files, &opts); + +string_list_clear (&files,1 ); +strbuf_release (&print_file_item_data.buf); +strbuf_release (&print_file_item_data.index); +strbuf_release (&print_file_item_data.worktree); +strbuf_release (&header); + +return res; }
Signature · 1 line
gitgitgadget
PATCH v7 8/9 built-in add -i: use color in the main loop
In reply to Johannes Schindelin via GitGitGadgetFrom: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <[email protected]>
The error messages as well as the unique prefixes are colored in git add -i by default; We need to do the same in the built-in version.
--- add-interactive.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-)
add-interactive.c +29 -6
@@ -12,6 +12,9 @@ struct add_i_state {int use_color;char header_color[COLOR_MAXLEN ];char help_color[COLOR_MAXLEN ]; +char prompt_color[COLOR_MAXLEN ]; +char error_color[COLOR_MAXLEN ]; +char reset_color[COLOR_MAXLEN ]; }; static void init_color(struct repository *r, struct add_i_state *s,
@@ -45,6 +48,9 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r)init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD );init_color (r, s,"help" , s->help_color,GIT_COLOR_BOLD_RED ); +init_color (r, s,"prompt" , s->prompt_color,GIT_COLOR_BOLD_BLUE ); +init_color (r, s,"error" , s->error_color,GIT_COLOR_BOLD_RED ); +init_color (r, s,"reset" , s->reset_color,GIT_COLOR_RESET ); }/*
@@ -240,7 +246,8 @@ static ssize_t list_and_choose(struct add_i_state *s,list (s, &items->items, &opts->list_opts); -printf ("%s%s" , opts->prompt,"> " ); +color_fprintf (stdout, s->prompt_color,"%s" , opts->prompt); +fputs ("> " , stdout);fflush (stdout);if (strbuf_getline (&input, stdin) ==EOF ) {
@@ -283,7 +290,8 @@ static ssize_t list_and_choose(struct add_i_state *s, index =find_unique (p, items);if (index <0 || index >= items->items.nr) -printf (_ ("Huh (%s)?\n" ), p); +color_fprintf_ln (stdout, s->error_color, +_ ("Huh (%s)?" ), p);else { res = index;break ;
@@ -509,18 +517,23 @@ struct command_item {command_t command; }; +struct print_command_item_data { +const char *color, *reset; +}; +static void print_command_item (int i,struct string_list_item *item,void *print_command_item_data) { +struct print_command_item_data *d = print_command_item_data;struct command_item *util = item->util;if (!util->prefix_length || !is_valid_prefix (item->string, util->prefix_length))printf (" %2d: %s" , i +1 , item->string);else -printf (" %2d: [%.*s]%s" , i +1 , - (int )util->prefix_length, item->string, - item->string + util->prefix_length); +printf (" %2d: %s%.*s%s%s" , i +1 , + d->color, (int )util->prefix_length, item->string, + d->reset, item->string + util->prefix_length); }static void command_prompt_help (struct add_i_state *s)
@@ -538,8 +551,9 @@ static void command_prompt_help(struct add_i_state *s)int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL }; +struct print_command_item_data data = {"[" ,"]" };struct list_and_choose_options main_loop_opts = { - {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, + {4 ,N_ ("*** Commands ***" ), print_command_item, &data },N_ ("What now" ), command_prompt_help };struct {
@@ -570,6 +584,15 @@ int run_add_i(struct repository *r, const struct pathspec *ps)init_add_i_state (&s, r); +/* +* When color was asked for, use the prompt color for +* highlighting, otherwise use square brackets. +*/ +if (s.use_color) { + data.color = s.prompt_color; + data.reset = s.reset_color; + } +strbuf_addstr (&header," " );strbuf_addf (&header, print_file_item_data.modified_fmt,_ ("staged" ),_ ("unstaged" ),_ ("path" ));
Signature · 1 line
gitgitgadget
PATCH v7 9/9 built-in add -i: implement the `help` command
In reply to Johannes Schindelin via GitGitGadgetFrom: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= <[email protected]>
This imitates the code to show the help text from the Perl script git-add--interactive.perl in the built-in version.
To make sure that it renders exactly like the Perl version of git add -i, we also add a test case for that to t3701-add-interactive.sh.
--- add-interactive.c | 21 +++++++++++++++++++++ t/t3701-add-interactive.sh | 25 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+)
add-interactive.c +21 -0
@@ -508,6 +508,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return 0 ; } +static int run_help (struct add_i_state *s,const struct pathspec *unused_ps, +struct string_list *unused_files, +struct list_options *unused_opts) +{ +color_fprintf_ln (stdout, s->help_color,"status - %s" , +_ ("show paths with changes" )); +color_fprintf_ln (stdout, s->help_color,"update - %s" , +_ ("add working tree state to the staged set of changes" )); +color_fprintf_ln (stdout, s->help_color,"revert - %s" , +_ ("revert staged set of changes back to the HEAD version" )); +color_fprintf_ln (stdout, s->help_color,"patch - %s" , +_ ("pick hunks and update selectively" )); +color_fprintf_ln (stdout, s->help_color,"diff - %s" , +_ ("view diff between HEAD and index" )); +color_fprintf_ln (stdout, s->help_color,"add untracked - %s" , +_ ("add contents of untracked files to the staged set of changes" )); + +return 0 ; +} +typedef int (*command_t )(struct add_i_state *s,const struct pathspec *ps,struct string_list *files,struct list_options *opts);
@@ -561,6 +581,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)command_t command; } command_list[] = { { "status", run_status }, + { "help", run_help }, }; struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
t/t3701-add-interactive.sh +25 -0
@@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' ' test_write_lines a b a b a a b a b a >expect && test_cmp expect a ' + +test_expect_success 'show help from add--helper' ' + git reset --hard && + cat >expect <<-EOF && + + <BOLD>*** Commands ***<RESET> + 1: <BOLD;BLUE>s<RESET>tatus 2: <BOLD;BLUE>u<RESET>pdate 3: <BOLD;BLUE>r<RESET>evert 4: <BOLD;BLUE>a<RESET>dd untracked + 5: <BOLD;BLUE>p<RESET>atch 6: <BOLD;BLUE>d<RESET>iff 7: <BOLD;BLUE>q<RESET>uit 8: <BOLD;BLUE>h<RESET>elp + <BOLD;BLUE>What now<RESET>> <BOLD;RED>status - show paths with changes<RESET> + <BOLD;RED>update - add working tree state to the staged set of changes<RESET> + <BOLD;RED>revert - revert staged set of changes back to the HEAD version<RESET> + <BOLD;RED>patch - pick hunks and update selectively<RESET> + <BOLD;RED>diff - view diff between HEAD and index<RESET> + <BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET> + <BOLD>*** Commands ***<RESET> + 1: <BOLD;BLUE>s<RESET>tatus 2: <BOLD;BLUE>u<RESET>pdate 3: <BOLD;BLUE>r<RESET>evert 4: <BOLD;BLUE>a<RESET>dd untracked + 5: <BOLD;BLUE>p<RESET>atch 6: <BOLD;BLUE>d<RESET>iff 7: <BOLD;BLUE>q<RESET>uit 8: <BOLD;BLUE>h<RESET>elp + <BOLD;BLUE>What now<RESET>>$SP + Bye. + EOF + test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored && + test_decode_color <actual.colored >actual && + test_i18ncmp expect actual +' + test_done
Signature · 1 line
gitgitgadget