git add -i: add a rudimentary version in C (supporting only status and help so far)
Subsystems (2)
cover git add -i: add a rudimentary version in C (supporting only status and help so far)
This is the first leg on the long journey to a fully built-in git add -i (next up: parts 2 [https://github.com/gitgitgadget/git/pull/171], 3
[https://github.com/gitgitgadget/git/pull/172], 4
[https://github.com/gitgitgadget/git/pull/173], 5
[https://github.com/gitgitgadget/git/pull/174], and 6
[https://github.com/gitgitgadget/git/pull/175]).
It reflects the part that was submitted a couple of times (see
https://github.com/gitgitgadget/git/pull/103) during the Outreachy project by Slavica Ðukic that continued the journey based on an initial patch series by Daniel Ferreira.
This part only implements the status and the help part, like Slavica's last iteration did, in the interest of making the review remotely more reviewable. I fear that this attempt of making it a bit more reviewable is pretty futile, as so many things changed. So I will ask the reviewers for
forgiveness: please be kind, and give this sort of a fresh review.
I threw in a couple of major changes on top of that iteration, though:
* The original plan was to add a helper (git add--helper) that takes over more and more responsibility from the Perl script over the course of the conversion.
This plan is no longer in effect, as I encountered a serious problem with
that: the MSYS2 runtime used by the Perl interpreter which Git for Windows employs to run git add -i has a curious bug (that is safely outside the purview of this here patch series) where it fails to read from standard input after it spawned a non-MSYS2 program (such as add--helper) that reads from standard input. So that wouldn't work, but quit the interactive add as soon as even one interactive command had been handled by the helper. To keep my git add -i in a working state, I therefore adopted a different strategy:
Just like git difftool was converted by starting with a built-in that did nothing but handing off to the scripted version, guarded by the (opt-in) difftool.useBuiltin config setting, I start this patch series by a built-in add -i that does nothing else but state that it is not implemented yet, guarded by the (opt-in) add.interactive.useBuiltin config setting.
In contrast to the git difftool situation, it is quite a bit easier here, as we do not even have to rename the script to git-legacy-add--interactive.perl: the add--interactive command is an implementation detail that users are not even supposed to know about. Therefore, we can implement that road fork between the built-in and the scripted version in builtin/add.c, i.e. in the user-facing git add command.
This will also naturally help with the transition to a fully built-in git add -i/git add -p, as we saw with the built-in git rebase how important it is for end users to have an escape hatch (and for that reason, tried our best to provide the same with the built-in git stash).
* The help command was actually not hooked up in git add -i in Slavica's last iteration, but was only available as a special option of the git add--helper command. As that command no longer exists, I kind of had to implement some way to let the built-in git add -i show the help text.
* The main loop of git add -i (i.e. the thing that lets you choose status or help) is now implemented (but only lists status and help, of course), as it makes use of that feature that took the main chunk of the Outreachy
project: the function to determine unique prefixes of a list of strings.
* Speaking of the unique prefixes: the functionality to determine those is now encapsulated in the prefix-map.c file, and I also added a regression test.
* Speaking of the tests: I also implemented support for the environment variable GIT_TEST_ADD_I_USE_BUILTIN: by setting it, the test suite can be forced to use the built-in, or the Perl script, version of git add -i. Needless to say: by the end of this patch series, running the test suite with GIT_TEST_ADD_I_USE_BUILTIN=true will still result in a ton of test failures due to not-yet-implemented commands, but it will also demonstrate what already works.
* Since the main loop starts not only by showing the status, but refreshes the index before that, I added that, and I actually refactored that code into a new function (repo_refresh_and_write_index()), as it will be used a couple of times by the end of the complete conversion of git add -i into a built-in command.
Daniel Ferreira (2):
diff: export diffstat interface
built-in add -i: implement the status command
Johannes Schindelin (6):
Start to implement a built-in version of git add --interactive built-in add -i: refresh the index before running status built-in add -i: color the header in the status command built-in add -i: implement the main loop
built-in add -i: support ? (prompt help)
built-in add -i: implement the help command
Slavica Djukic (3):
Add a function to determine unique prefixes for a list of strings built-in add -i: show unique prefixes of the commands built-in add -i: use color in the main loop
Documentation/config/add.txt | 5 + Makefile | 3 + add-interactive.c | 560 +++++++++++++++++++++++++++++++++++ add-interactive.h | 10 + builtin/add.c | 16 +- diff.c | 37 +-- diff.h | 19 ++ prefix-map.c | 111 +++++++ prefix-map.h | 40 +++ repository.c | 19 ++ repository.h | 7 + t/README | 4 + t/helper/test-prefix-map.c | 58 ++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0016-prefix-map.sh | 10 + t/t3701-add-interactive.sh | 24 ++ 17 files changed, 902 insertions(+), 23 deletions(-) create mode 100644 add-interactive.c create mode 100644 add-interactive.h create mode 100644 prefix-map.c create mode 100644 prefix-map.h create mode 100644 t/helper/test-prefix-map.c create mode 100755 t/t0016-prefix-map.sh
base-commit: 8104ec994ea3849a968b4667d072fedd1e688642
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/170
Signature · 1 line
gitgitgadget
PATCH 1/11 Start to implement a built-in version of `git add --interactive`
From: Johannes Schindelin <[email protected]>
This is hardly the first conversion of a Git command that is implemented as a script to a built-in. So far, the most successful strategy for such conversions has been to add a built-in helper and call that for more and more functionality from the script, as more and more parts are converted.
With the interactive add, we choose a different strategy. The sole reason for this is that on Windows (where such a conversion has the most benefits in terms of speed and robustness) we face the very specific problem that a system() call in Perl seems to close stdin in the parent process when the spawned process consumes even one character from stdin. And that just does not work for us here, as it would stop the main loop as soon as any interactive command was performed by the helper. Which is almost all of the commands in git add -i.
It is almost as if Perl told us once again that it does not want us to use it on Windows.
Instead, we follow the opposite route where we start with a bare-bones version of the built-in interactive add, guarded by the new add.interactive.useBuiltin config variable, and then add more and more functionality to it, until it is feature complete.
At this point, the built-in version of git add -i only states that it cannot do anything yet ;-)
--- Documentation/config/add.txt | 5 +++++ Makefile | 1 + add-interactive.c | 13 +++++++++++++ add-interactive.h | 10 ++++++++++ builtin/add.c | 16 +++++++++++++++- t/README | 4 ++++ 6 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 add-interactive.c create mode 100644 add-interactive.h
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
@@ -848,6 +848,7 @@ LIB_H = $(shell $(FIND) . \ -name '*.h' -print) LIB_OBJS += abspath.o +LIB_OBJS += add-interactive.o LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.o
add-interactive.c new file +13 -0
@@ -0,0 +1,13 @@ +#include "cache.h" +#include "add-interactive.h" +#include "config.h" + +int add_i_config (const char *var,const char *value,void *cb) +{ +return git_default_config (var, value, cb); +} + +int run_add_i (struct repository *r,const struct pathspec *ps) +{ +die (_ ("No commands are available in the built-in `git add -i` yet!" )); +}
add-interactive.h new file +10 -0
@@ -0,0 +1,10 @@ +#ifndef ADD_INTERACTIVE_H +#define ADD_INTERACTIVE_H + +int add_i_config (const char *var,const char *value,void *cb); + +struct repository ; +struct pathspec ; +int run_add_i (struct repository *r,const struct pathspec *ps); + +#endif
builtin/add.c +15 -1
@@ -20,6 +20,7 @@#include "bulk-checkin.h" #include "argv-array.h" #include "submodule.h" +#include "add-interactive.h" static const char *const builtin_add_usage[] = {N_ ("git add [<options>] [--] <pathspec>..." ),
@@ -28,6 +29,7 @@ static const char * const builtin_add_usage[] = {static int patch_interactive, add_interactive, edit_interactive;static int take_worktree_changes;static int add_renormalize; +static int use_builtin_add_i;struct update_callback_data {int flags;
@@ -186,6 +188,9 @@ int run_add_interactive(const char *revision, const char *patch_mode,int status, i;struct argv_array argv =ARGV_ARRAY_INIT ; +if (use_builtin_add_i && !patch_mode) +return !!run_add_i (the_repository, pathspec); +argv_array_push (&argv,"add--interactive" );if (patch_mode)argv_array_push (&argv, patch_mode);
@@ -319,7 +324,12 @@ static int add_config(const char *var, const char *value, void *cb) ignore_add_errors =git_config_bool (var, value);return 0 ; } -return git_default_config (var, value, cb); +if (!strcmp (var,"add.interactive.usebuiltin" )) { + use_builtin_add_i =git_config_bool (var, value); +return 0 ; + } + +return add_i_config (var, value, cb); } static const char embedded_advice[] = N_(
@@ -394,8 +404,12 @@ int cmd_add(int argc, const char **argv, const char *prefix)int require_pathspec;char *seen =NULL ;struct lock_file lock_file =LOCK_INIT ; +int use_builtin_add_i_env = +git_env_bool ("GIT_TEST_ADD_I_USE_BUILTIN" ,-1 );git_config (add_config,NULL ); +if (use_builtin_add_i_env >=0 ) + use_builtin_add_i = use_builtin_add_i_env; argc =parse_options (argc, argv, prefix, builtin_add_options, builtin_add_usage,PARSE_OPT_KEEP_ARGV0 );
t/README +4 -0
@@ -383,6 +383,10 @@ GIT_TEST_REBASE_USE_BUILTIN=<boolean>, when false, disables the builtin version of git-rebase. See 'rebase.useBuiltin' in git-config(1). +GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the +builtin version of git add -i. See 'add.interactive.useBuiltin' in +git-config(1). + GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading of the index for the whole test suite by bypassing the default number of cache entries and thread minimums. Setting this to 1 will make the
Signature · 1 line
gitgitgadget
PATCH 2/11 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 | 37 +++++++++++++++---------------------- diff.h | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+), 22 deletions(-)
diff.c +15 -22
@@ -2489,22 +2489,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b) } } -struct diffstat_t { -int nr; -int alloc; -struct diffstat_file { -char *from_name; -char *name; -char *print_name; -const char *comments; -unsigned is_unmerged:1 ; -unsigned is_binary:1 ; -unsigned is_renamed:1 ; -unsigned is_interesting:1 ; -uintmax_t added, deleted; - } **files; -}; -static struct diffstat_file *diffstat_add (struct diffstat_t *diffstat,const char *name_a,const char *name_b)
@@ -6001,12 +5985,7 @@ void diff_flush(struct diff_options *options) dirstat_by_line) {struct diffstat_t diffstat; -memset (&diffstat,0 ,sizeof (struct diffstat_t )); -for (i =0 ; i < q->nr; i++) { -struct diff_filepair *p = q->queue[i]; -if (check_pair_status (p)) -diff_flush_stat (p, options, &diffstat); - } +compute_diffstat (options, &diffstat, q);if (output_format &DIFF_FORMAT_NUMSTAT )show_numstat (&diffstat, options);if (output_format &DIFF_FORMAT_DIFFSTAT )
@@ -6306,6 +6285,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)return ignored; } +void compute_diffstat (struct diff_options *options, +struct diffstat_t *diffstat, +struct diff_queue_struct *q) +{ +int i; + +memset (diffstat,0 ,sizeof (struct diffstat_t )); +for (i =0 ; i < q->nr; i++) { +struct diff_filepair *p = q->queue[i]; +if (check_pair_status (p)) +diff_flush_stat (p, options, diffstat); + } +} + void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const struct object_id *oid,
diff.h +19 -0
@@ -240,6 +240,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);void diff_emit_submodule_pipethrough (struct diff_options *o,const char *line,int len); +struct diffstat_t { +int nr; +int alloc; +struct diffstat_file { +char *from_name; +char *name; +char *print_name; +const char *comments; +unsigned is_unmerged:1 ; +unsigned is_binary:1 ; +unsigned is_renamed:1 ; +unsigned is_interesting:1 ; +uintmax_t added, deleted; + } **files; +}; +enum color_diff {DIFF_RESET =0 ,DIFF_CONTEXT =1 ,
@@ -328,6 +344,9 @@ void diff_change(struct diff_options *,struct diff_filepair *diff_unmerge (struct diff_options *,const char *path); +void compute_diffstat (struct diff_options *options,struct diffstat_t *diffstat, +struct diff_queue_struct *q); +#define DIFF_SETUP_REVERSE 1#define DIFF_SETUP_USE_SIZE_CACHE 4
Signature · 1 line
gitgitgadget
PATCH 3/11 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 as needed later.
At this point, we re-implement only part of the list_and_choose() function of the Perl script git-add--interactive.perl and call it list(). It does not yet color anything, or do columns, or allow user input.
Over the course of the next commits, we will introduce a list_and_choose() function that uses list() to display the list of options and let the user choose one or more of the displayed items. This will be used to implement the main loop of the built-in git add -i, at which point the new status command can actually be used.
Note that we pass the list of items as a struct item ** as opposed to a struct item *, to allow for the actual items to contain much more information than merely the name.
--- add-interactive.c | 265 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 264 insertions(+), 1 deletion(-)
add-interactive.c +264 -1
@@ -1,13 +1,276 @@#include "cache.h" #include "add-interactive.h" #include "config.h" +#include "diffcore.h" +#include "revision.h" +#include "refs.h" int add_i_config (const char *var,const char *value,void *cb) {return git_default_config (var, value, cb); } +struct item { +const char *name; +}; + +struct list_options { +const char *header; +void (*print_item)(int i,struct item *item,void *print_item_data); +void *print_item_data; +}; + +static void list (struct item **list,size_t nr,struct list_options *opts) +{ +int i; + +if (!nr) +return ; + +if (opts->header) +printf ("%s\n" , opts->header); + +for (i =0 ; i < nr; i++) { + opts->print_item (i, list[i], opts->print_item_data); +putchar ('\n' ); + } +} + +struct adddel { +uintmax_t add, del; +unsigned seen:1 , binary:1 ; +}; + +struct file_list { +struct file_item { +struct item item; +struct adddel index, worktree; + } **file; +size_t nr, alloc; +}; + +static void add_file_item (struct file_list *list,const char *name) +{ +struct file_item *item; + +FLEXPTR_ALLOC_STR (item, item.name, name); + +ALLOC_GROW (list->file, list->nr +1 , list->alloc); + list->file[list->nr++] = item; +} + +static void reset_file_list (struct file_list *list) +{ +size_t i; + +for (i =0 ; i < list->nr; i++) +free (list->file[i]); + list->nr =0 ; +} + +static void release_file_list (struct file_list *list) +{ +reset_file_list (list); +FREE_AND_NULL (list->file); + list->alloc =0 ; +} + +static int file_item_cmp (const void *a,const void *b) +{ +const struct file_item *const *f1 = a; +const struct file_item *const *f2 = b; + +return strcmp ((*f1)->item.name, (*f2)->item.name); +} + +struct pathname_entry { +struct hashmap_entry ent; +size_t index; +char pathname[FLEX_ARRAY ]; +}; + +static int pathname_entry_cmp (const void *unused_cmp_data, +const void *entry,const void *entry_or_key, +const void *pathname) +{ +const struct pathname_entry *e1 = entry, *e2 = entry_or_key; + +return strcmp (e1->pathname, + pathname ? (const char *)pathname : e2->pathname); +} + +struct collection_status { +enum {FROM_WORKTREE =0 ,FROM_INDEX =1 } phase; + +const char *reference; + +struct file_list *list; +struct hashmap file_map; +}; + +static void collect_changes_cb (struct diff_queue_struct *q, +struct diff_options *options, +void *data) +{ +struct collection_status *s = data; +struct diffstat_t stat = {0 }; +int i; + +if (!q->nr) +return ; + +compute_diffstat (options, &stat, q); + +for (i =0 ; i < stat.nr; i++) { +const char *name = stat.files[i]->name; +int hash =strhash (name); +struct pathname_entry *entry; +size_t file_index; +struct file_item *file; +struct adddel *adddel; + + entry =hashmap_get_from_hash (&s->file_map, hash, name); +if (entry) + file_index = entry->index; +else { +FLEX_ALLOC_STR (entry, pathname, name); +hashmap_entry_init (entry, hash); + entry->index = file_index = s->list->nr; +hashmap_add (&s->file_map, entry); + +add_file_item (s->list, name); + } + file = s->list->file[file_index]; + + adddel = s->phase ==FROM_INDEX ? &file->index : &file->worktree; + adddel->seen =1 ; + adddel->add = stat.files[i]->added; + adddel->del = stat.files[i]->deleted; +if (stat.files[i]->is_binary) + adddel->binary =1 ; + } +} + +static int get_modified_files (struct repository *r,struct file_list *list, +const struct pathspec *ps) +{ +struct object_id head_oid; +int is_initial = !resolve_ref_unsafe ("HEAD" ,RESOLVE_REF_READING , + &head_oid,NULL ); +struct collection_status s = {FROM_WORKTREE }; + +if (repo_read_index_preload (r, ps,0 ) <0 ) +return error (_ ("could not read index" )); + + s.list = list; +hashmap_init (&s.file_map, pathname_entry_cmp,NULL ,0 ); + +for (s.phase =FROM_WORKTREE ; s.phase <=FROM_INDEX ; s.phase++) { +struct rev_info rev; +struct setup_revision_opt opt = {0 }; + + opt.def = is_initial ? +empty_tree_oid_hex () :oid_to_hex (&head_oid); + +init_revisions (&rev,NULL ); +setup_revisions (0 ,NULL , &rev, &opt); + + rev.diffopt.output_format =DIFF_FORMAT_CALLBACK ; + rev.diffopt.format_callback = collect_changes_cb; + rev.diffopt.format_callback_data = &s; + +if (ps) +copy_pathspec (&rev.prune_data, ps); + +if (s.phase ==FROM_INDEX ) +run_diff_index (&rev,1 ); +else { + rev.diffopt.flags.ignore_dirty_submodules =1 ; +run_diff_files (&rev,0 ); + } + } +hashmap_free (&s.file_map,1 ); + +/* While the diffs are ordered already, we ran *two* diffs... */ +QSORT (list->file, list->nr, file_item_cmp); + +return 0 ; +} + +static void populate_wi_changes (struct strbuf *buf, +struct adddel *ad,const char *no_changes) +{ +if (ad->binary) +strbuf_addstr (buf,_ ("binary" )); +else if (ad->seen) +strbuf_addf (buf,"+%" PRIuMAX"/-%" PRIuMAX, + (uintmax_t )ad->add, (uintmax_t )ad->del); +else +strbuf_addstr (buf, no_changes); +} + +struct print_file_item_data { +const char *modified_fmt; +struct strbuf buf, index, worktree; +}; + +static void print_file_item (int i,struct item *item, +void *print_file_item_data) +{ +struct file_item *c = (struct file_item *)item; +struct print_file_item_data *d = print_file_item_data; + +strbuf_reset (&d->index); +strbuf_reset (&d->worktree); +strbuf_reset (&d->buf); + +populate_wi_changes (&d->worktree, &c->worktree,_ ("nothing" )); +populate_wi_changes (&d->index, &c->index,_ ("unchanged" )); +strbuf_addf (&d->buf, d->modified_fmt, + d->index.buf, d->worktree.buf, item->name); + +printf (" %2d: %s" , i +1 , d->buf.buf); +} + +static int run_status (struct repository *r,const struct pathspec *ps, +struct file_list *files,struct list_options *opts) +{ +reset_file_list (files); + +if (get_modified_files (r, files, ps) <0 ) +return -1 ; + +if (files->nr) +list ((struct item **)files->file, files->nr, opts); +putchar ('\n' ); + +return 0 ; +} +int run_add_i (struct repository *r,const struct pathspec *ps) { -die (_ ("No commands are available in the built-in `git add -i` yet!" )); +struct print_file_item_data print_file_item_data = { +"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT + }; +struct list_options opts = { +NULL , print_file_item, &print_file_item_data + }; +struct strbuf header =STRBUF_INIT ; +struct file_list files = {NULL }; +int res =0 ; + +strbuf_addstr (&header," " ); +strbuf_addf (&header, print_file_item_data.modified_fmt, +_ ("staged" ),_ ("unstaged" ),_ ("path" )); + opts.header = header.buf; + + res =run_status (r, ps, &files, &opts); + +release_file_list (&files); +strbuf_release (&print_file_item_data.buf); +strbuf_release (&print_file_item_data.index); +strbuf_release (&print_file_item_data.worktree); +strbuf_release (&header); + +return res; }
Signature · 1 line
gitgitgadget
PATCH 4/11 built-in add -i: refresh the index before running `status`
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This is what the Perl version does, and therefore it is what the built-in version should do, too.
--- add-interactive.c | 4 +++- repository.c | 19 +++++++++++++++++++ repository.h | 7 +++++++ 3 files changed, 29 insertions(+), 1 deletion(-)
add-interactive.c +3 -1
@@ -264,7 +264,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps) _("staged"), _("unstaged"), _("path")); opts.header = header.buf; - res =run_status (r, ps, &files, &opts); +repo_refresh_and_write_index (r,REFRESH_QUIET ,1 ); +if (run_status (r, ps, &files, &opts) <0 ) + res =-1 ;release_file_list (&files);strbuf_release (&print_file_item_data.buf);
repository.c +19 -0
@@ -272,3 +272,22 @@ int repo_hold_locked_index(struct repository *repo,BUG ("the repo hasn't been setup" );return hold_lock_file_for_update (lf, repo->index_file, flags); } + +int repo_refresh_and_write_index (struct repository *r, +unsigned int flags,int gentle) +{ +struct lock_file lock_file =LOCK_INIT ; +int fd; + +if (repo_read_index_preload (r,NULL ,0 ) <0 ) +return error (_ ("could not read index" )); + fd =repo_hold_locked_index (r, &lock_file,0 ); +if (!gentle && fd <0 ) +return error (_ ("could not lock index for writing" )); +refresh_index (r->index, flags,NULL ,NULL ,NULL ); +if (0 <= fd) +repo_update_index_if_able (r, &lock_file); +rollback_lock_file (&lock_file); + +return 0 ; +}
repository.h +7 -0
@@ -154,5 +154,12 @@ int repo_read_index_unmerged(struct repository *);*/ void repo_update_index_if_able (struct repository *,struct lock_file *); +/* +* Refresh the index and write it out. If the index file could not be +* locked, error out, except in gentle mode. The flags will be passed +* through to refresh_index(). +*/ +int repo_refresh_and_write_index (struct repository *r, +unsigned int flags,int gentle);#endif /* REPOSITORY_H */
Signature · 1 line
gitgitgadget
PATCH 5/11 built-in add -i: color the header in the `status` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[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 | 51 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-)
add-interactive.c +48 -3
@@ -1,13 +1,56 @@#include "cache.h" #include "add-interactive.h" #include "config.h" +#include "color.h" +#include "config.h" #include "diffcore.h" #include "revision.h" #include "refs.h" +static int use_color =-1 ; + +enum color_add_i { +COLOR_HEADER =0 , +}; + +static char list_colors[][COLOR_MAXLEN ] = { +GIT_COLOR_BOLD ,/* Header */ +}; + +static const char *get_add_i_color (enum color_add_i ix) +{ +if (want_color (use_color)) +return list_colors[ix]; +return "" ; +} + +static int parse_color_slot (const char *slot) +{ +if (!strcasecmp (slot,"header" )) +return COLOR_HEADER ; + +return -1 ; +} +int add_i_config (const char *var,const char *value,void *cb) { -return git_default_config (var, value, cb); +const char *name; + +if (!strcmp (var,"color.interactive" )) { + use_color =git_config_colorbool (var, value); +return 0 ; + } + +if (skip_prefix (var,"color.interactive." , &name)) { +int slot =parse_color_slot (name); +if (slot <0 ) +return 0 ; +if (!value) +return config_error_nonbool (var); +return color_parse (value, list_colors[slot]); + } + +return git_color_default_config (var, value, cb); }struct item {
@@ -27,8 +70,10 @@ static void list(struct item **list, size_t nr, struct list_options *opts)if (!nr)return ; -if (opts->header) -printf ("%s\n" , opts->header); +if (opts->header) { +const char *header_color =get_add_i_color (COLOR_HEADER ); +color_fprintf_ln (stdout, header_color,"%s" , opts->header); + }for (i =0 ; i < nr; i++) { opts->print_item (i, list[i], opts->print_item_data);
Signature · 1 line
gitgitgadget
PATCH 6/11 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.
Apart from the "and choose" part, there are more differences between the way the status command calls the list_and_choose() function in the Perl version of git add -i compared to the other callers of said function. The most important ones:
- The list is not only shown, but the user is also asked to make a choice, possibly selecting multiple entries.
- The list of items is prefixed with a marker indicating what items have been selected, if multi-selection is allowed.
- Initially, for each item a unique prefix (if there exists any within the given parameters) is determined, and shown in the list, and accepted as a shortcut for the selection.
These features will be implemented later, except the part where the user can choose a command. At this stage, though, the built-in git add -i still only supports the status command, with the remaining commands to follow over the course of the next commits.
In addition, we also modify list() to support displaying the commands in columns, even if there is currently only one.
The Perl script git-add--interactive.perl mixed the purposes of the "list" and the "and choose" part into the same function. In the C version, we will keep them separate instead, calling the list() function from the list_and_choose() function.
Note that we only have a prompt ending in a single ">" at this stage; later commits will add commands that display a double ">>" to indicate that the user is in a different loop than the main one.
--- add-interactive.c | 122 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 2 deletions(-)
add-interactive.c +120 -2
@@ -58,6 +58,7 @@ struct item { };struct list_options { +int columns;const char *header;void (*print_item)(int i,struct item *item,void *print_item_data);void *print_item_data;
@@ -65,7 +66,7 @@ struct list_options {static void list (struct item **list,size_t nr,struct list_options *opts) { -int i; +int i, last_lf =0 ;if (!nr)return ;
@@ -77,8 +78,90 @@ static void list(struct item **list, size_t nr, struct list_options *opts)for (i =0 ; i < nr; i++) { opts->print_item (i, list[i], opts->print_item_data); + +if ((opts->columns) && ((i +1 ) % (opts->columns))) { +putchar ('\t' ); + last_lf =0 ; + } +else { +putchar ('\n' ); + last_lf =1 ; + } + } + +if (!last_lf)putchar ('\n' ); +} +struct list_and_choose_options { +struct list_options list_opts; + +const char *prompt; +}; + +/* +* Returns the selected index. +*/ +static ssize_t list_and_choose (struct item **items,size_t nr, +struct list_and_choose_options *opts) +{ +struct strbuf input =STRBUF_INIT ; +ssize_t res =-1 ; + +for (;;) { +char *p, *endp; + +strbuf_reset (&input); + +list (items, nr, &opts->list_opts); + +printf ("%s%s" , opts->prompt,"> " ); +fflush (stdout); + +if (strbuf_getline (&input, stdin) ==EOF ) { +putchar ('\n' ); + res =-2 ; +break ; + } +strbuf_trim (&input); + +if (!input.len) +break ; + + p = input.buf; +for (;;) { +size_t sep =strcspn (p," \t\r\n," ); +ssize_t index =-1 ; + +if (!sep) { +if (!*p) +break ; + p++; +continue ; + } + +if (isdigit (*p)) { + index =strtoul (p, &endp,10 ) -1 ; +if (endp != p + sep) + index =-1 ; + } + + p[sep] ='\0' ; +if (index <0 || index >= nr) +printf (_ ("Huh (%s)?\n" ), p); +else { + res = index; +break ; + } + + p += sep +1 ; + } + +if (res >=0 ) +break ; } + +strbuf_release (&input); +return res; }struct adddel {
@@ -292,16 +375,39 @@ static int run_status(struct repository *r, const struct pathspec *ps,return 0 ; } +static void print_command_item (int i,struct item *item, +void *print_command_item_data) +{ +printf (" %2d: %s" , i +1 , item->name); +} + +struct command_item { +struct item item; +int (*command)(struct repository *r,const struct pathspec *ps, +struct file_list *files,struct list_options *opts); +}; +int run_add_i (struct repository *r,const struct pathspec *ps) { +struct list_and_choose_options main_loop_opts = { + {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, +N_ ("What now" ) + }; +struct command_item + status = { {"status" }, run_status }; +struct command_item *commands[] = { + &status + }; +struct print_file_item_data print_file_item_data = {"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT };struct list_options opts = { -NULL , print_file_item, &print_file_item_data +0 ,NULL , print_file_item, &print_file_item_data };struct strbuf header =STRBUF_INIT ;struct file_list files = {NULL }; +ssize_t i;int res =0 ;strbuf_addstr (&header," " );
@@ -313,6 +419,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)if (run_status (r, ps, &files, &opts) <0 ) res =-1 ; +for (;;) { + i =list_and_choose ((struct item **)commands, +ARRAY_SIZE (commands), &main_loop_opts); +if (i <-1 ) { +printf (_ ("Bye.\n" )); + res =0 ; +break ; + } +if (i >=0 ) + res = commands[i]->command (r, ps, &files, &opts); + } +release_file_list (&files);strbuf_release (&print_file_item_data.buf);strbuf_release (&print_file_item_data.index);
Signature · 1 line
gitgitgadget
PATCH 9/11 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 | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-)
add-interactive.c +23 -1
@@ -12,10 +12,12 @@ static int use_color = -1;enum color_add_i {COLOR_HEADER =0 , +COLOR_HELP , };static char list_colors[][COLOR_MAXLEN ] = {GIT_COLOR_BOLD ,/* Header */ +GIT_COLOR_BOLD_RED ,/* Help */ };static const char *get_add_i_color (enum color_add_i ix)
@@ -29,6 +31,8 @@ static int parse_color_slot(const char *slot) {if (!strcasecmp (slot,"header" ))return COLOR_HEADER ; +if (!strcasecmp (slot,"help" )) +return COLOR_HELP ;return -1 ; }
@@ -112,6 +116,7 @@ struct list_and_choose_options {struct list_options list_opts;const char *prompt; +void (*print_help)(void ); };/*
@@ -145,6 +150,11 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,if (!input.len)break ; +if (!strcmp (input.buf,"?" )) { + opts->print_help (); +continue ; + } + p = input.buf;for (;;) {size_t sep =strcspn (p," \t\r\n," );
@@ -431,11 +441,23 @@ struct command_item { struct file_list *files, struct list_options *opts); }; +static void command_prompt_help (void ) +{ +const char *help_color =get_add_i_color (COLOR_HELP ); +color_fprintf_ln (stdout, help_color,"%s" ,_ ("Prompt help:" )); +color_fprintf_ln (stdout, help_color,"1 - %s" , +_ ("select a numbered item" )); +color_fprintf_ln (stdout, help_color,"foo - %s" , +_ ("select item based on unique prefix" )); +color_fprintf_ln (stdout, help_color," - %s" , +_ ("(empty) select nothing" )); +} +int run_add_i (struct repository *r,const struct pathspec *ps) {struct list_and_choose_options main_loop_opts = { {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, -N_ ("What now" ) +N_ ("What now" ), command_prompt_help };struct command_item status = { {"status" }, run_status };
Signature · 1 line
gitgitgadget
PATCH 8/11 built-in add -i: show unique prefixes of the commands
In reply to Johannes Schindelin via GitGitGadgetFrom: Slavica Djukic <[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.
We use the prefix map implementation that we just added in the previous commit for that purpose.
--- add-interactive.c | 70 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 13 deletions(-)
add-interactive.c +57 -13
@@ -6,6 +6,7 @@#include "diffcore.h" #include "revision.h" #include "refs.h" +#include "prefix-map.h" static int use_color =-1 ;
@@ -53,18 +54,33 @@ int add_i_config(const char *var, const char *value, void *cb)return git_color_default_config (var, value, cb); } -struct item { -const char *name; -}; +static ssize_t find_unique (const char *string, +struct prefix_item **list,size_t nr) +{ +ssize_t found =-1 , i; + +for (i =0 ; i < nr; i++) { +struct prefix_item *item = list[i]; +if (!starts_with (item->name, string)) +continue ; +if (found >=0 ) +return -1 ; + found = i; + } + +return found; +}struct list_options {int columns;const char *header; -void (*print_item)(int i,struct item *item,void *print_item_data); +void (*print_item)(int i,struct prefix_item *item, +void *print_item_data);void *print_item_data; }; -static void list (struct item **list,size_t nr,struct list_options *opts) +static void list (struct prefix_item **list,size_t nr, +struct list_options *opts) {int i, last_lf =0 ;
@@ -101,12 +117,14 @@ struct list_and_choose_options {/* * Returns the selected index. */ -static ssize_t list_and_choose (struct item **items,size_t nr, +static ssize_t list_and_choose (struct prefix_item **items,size_t nr,struct list_and_choose_options *opts) {struct strbuf input =STRBUF_INIT ;ssize_t res =-1 ; +find_unique_prefixes (items, nr,1 ,4 ); +for (;;) {char *p, *endp;
@@ -146,6 +164,9 @@ static ssize_t list_and_choose(struct item **items, size_t nr, } p[sep] ='\0' ; +if (index <0 ) + index =find_unique (p, items, nr); +if (index <0 || index >= nr)printf (_ ("Huh (%s)?\n" ), p);else {
@@ -171,7 +192,7 @@ struct adddel {struct file_list {struct file_item { -struct item item; +struct prefix_item item;struct adddel index, worktree; } **file;size_t nr, alloc;
@@ -337,12 +358,29 @@ static void populate_wi_changes(struct strbuf *buf,strbuf_addstr (buf, no_changes); } +/* filters out prefixes which have special meaning to list_and_choose() */ +static int is_valid_prefix (const char *prefix,size_t prefix_len) +{ +return prefix_len && prefix && +/* +* We expect `prefix` to be NUL terminated, therefore this +* `strcspn()` call is okay, even if it might do much more +* work than strictly necessary. +*/ +strcspn (prefix," \t\r\n," ) >= prefix_len &&/* separators */ + *prefix !='-' &&/* deselection */ + !isdigit (*prefix) &&/* selection */ + (prefix_len !=1 || + (*prefix !='*' &&/* "all" wildcard */ + *prefix !='?' ));/* prompt help */ +} +struct print_file_item_data {const char *modified_fmt;struct strbuf buf, index, worktree; }; -static void print_file_item (int i,struct item *item, +static void print_file_item (int i,struct prefix_item *item,void *print_file_item_data) {struct file_item *c = (struct file_item *)item;
@@ -369,20 +407,26 @@ static int run_status(struct repository *r, const struct pathspec *ps,return -1 ;if (files->nr) -list ((struct item **)files->file, files->nr, opts); +list ((struct prefix_item **)files->file, files->nr, opts);putchar ('\n' );return 0 ; } -static void print_command_item (int i,struct item *item, +static void print_command_item (int i,struct prefix_item *item,void *print_command_item_data) { -printf (" %2d: %s" , i +1 , item->name); +if (!item->prefix_length || + !is_valid_prefix (item->name, item->prefix_length)) +printf (" %2d: %s" , i +1 , item->name); +else +printf (" %3d: [%.*s]%s" , i +1 , + (int )item->prefix_length, item->name, + item->name + item->prefix_length); }struct command_item { -struct item item; +struct prefix_item item;int (*command)(struct repository *r,const struct pathspec *ps,struct file_list *files,struct list_options *opts); };
@@ -420,7 +464,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps) res =-1 ;for (;;) { - i =list_and_choose ((struct item **)commands, + i =list_and_choose ((struct prefix_item **)commands,ARRAY_SIZE (commands), &main_loop_opts);if (i <-1 ) {printf (_ ("Bye.\n" ));
Signature · 1 line
gitgitgadget
PATCH 7/11 Add a function to determine unique prefixes for a list of strings
In reply to Johannes Schindelin via GitGitGadgetFrom: Slavica Djukic <[email protected]>
In the git add -i command, we show unique prefixes of the commands and files, to give an indication what prefix would select them.
Naturally, the C implementation looks a lot different than the Perl
implementation: in Perl, a trie is much easier implemented, while we already have a pretty neat hashmap implementation in C that we use for the purpose of storing (not necessarily unique) prefixes.
The idea: for each item that we add, we generate prefixes starting with the first letter, then the first two letters, then three, etc, until we find a prefix that is unique (or until the prefix length would be longer than we want). If we encounter a previously-unique prefix on the way, we adjust that item's prefix to make it unique again (or we mark it as having no unique prefix if we failed to find one). These partial prefixes are stored in a hash map (for quick lookup times).
To make sure that this function works as expected, we add a test using a special-purpose test helper that was added for that purpose.
Note: We expect the list of prefix items to be passed in as a list of pointers rather than as regular list to avoid having to copy information (the actual items will most likely contain more information than just the name and the length of the unique prefix, but passing in struct prefix_item * would not allow for that).
--- Makefile | 2 + prefix-map.c | 111 +++++++++++++++++++++++++++++++++++++ prefix-map.h | 40 +++++++++++++ t/helper/test-prefix-map.c | 58 +++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0016-prefix-map.sh | 10 ++++ 7 files changed, 223 insertions(+) create mode 100644 prefix-map.c create mode 100644 prefix-map.h create mode 100644 t/helper/test-prefix-map.c create mode 100755 t/t0016-prefix-map.sh
Makefile +2 -0
@@ -754,6 +754,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o TEST_BUILTINS_OBJS += test-parse-options.o TEST_BUILTINS_OBJS += test-path-utils.o TEST_BUILTINS_OBJS += test-pkt-line.o +TEST_BUILTINS_OBJS += test-prefix-map.o TEST_BUILTINS_OBJS += test-prio-queue.o TEST_BUILTINS_OBJS += test-reach.o TEST_BUILTINS_OBJS += test-read-cache.o
@@ -967,6 +968,7 @@ LIB_OBJS += patch-ids.o LIB_OBJS += path.o LIB_OBJS += pathspec.o LIB_OBJS += pkt-line.o +LIB_OBJS += prefix-map.o LIB_OBJS += preload-index.o LIB_OBJS += pretty.o LIB_OBJS += prio-queue.o
prefix-map.c new file +111 -0
111 lines not on this page · raw message
prefix-map.h new file +40 -0
@@ -0,0 +1,40 @@ +#ifndef PREFIX_MAP_H +#define PREFIX_MAP_H + +#include "hashmap.h" + +struct prefix_item { +const char *name; +size_t prefix_length; +}; + +struct prefix_map_entry { +struct hashmap_entry e; +const char *name; +size_t prefix_length; +/* if item is NULL, the prefix is not unique */ +struct prefix_item *item; +}; + +struct prefix_map { +struct hashmap map; +int min_length, max_length; +}; + +/* +* Find unique prefixes in a given list of strings. +* +* Typically, the `struct prefix_item` information will be but a field in the +* actual item struct; For this reason, the `list` parameter is specified as a +* list of pointers to the items. +* +* The `min_length`/`max_length` parameters define what length the unique +* prefixes should have. +* +* If no unique prefix could be found for a given item, its `prefix_length` +* will be set to 0. +*/ +void find_unique_prefixes (struct prefix_item **list,size_t nr, +int min_length,int max_length); + +#endif
t/helper/test-prefix-map.c new file +58 -0
58 lines not on this page · raw message
t/helper/test-tool.c +1 -0
@@ -32,6 +32,7 @@ static struct test_cmd cmds[] = { { "parse-options", cmd__parse_options }, { "path-utils", cmd__path_utils }, { "pkt-line", cmd__pkt_line }, + { "prefix-map", cmd__prefix_map }, { "prio-queue", cmd__prio_queue }, { "reach", cmd__reach }, { "read-cache", cmd__read_cache },
t/helper/test-tool.h +1 -0
@@ -29,6 +29,7 @@ int cmd__online_cpus(int argc, const char **argv);int cmd__parse_options (int argc,const char **argv);int cmd__path_utils (int argc,const char **argv);int cmd__pkt_line (int argc,const char **argv); +int cmd__prefix_map (int argc,const char **argv);int cmd__prio_queue (int argc,const char **argv);int cmd__reach (int argc,const char **argv);int cmd__read_cache (int argc,const char **argv);
t/t0016-prefix-map.sh new file +10 -0
@@ -0,0 +1,10 @@ +#!/bin/sh + +test_description='basic tests for prefix map' +. ./test-lib.sh + +test_expect_success 'prefix map' ' + test-tool prefix-map +' + +test_done
Signature · 1 line
gitgitgadget
PATCH 10/11 built-in add -i: use color in the main loop
In reply to Johannes Schindelin via GitGitGadgetFrom: Slavica Djukic <[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 | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-)
add-interactive.c +35 -5
@@ -13,11 +13,17 @@ static int use_color = -1;enum color_add_i {COLOR_HEADER =0 ,COLOR_HELP , +COLOR_PROMPT , +COLOR_ERROR , +COLOR_RESET , };static char list_colors[][COLOR_MAXLEN ] = {GIT_COLOR_BOLD ,/* Header */ GIT_COLOR_BOLD_RED ,/* Help */ +GIT_COLOR_BOLD_BLUE ,/* Prompt */ +GIT_COLOR_BOLD_RED ,/* Error */ +GIT_COLOR_RESET ,/* Reset */ };static const char *get_add_i_color (enum color_add_i ix)
@@ -33,6 +39,12 @@ static int parse_color_slot(const char *slot)return COLOR_HEADER ;if (!strcasecmp (slot,"help" ))return COLOR_HELP ; +if (!strcasecmp (slot,"prompt" )) +return COLOR_PROMPT ; +if (!strcasecmp (slot,"error" )) +return COLOR_ERROR ; +if (!strcasecmp (slot,"reset" )) +return COLOR_RESET ;return -1 ; }
@@ -125,6 +137,8 @@ struct list_and_choose_options {static ssize_t list_and_choose (struct prefix_item **items,size_t nr,struct list_and_choose_options *opts) { +const char *prompt_color =get_add_i_color (COLOR_PROMPT ); +const char *error_color =get_add_i_color (COLOR_ERROR );struct strbuf input =STRBUF_INIT ;ssize_t res =-1 ;
@@ -137,7 +151,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,list (items, nr, &opts->list_opts); -printf ("%s%s" , opts->prompt,"> " ); +color_fprintf (stdout, prompt_color,"%s" , opts->prompt); +fputs ("> " , stdout);fflush (stdout);if (strbuf_getline (&input, stdin) ==EOF ) {
@@ -178,7 +193,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr, index =find_unique (p, items, nr);if (index <0 || index >= nr) -printf (_ ("Huh (%s)?\n" ), p); +color_fprintf_ln (stdout, error_color, +_ ("Huh (%s)?" ), p);else { res = index;break ;
@@ -423,15 +439,21 @@ static int run_status(struct repository *r, const struct pathspec *ps,return 0 ; } +struct print_command_item_data { +const char *color, *reset; +}; +static void print_command_item (int i,struct prefix_item *item,void *print_command_item_data) { +struct print_command_item_data *d = print_command_item_data; +if (!item->prefix_length || !is_valid_prefix (item->name, item->prefix_length))printf (" %2d: %s" , i +1 , item->name);else -printf (" %3d: [%.*s]%s" , i +1 , - (int )item->prefix_length, item->name, +printf (" %2d: %s%.*s%s%s" , i +1 , + d->color, (int )item->prefix_length, item->name, d->reset, item->name + item->prefix_length); }
@@ -455,8 +477,16 @@ static void command_prompt_help(void)int run_add_i (struct repository *r,const struct pathspec *ps) { +struct print_command_item_data data = { +/* +* When color was asked for, use the prompt color for +* highlighting, otherwise use square brackets. +*/ +want_color (use_color) ?get_add_i_color (COLOR_PROMPT ) :"[" , +want_color (use_color) ?get_add_i_color (COLOR_RESET ) :"]" + };struct list_and_choose_options main_loop_opts = { - {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, + {4 ,N_ ("*** Commands ***" ), print_command_item, &data },N_ ("What now" ), command_prompt_help };struct command_item
Signature · 1 line
gitgitgadget
PATCH 11/11 built-in add -i: implement the `help` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[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 | 27 +++++++++++++++++++++++++-- t/t3701-add-interactive.sh | 24 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-)
add-interactive.c +25 -2
@@ -439,6 +439,27 @@ static int run_status(struct repository *r, const struct pathspec *ps,return 0 ; } +static int run_help (struct repository *r,const struct pathspec *ps, +struct file_list *files,struct list_options *opts) +{ +const char *help_color =get_add_i_color (COLOR_HELP ); + +color_fprintf_ln (stdout, help_color,"status - %s" , +_ ("show paths with changes" )); +color_fprintf_ln (stdout, help_color,"update - %s" , +_ ("add working tree state to the staged set of changes" )); +color_fprintf_ln (stdout, help_color,"revert - %s" , +_ ("revert staged set of changes back to the HEAD version" )); +color_fprintf_ln (stdout, help_color,"patch - %s" , +_ ("pick hunks and update selectively" )); +color_fprintf_ln (stdout, help_color,"diff - %s" , +_ ("view diff between HEAD and index" )); +color_fprintf_ln (stdout, help_color,"add untracked - %s" , +_ ("add contents of untracked files to the staged set of changes" )); + +return 0 ; +} +struct print_command_item_data {const char *color, *reset; };
@@ -490,9 +511,11 @@ int run_add_i(struct repository *r, const struct pathspec *ps)N_ ("What now" ), command_prompt_help };struct command_item - status = { {"status" }, run_status }; + status = { {"status" }, run_status }, + help = { {"help" }, run_help };struct command_item *commands[] = { - &status + &status, + &help }; struct print_file_item_data print_file_item_data = {
t/t3701-add-interactive.sh +24 -0
@@ -639,4 +639,28 @@ test_expect_success 'add -p patch editing works with pathological context lines' test_cmp expected-2 actual ' +test_expect_success 'show help from add--helper' ' + git reset --hard && + cat >expect <<-EOF && + + <BOLD>*** Commands ***<RESET> + 1: <BOLD;BLUE>s<RESET>tatus 2: <BOLD;BLUE>u<RESET>pdate 3: <BOLD;BLUE>r<RESET>evert 4: <BOLD;BLUE>a<RESET>dd untracked + 5: <BOLD;BLUE>p<RESET>atch 6: <BOLD;BLUE>d<RESET>iff 7: <BOLD;BLUE>q<RESET>uit 8: <BOLD;BLUE>h<RESET>elp + <BOLD;BLUE>What now<RESET>> <BOLD;RED>status - show paths with changes<RESET> + <BOLD;RED>update - add working tree state to the staged set of changes<RESET> + <BOLD;RED>revert - revert staged set of changes back to the HEAD version<RESET> + <BOLD;RED>patch - pick hunks and update selectively<RESET> + <BOLD;RED>diff - view diff between HEAD and index<RESET> + <BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET> + <BOLD>*** Commands ***<RESET> + 1: <BOLD;BLUE>s<RESET>tatus 2: <BOLD;BLUE>u<RESET>pdate 3: <BOLD;BLUE>r<RESET>evert 4: <BOLD;BLUE>a<RESET>dd untracked + 5: <BOLD;BLUE>p<RESET>atch 6: <BOLD;BLUE>d<RESET>iff 7: <BOLD;BLUE>q<RESET>uit 8: <BOLD;BLUE>h<RESET>elp + <BOLD;BLUE>What now<RESET>>$SP + Bye. + EOF + test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored && + test_decode_color <actual.colored >actual && + test_i18ncmp expect actual +' + test_done
Signature · 1 line
gitgitgadget
Re: [PATCH 01/11] Start to implement a built-in version of `git add --interactive`
In reply to Johannes Schindelin via GitGitGadgetQuoted 79 lines · Johannes Schindelin via GitGitGadget
From: Johannes Schindelin <[email protected]>
This is hardly the first conversion of a Git command that is implemented as a script to a built-in. So far, the most successful strategy for such conversions has been to add a built-in helper and call that for more and more functionality from the script, as more and more parts are converted.
With the interactive add, we choose a different strategy. The sole reason for this is that on Windows (where such a conversion has the most benefits in terms of speed and robustness) we face the very specific problem that a
system()call in Perl seems to closestdinin the parent process when the spawned process consumes even one character fromstdin. 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.
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.At this point, the built-in version of
git add -ionly states that it cannot do anything yet ;-)Signed-off-by Johannes Schindelin <[email protected]>--- Documentation/config/add.txt | 5 +++++ Makefile | 1 + add-interactive.c | 13 +++++++++++++ add-interactive.h | 10 ++++++++++ builtin/add.c | 16 +++++++++++++++- t/README | 4 ++++ 6 files changed, 48 insertions(+), 1 deletion(-)create mode 100644
add-interactive.c
create mode 100644add-interactive.hDocumentation/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@@ -848,6 +848,7 @@ LIB_H = $(shell $(FIND) . \ -name '*.h' -print) LIB_OBJS += abspath.o +LIB_OBJS += add-interactive.o LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.oadd-interactive.c new file +8
-0@@ -0,0 +1,13 @@ +#include "cache.h" +#include "add-interactive.h" +#include "config.h" + +int add_i_config (const char *var,const char *value,void *cb) +{ +return git_default_config (var, value, cb); +}
[...]
Quoted 4 lines
builtin/add.c +0
-0
[...]
Quoted 1 line
+static int
use_builtin_add_i;
[...]
builtin/add.c static int add_config(const char *var, const char *value, void *cb) · line 333
Quoted 12 lines
builtin/add.c +6
-1@@ -319,7 +324,12 @@ static int add_config(const char *var, const char *value, void *cb) ignore_add_errors =git_config_bool (var, value);return 0 ; } -return git_default_config (var, value, cb); +if (!strcmp (var,"add.interactive.usebuiltin" )) { + use_builtin_add_i =git_config_bool (var, value); +return 0 ; + } + +return add_i_config (var, value, cb); }
Something about this split between add_config() in this file and add_i_config() in add-interactive.c bothers me. I'm not saying it is wrong, but it bothers me.
Perhaps it is that we moved the call to git_default_config() to add-interactive.c -- so correct behavior of the non-interactive case depends on add-interactive.c to do the right thing.
Also, since we can't control the order of observed the k/v pairs, we can't do "if (use_builtin_add_i) return add_i_config(...);" And that wouldn't allow both versions to pickup a common config setting (in their own static variables).
Currently, neither function looks at any other k/v pairs, so this is a bit of a moot point, but I'm wondering if this should look like this:
int add_config(...)
{
// give add-interactive.c a chance to look at k/v pair, but
// do not short-cut because we don't know yet whether we
// will be interactive or not yet.
(void)add_i_config(...);
...ignore_add_errors...
...use_builtin_add_i...
return git_default_config(...);
}
int add_i_config(...)
{
return 0;
}or just inline everything here in add_config() and be done with it.
Jeff
Re: [PATCH 01/11] Start to implement a built-in version of `git add --interactive`
Quoted 4 lines · Jeff Hostetler
Currently, neither function looks at any other k/v pairs, so this is a bit of a moot point, but I'm wondering if this should look like this:
Quoted 12 lines
int add_config(...)
{
// give add-interactive.c a chance to look at k/v pair, but
// do not short-cut because we don't know yet whether we
// will be interactive or not yet.
(void)add_i_config(...);
...ignore_add_errors...
...use_builtin_add_i...
return git_default_config(...);
}Yeah, I agree this split seems a bit more natural. It is worth propagating errors from add_i_config(), though, like:
if (add_i_config(var, value, data))
return -1;
so that any key-specific errors (e.g., config_error_nonbool) stop the parsing in the usual way.
-Peff
Re: [PATCH 06/11] built-in add -i: implement the main loop
In reply to Johannes Schindelin via GitGitGadgetQuoted 101 lines · Johannes Schindelin via GitGitGadget
From: 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 thelist()function that we conveniently introduced for use by thestatuscommand.Apart from the "and choose" part, there are more differences between the way the
statuscommand calls thelist_and_choose()function in the Perl version ofgit add -icompared to the other callers of said function. The most important ones:- The list is not only shown, but the user is also asked to make a choice, possibly selecting multiple entries.
- The list of items is prefixed with a marker indicating what items have been selected, if multi-selection is allowed.
- Initially, for each item a unique prefix (if there exists any within the given parameters) is determined, and shown in the list, and accepted as a shortcut for the selection.
These features will be implemented later, except the part where the user can choose a command. At this stage, though, the built-in
git add -istill only supports thestatuscommand, with the remaining commands to follow over the course of the next commits.In addition, we also modify
list()to support displaying the commands in columns, even if there is currently only one.The Perl script
git-add--interactive.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.Note that we only have a prompt ending in a single ">" at this stage; later commits will add commands that display a double ">>" to indicate that the user is in a different loop than the main one.
Signed-off-by Johannes Schindelin <[email protected]>--- add-interactive.c | 122 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 2 deletions(-)add-interactive.c +33
-1@@ -58,6 +58,7 @@ struct item { };struct list_options { +int columns;const char *header;void (*print_item)(int i,struct item *item,void *print_item_data);void *print_item_data;@@ -65,7 +66,7 @@ struct list_options {static void list (struct item **list,size_t nr,struct list_options *opts) { -int i; +int i, last_lf =0 ;if (!nr)return ;@@ -77,8 +78,90 @@ static void list(struct item **list, size_t nr, struct list_options *opts)for (i =0 ; i < nr; i++) { opts->print_item (i, list[i], opts->print_item_data); + +if ((opts->columns) && ((i +1 ) % (opts->columns))) { +putchar ('\t' ); + last_lf =0 ; + } +else { +putchar ('\n' ); + last_lf =1 ; + } + } + +if (!last_lf)putchar ('\n' ); +} +struct list_and_choose_options { +struct list_options list_opts; + +const char *prompt; +}; + +/* +* Returns the selected index. +*/ +static ssize_t list_and_choose (struct item **items,size_t nr, +struct list_and_choose_options *opts) +{ +struct strbuf input =STRBUF_INIT ; +ssize_t res =-1 ; + +for (;;) { +char *p, *endp; +
add-interactive.c static void list(struct item **list, size_t nr, struct list_options *opts) · line 122
Quoted 10 lines
add-interactive.c +10
-0+strbuf_reset (&input); + +list (items, nr, &opts->list_opts); + +printf ("%s%s" , opts->prompt,"> " ); +fflush (stdout); + +if (strbuf_getline (&input, stdin) ==EOF ) { +putchar ('\n' ); + res =-2 ;
It would be nice to know what -1 and -2 mean if
they get returned to our caller. Maybe a #define
for these??
Quoted 106 lines
add-interactive.c +77
-1+break ; + } +strbuf_trim (&input); + +if (!input.len) +break ; + + p = input.buf; +for (;;) { +size_t sep =strcspn (p," \t\r\n," ); +ssize_t index =-1 ; + +if (!sep) { +if (!*p) +break ; + p++; +continue ; + } + +if (isdigit (*p)) { + index =strtoul (p, &endp,10 ) -1 ; +if (endp != p + sep) + index =-1 ; + } + + p[sep] ='\0' ; +if (index <0 || index >= nr) +printf (_ ("Huh (%s)?\n" ), p); +else { + res = index; +break ; + } + + p += sep +1 ; + } + +if (res >=0 ) +break ; } + +strbuf_release (&input); +return res; }struct adddel {@@ -292,16 +375,39 @@ static int run_status(struct repository *r, const struct pathspec *ps,return 0 ; } +static void print_command_item (int i,struct item *item, +void *print_command_item_data) +{ +printf (" %2d: %s" , i +1 , item->name); +} + +struct command_item { +struct item item; +int (*command)(struct repository *r,const struct pathspec *ps, +struct file_list *files,struct list_options *opts); +}; +int run_add_i (struct repository *r,const struct pathspec *ps) { +struct list_and_choose_options main_loop_opts = { + {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, +N_ ("What now" ) + }; +struct command_item + status = { {"status" }, run_status }; +struct command_item *commands[] = { + &status + }; +struct print_file_item_data print_file_item_data = {"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT };struct list_options opts = { -NULL , print_file_item, &print_file_item_data +0 ,NULL , print_file_item, &print_file_item_data };struct strbuf header =STRBUF_INIT ;struct file_list files = {NULL }; +ssize_t i;int res =0 ;strbuf_addstr (&header," " );@@ -313,6 +419,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)if (run_status (r, ps, &files, &opts) <0 ) res =-1 ; +for (;;) { + i =list_and_choose ((struct item **)commands, +ARRAY_SIZE (commands), &main_loop_opts); +if (i <-1 ) { +printf (_ ("Bye.\n" )); + res =0 ; +break ; + } +if (i >=0 ) + res = commands[i]->command (r, ps, &files, &opts); + } +release_file_list (&files);strbuf_release (&print_file_item_data.buf);strbuf_release (&print_file_item_data.index);
Re: [PATCH 07/11] Add a function to determine unique prefixes for a list of strings
In reply to Slavica Djukic via GitGitGadgetQuoted 104 lines · Slavica Djukic via GitGitGadget
From: Slavica Djukic <[email protected]>
In the
git add -icommand, we show unique prefixes of the commands and files, to give an indication what prefix would select them.Naturally, the C implementation looks a lot different than the Perl
implementation: in Perl, a trie is much easier implemented, while we already have a pretty neat hashmap implementation in C that we use for the purpose of storing (not necessarily unique) prefixes.The idea: for each item that we add, we generate prefixes starting with the first letter, then the first two letters, then three, etc, until we find a prefix that is unique (or until the prefix length would be longer than we want). If we encounter a previously-unique prefix on the way, we adjust that item's prefix to make it unique again (or we mark it as having no unique prefix if we failed to find one). These partial prefixes are stored in a hash map (for quick lookup times).
To make sure that this function works as expected, we add a test using a special-purpose test helper that was added for that purpose.
Note: We expect the list of prefix items to be passed in as a list of pointers rather than as regular list to avoid having to copy information (the actual items will most likely contain more information than just the name and the length of the unique prefix, but passing in
struct prefix_item *would not allow for that).Signed-off-by Slavica Djukic <[email protected]>Signed-off-by Johannes Schindelin <[email protected]>--- Makefile | 2 + prefix-map.c | 111 +++++++++++++++++++++++++++++++++++++ prefix-map.h | 40 +++++++++++++ t/helper/test-prefix-map.c | 58 +++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0016-prefix-map.sh | 10 ++++ 7 files changed, 223 insertions(+)create mode 100644
prefix-map.c
create mode 100644prefix-map.h
create mode 100644t/helper/test-prefix-map.c
create mode 100755t/t0016-prefix-map.shMakefile +2
-0@@ -754,6 +754,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o TEST_BUILTINS_OBJS += test-parse-options.o TEST_BUILTINS_OBJS += test-path-utils.o TEST_BUILTINS_OBJS += test-pkt-line.o +TEST_BUILTINS_OBJS += test-prefix-map.o TEST_BUILTINS_OBJS += test-prio-queue.o TEST_BUILTINS_OBJS += test-reach.o TEST_BUILTINS_OBJS += test-read-cache.o@@ -967,6 +968,7 @@ LIB_OBJS += patch-ids.o LIB_OBJS += path.o LIB_OBJS += pathspec.o LIB_OBJS += pkt-line.o +LIB_OBJS += prefix-map.o LIB_OBJS += preload-index.o LIB_OBJS += pretty.o LIB_OBJS += prio-queue.oprefix-map.c new file +35
-0@@ -0,0 +1,111 @@ +#include "cache.h" +#include "prefix-map.h" + +static int map_cmp (const void *unused_cmp_data, +const void *entry, +const void *entry_or_key, +const void *unused_keydata) +{ +const struct prefix_map_entry *a = entry; +const struct prefix_map_entry *b = entry_or_key; + +return a->prefix_length != b->prefix_length || +strncmp (a->name, b->name, a->prefix_length); +} + +static void add_prefix_entry (struct hashmap *map,const char *name, +size_t prefix_length,struct prefix_item *item) +{ +struct prefix_map_entry *result =xmalloc (sizeof (*result)); + result->name = name; + result->prefix_length = prefix_length; + result->item = item; +hashmap_entry_init (result,memhash (name, prefix_length)); +hashmap_add (map, result); +} + +static void init_prefix_map (struct prefix_map *prefix_map, +int min_prefix_length,int max_prefix_length) +{ +hashmap_init (&prefix_map->map, map_cmp,NULL ,0 ); + prefix_map->min_length = min_prefix_length; + prefix_map->max_length = max_prefix_length; +} + +static void add_prefix_item(struct prefix_map *prefix_map,
prefix-map.c line 45
Quoted 10 lines
prefix-map.c new file +10
-0+ struct prefix_item *item) +{ +struct prefix_map_entry *e =xmalloc (sizeof (*e)), *e2; +int j; + + e->item = item; + e->name = e->item->name; + +for (j = prefix_map->min_length; j <= prefix_map->max_length; j++) { +if (!isascii (e->name[j])) {
This feels odd, if I understand the intent.
First, why "isascii()" rather than just non-zero?
But mainly, can we walk off the end of the array and read potentially uninitialized memory? Shouldn't we have something at the top of the function like:
len = strlen(item->name);
if (len < prefix_map->min_length)
return;(And maybe avoid the xmalloc() too?)
And maybe do " j <= min(len, max_length) " in the loop? But I see you're modifying "j" down in the body of the loop, so I'll wait on suggesting that.
Quoted 10 lines
prefix-map.c new file +10
-0+free (e); +break ; + } + + e->prefix_length = j; +hashmap_entry_init (e,memhash (e->name, j)); + e2 =hashmap_get (&prefix_map->map, e,NULL ); +if (!e2) { +/* prefix is unique so far */ + e->item->prefix_length = j;
prefix-map.c line 65
Quoted 10 lines
prefix-map.c new file +10
-0+hashmap_add (&prefix_map->map, e); +break ; + } + +if (!e2->item) +continue ;/* non-unique prefix */ + +if (j != e2->item->prefix_length) +BUG ("unexpected prefix length: %d != %d" , + (int )j, (int )e2->item->prefix_length);
IIUC, this assurance comes directly from map_cmp(), right? We could strengthen this to
(j != e2->item->prefix_length || strncmp(...))
if we wanted to, right?
prefix-map.c line 70
Quoted 5 lines
prefix-map.c new file +5
-0+ +/* skip common prefix */ +for (; j < prefix_map->max_length && e->name[j]; j++) { +if (e->item->name[j] != e2->item->name[j]) +break ;
Same comment here about walking off of the defined end of both arrays.
I'm going to stop here. I'm getting confused.
Quoted 192 lines
prefix-map.c new file +41
-0+add_prefix_entry (&prefix_map->map, e->name, j +1 , +NULL ); + } + +/* e2 no longer refers to a unique prefix */ +if (j < prefix_map->max_length && e2->name[j]) { +/* found a new unique prefix for e2's item */ + e2->item->prefix_length = j +1 ; +add_prefix_entry (&prefix_map->map, e2->name, j +1 , + e2->item); + } +else + e2->item->prefix_length =0 ; + e2->item =NULL ; + +if (j < prefix_map->max_length && e->name[j]) { +/* found a unique prefix for the item */ + e->item->prefix_length = j +1 ; +add_prefix_entry (&prefix_map->map, e->name, j +1 , + e->item); + }else { +/* item has no (short enough) unique prefix */ + e->item->prefix_length =0 ; +free (e); + } + +break ; + } +} + +void find_unique_prefixes (struct prefix_item **list,size_t nr, +int min_length,int max_length) +{ +int i; +struct prefix_map prefix_map; + +init_prefix_map (&prefix_map, min_length, max_length); +for (i =0 ; i < nr; i++) +add_prefix_item (&prefix_map, list[i]); +hashmap_free (&prefix_map.map,1 ); +}prefix-map.h new file +40
-0@@ -0,0 +1,40 @@ +#ifndef PREFIX_MAP_H +#define PREFIX_MAP_H + +#include "hashmap.h" + +struct prefix_item { +const char *name; +size_t prefix_length; +}; + +struct prefix_map_entry { +struct hashmap_entry e; +const char *name; +size_t prefix_length; +/* if item is NULL, the prefix is not unique */ +struct prefix_item *item; +}; + +struct prefix_map { +struct hashmap map; +int min_length, max_length; +}; + +/* +* Find unique prefixes in a given list of strings. +* +* Typically, the `struct prefix_item` information will be but a field in the +* actual item struct; For this reason, the `list` parameter is specified as a +* list of pointers to the items. +* +* The `min_length`/`max_length` parameters define what length the unique +* prefixes should have. +* +* If no unique prefix could be found for a given item, its `prefix_length` +* will be set to 0. +*/ +void find_unique_prefixes (struct prefix_item **list,size_t nr, +int min_length,int max_length); + +#endif t/helper/test-prefix-map.c new file +58
-0@@ -0,0 +1,58 @@ +#include "test-tool.h" +#include "cache.h" +#include "prefix-map.h" + +static size_t test_count, failed_count; + +static void check (int succeeded,const char *file,size_t line_no, +const char *fmt, ...) +{ +va_list ap; + + test_count++; +if (succeeded) +return ; + +va_start (ap, fmt); +fprintf (stderr,"%s:%d: " , file, (int )line_no); +vfprintf (stderr, fmt, ap); +fputc ('\n' , stderr); +va_end (ap); + + failed_count++; +} + +#define EXPECT_SIZE_T_EQUALS (expect, actual, hint) \ + check(expect == actual, __FILE__, __LINE__, \ + "size_t's do not match: %" \ + PRIdMAX " != %" PRIdMAX " (%s) (%s)", \ + (intmax_t)expect, (intmax_t)actual, #actual, hint) + +int cmd__prefix_map (int argc,const char **argv) +{ +#define NR 5 +struct prefix_item items[NR ] = { + {"unique" }, + {"hell" }, + {"hello" }, + {"wok" }, + {"world" }, + }; +struct prefix_item *list[NR ] = { + items, items +1 , items +2 , items +3 , items +4 + }; + +find_unique_prefixes (list,NR ,1 ,3 ); + +#define EXPECT_PREFIX_LENGTH_EQUALS (expect, index) \ + EXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \ + list[index]->name) + +EXPECT_PREFIX_LENGTH_EQUALS (1 ,0 ); +EXPECT_PREFIX_LENGTH_EQUALS (0 ,1 ); +EXPECT_PREFIX_LENGTH_EQUALS (0 ,2 ); +EXPECT_PREFIX_LENGTH_EQUALS (3 ,3 ); +EXPECT_PREFIX_LENGTH_EQUALS (3 ,4 ); + +return !!failed_count; +}t/helper/test-tool.c +1
-0@@ -32,6 +32,7 @@ static struct test_cmd cmds[] = { { "parse-options", cmd__parse_options }, { "path-utils", cmd__path_utils }, { "pkt-line", cmd__pkt_line }, + { "prefix-map", cmd__prefix_map }, { "prio-queue", cmd__prio_queue }, { "reach", cmd__reach }, { "read-cache", cmd__read_cache },t/helper/test-tool.h +1
-0@@ -29,6 +29,7 @@ int cmd__online_cpus(int argc, const char **argv);int cmd__parse_options (int argc,const char **argv);int cmd__path_utils (int argc,const char **argv);int cmd__pkt_line (int argc,const char **argv); +int cmd__prefix_map (int argc,const char **argv);int cmd__prio_queue (int argc,const char **argv);int cmd__reach (int argc,const char **argv);int cmd__read_cache (int argc,const char **argv);t/t0016-prefix-map.sh new file +10
-0@@ -0,0 +1,10 @@ +#!/bin/sh + +test_description='basic tests for prefix map' +. ./test-lib.sh + +test_expect_success 'prefix map' ' + test-tool prefix-map +' + +test_done
Re: [PATCH 01/11] Start to implement a built-in version of `git add --interactive`
In reply to Jeff KingHi Jeff & Jeff,
Quoted 19 lines · Jeff King
Quoted 4 lines · Jeff Hostetler
Currently, neither function looks at any other k/v pairs, so this is a bit of a moot point, but I'm wondering if this should look like this:
Quoted 12 lines
int add_config(...) { // give add-interactive.c a chance to look at k/v pair, but // do not short-cut because we don't know yet whether we // will be interactive or not yet. (void)add_i_config(...); ...ignore_add_errors... ...use_builtin_add_i... return git_default_config(...); }
Quoted 8 lines
Yeah, I agree this split seems a bit more natural. It is worth propagating errors from
add_i_config(), though, like:if (
add_i_config(var, value, data))return -1;so that any key-specific errors (e.g.,
config_error_nonbool) stop the parsing in the usual way.
The only problem there is that add_i_config() (like all the other git_config() callbacks) does not report whether it consumed the key/value pair or not. I tried to avoid deviating from the standard practice to avoid calling git_default_config() when we already consumed the config setting.
And I also tried pretty hard to *not* bleed any internal state of add-interactive into builtin/add, as I wanted the new code to be as libified as possible (in a nearby thread, somebody wished for a new -p mode that would essentially be a combined git stash -p and git add -p, and with properly libified code such a beast is a lot more feasible).
Any idea how to deal with that?
I guess I could invert the order, where add_config() would be called as a fall-back from add_i_config()...
Or I invent a new convention where add_i_config() returns 1 when it consumed the key/value pair. But that would set a precedent that is inconsistent with the entire existing code base, something I am uncomfortable to do for the sake of add -i...
Ciao,
Dscho
Re: [PATCH 01/11] Start to implement a built-in version of `git add --interactive`
Quoted 6 lines · Johannes Schindelin
Quoted 6 lines
Yeah, I agree this split seems a bit more natural. It is worth propagating errors from
add_i_config(), though, like:if (
add_i_config(var, value, data))return -1;
Quoted 8 lines
Quoted 2 lines
so that any key-specific errors (e.g.,
config_error_nonbool) stop the parsing in the usual way.The only problem there is that
add_i_config()(like all the othergit_config()callbacks) does not report whether it consumed the key/value pair or not. I tried to avoid deviating from the standard practice to avoid callinggit_default_config()when we already consumed the config setting.
I don't think it's worth worrying too much about that. We wouldn't match the keys in multiple places anyway (and even if we did, it would arguably be the right thing to give every callback a chance to see them).
The only thing it does is short-circuit the rest of the checks that we know won't match. But that doesn't really change the performance substantially; the worst case is already that we have to hit every possible strcmp().
And most of our config code does not worry about this, and is OK with branching (it just needs to propagate errors, as above). For some more discussion, see 6680a0874f (drop odd return value semantics from userdiff_config, 2012-02-07).
All that said...
Quoted 7 lines
And I also tried pretty hard to *not* bleed any internal state of
add-interactiveintobuiltin/add, as I wanted the new code to be as libified as possible (in a nearby thread, somebody wished for a new-pmode that would essentially be a combinedgit stash -pandgit add -p, and with properly libified code such a beast is a lot more feasible).Any idea how to deal with that?
The most lib-ified thing is to just use the configset code. I.e., wherever you need the config, just load it on demand via git_config_get_int or whatever.
Quoted 4 lines
Or I invent a new convention where
add_i_config()returns 1 when it consumed the key/value pair. But that would set a precedent that is inconsistent with the entire existing code base, something I am uncomfortable to do for the sake ofadd -i...
Yes, don't do that. :) That was the same thing we finally got rid of for userdiff_config().
-Peff
Re: [PATCH 01/11] Start to implement a built-in version of `git add --interactive`
Hi Peff,
Quoted 9 lines · Jeff King
Quoted 7 lines · Johannes Schindelin
And I also tried pretty hard to *not* bleed any internal state of
add-interactiveintobuiltin/add, as I wanted the new code to be as libified as possible (in a nearby thread, somebody wished for a new-pmode that would essentially be a combinedgit stash -pandgit add -p, and with properly libified code such a beast is a lot more feasible).
Quoted 5 lines
Quoted 1 line
Any idea how to deal with that?
The most lib-ified thing is to just use the configset code. I.e., wherever you need the config, just load it on demand via
git_config_get_intor whatever.
True.
And it cost me *quite* a few days to implement the changes. But the result is definitely a lot better, in my opinion.
Quoted 7 lines
Quoted 4 lines
Or I invent a new convention where
add_i_config()returns 1 when it consumed the key/value pair. But that would set a precedent that is inconsistent with the entire existing code base, something I am uncomfortable to do for the sake ofadd -i...Yes, don't do that. :) That was the same thing we finally got rid of for
userdiff_config().
Thanks for stopping me. I did not remember about the userdiff_config() thing.
Ciao,
Dscho
Re: [PATCH 06/11] built-in add -i: implement the main loop
In reply to Jeff HostetlerHi Jeff,
Quoted 22 lines · Jeff Hostetler
Quoted 20 lines · Johannes Schindelin via GitGitGadget
[...]
add-interactive.c +19
-0+ +/* +* Returns the selected index. +*/ +static ssize_t list_and_choose (struct item **items,size_t nr, +struct list_and_choose_options *opts) +{ +struct strbuf input =STRBUF_INIT ; +ssize_t res =-1 ; + +for (;;) { +char *p, *endp; + +strbuf_reset (&input); + +list (items, nr, &opts->list_opts); + +printf ("%s%s" , opts->prompt,"> " ); +fflush (stdout);
Quoted 8 lines
add-interactive.c static void list(struct item **list, size_t nr, struct list_options *opts) · line 122
Quoted 4 lines
add-interactive.c +4
-0+ +if (strbuf_getline (&input, stdin) ==EOF ) { +putchar ('\n' ); + res =-2 ;It would be nice to know what -1 and -2 mean if
they get returned to our caller. Maybe a #define
for these??
Makes a total lot of sense. I changed that.
Thanks!
Dscho
Re: [PATCH 07/11] Add a function to determine unique prefixes for a list of strings
In reply to Jeff HostetlerHi Jeff,
Quoted 85 lines · Jeff Hostetler
Quoted 71 lines · Slavica Djukic via GitGitGadget
From: Slavica Djukic <[email protected]>
In the
git add -icommand, we show unique prefixes of the commands and files, to give an indication what prefix would select them.Naturally, the C implementation looks a lot different than the Perl
implementation: in Perl, a trie is much easier implemented, while we already have a pretty neat hashmap implementation in C that we use for the purpose of storing (not necessarily unique) prefixes.The idea: for each item that we add, we generate prefixes starting with the first letter, then the first two letters, then three, etc, until we find a prefix that is unique (or until the prefix length would be longer than we want). If we encounter a previously-unique prefix on the way, we adjust that item's prefix to make it unique again (or we mark it as having no unique prefix if we failed to find one). These partial prefixes are stored in a hash map (for quick lookup times).
To make sure that this function works as expected, we add a test using a special-purpose test helper that was added for that purpose.
Note: We expect the list of prefix items to be passed in as a list of pointers rather than as regular list to avoid having to copy information (the actual items will most likely contain more information than just the name and the length of the unique prefix, but passing in
struct prefix_item *would not allow for that).Signed-off-by Slavica Djukic <[email protected]>Signed-off-by Johannes Schindelin <[email protected]>---
prefix-map.c new file +35
-0@@ -0,0 +1,111 @@ +#include "cache.h" +#include "prefix-map.h" + +static int map_cmp (const void *unused_cmp_data, +const void *entry, +const void *entry_or_key, +const void *unused_keydata) +{ +const struct prefix_map_entry *a = entry; +const struct prefix_map_entry *b = entry_or_key; + +return a->prefix_length != b->prefix_length || +strncmp (a->name, b->name, a->prefix_length); +} + +static void add_prefix_entry (struct hashmap *map,const char *name, +size_t prefix_length,struct prefix_item *item) +{ +struct prefix_map_entry *result =xmalloc (sizeof (*result)); + result->name = name; + result->prefix_length = prefix_length; + result->item = item; +hashmap_entry_init (result,memhash (name, prefix_length)); +hashmap_add (map, result); +} + +static void init_prefix_map (struct prefix_map *prefix_map, +int min_prefix_length,int max_prefix_length) +{ +hashmap_init (&prefix_map->map, map_cmp,NULL ,0 ); + prefix_map->min_length = min_prefix_length; + prefix_map->max_length = max_prefix_length; +} + +static void add_prefix_item(struct prefix_map *prefix_map,prefix-map.c line 45
Quoted 10 lines
prefix-map.c new file +10
-0+ struct prefix_item *item) +{ +struct prefix_map_entry *e =xmalloc (sizeof (*e)), *e2; +int j; + + e->item = item; + e->name = e->item->name; + +for (j = prefix_map->min_length; j <= prefix_map->max_length; j++) { +if (!isascii (e->name[j])) {This feels odd, if I understand the intent.
Quoted 1 line
First, why "
isascii()" rather than just non-zero?
That's to imitate git-add--interactive.perl's
if (ord($letters[0]) > 127 || ($soft_limit && $j + 1 > $soft_limit))
See https://github.com/git/git/blob/v2.21.0/git-add--interactive.perl#L410
for more complete context.
I think the main benefit here is that we avoid running into the trap of using incomplete UTF-8 multi-byte sequences in prefixes.
I guess we could throw in an extra safety on the C side by excluding control characters, too. But that would be a deviation from Perl, and I actually do not even feel strongly about excluding, say, a HT (horizontal tab) from the prefixes.
Quoted 7 lines
But mainly, can we walk off the end of the array and read potentially uninitialized memory? Shouldn't we have something at the top of the function like:
len = strlen(item->name); if (len < prefix_map->min_length) return;
Ooops, you're right. But I would not use strlen() here, we can easily just add && e->name[j]` to the loop condition.
Quoted 1 line
(And maybe avoid the
xmalloc()too?)
Hmm. At first, I thought: no, we use *e *both* for lookup and for adding a new item once we did not find any existing for the current prefix length.
But it does indeed become a lot clearer when I separate those. It's not even performance or memory critical a code path.
Quoted 27 lines
And maybe do " j <=
min(len, max_length)" in the loop? But I see you're modifying "j" down in the body of the loop, so I'll wait on suggesting that.Quoted 10 lines
prefix-map.c new file +10
-0+free (e); +break ; + } + + e->prefix_length = j; +hashmap_entry_init (e,memhash (e->name, j)); + e2 =hashmap_get (&prefix_map->map, e,NULL ); +if (!e2) { +/* prefix is unique so far */ + e->item->prefix_length = j;prefix-map.c line 65
Quoted 10 lines
prefix-map.c new file +10
-0+hashmap_add (&prefix_map->map, e); +break ; + } + +if (!e2->item) +continue ;/* non-unique prefix */ + +if (j != e2->item->prefix_length) +BUG ("unexpected prefix length: %d != %d" , + (int )j, (int )e2->item->prefix_length);IIUC, this assurance comes directly from
map_cmp(), right? We could strengthen this to
Quoted 2 lines
(j != e2->item->prefix_length || strncmp(...))if we wanted to, right?
Right, I'll actually go for memcmp() here, but the idea is the same.
Quoted 7 lines
prefix-map.c line 70
Quoted 5 lines
prefix-map.c new file +5
-0+ +/* skip common prefix */ +for (; j < prefix_map->max_length && e->name[j]; j++) { +if (e->item->name[j] != e2->item->name[j]) +break ;Same comment here about walking off of the defined end of both arrays.
Actually, no, not here, as I already test for e->name[j] in the loop condition. If we reach the end of e2->item->name, the inner condition will break out of the loop.
Quoted 1 line
I'm going to stop here. I'm getting confused.
Oh no ;-)
Thank you for your helpful comments!
Ciao,
Dscho
PATCH v2 2/11 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 | 37 +++++++++++++++---------------------- diff.h | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+), 22 deletions(-)
diff.c +15 -22
@@ -2489,22 +2489,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b) } } -struct diffstat_t { -int nr; -int alloc; -struct diffstat_file { -char *from_name; -char *name; -char *print_name; -const char *comments; -unsigned is_unmerged:1 ; -unsigned is_binary:1 ; -unsigned is_renamed:1 ; -unsigned is_interesting:1 ; -uintmax_t added, deleted; - } **files; -}; -static struct diffstat_file *diffstat_add (struct diffstat_t *diffstat,const char *name_a,const char *name_b)
@@ -6001,12 +5985,7 @@ void diff_flush(struct diff_options *options) dirstat_by_line) {struct diffstat_t diffstat; -memset (&diffstat,0 ,sizeof (struct diffstat_t )); -for (i =0 ; i < q->nr; i++) { -struct diff_filepair *p = q->queue[i]; -if (check_pair_status (p)) -diff_flush_stat (p, options, &diffstat); - } +compute_diffstat (options, &diffstat, q);if (output_format &DIFF_FORMAT_NUMSTAT )show_numstat (&diffstat, options);if (output_format &DIFF_FORMAT_DIFFSTAT )
@@ -6306,6 +6285,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)return ignored; } +void compute_diffstat (struct diff_options *options, +struct diffstat_t *diffstat, +struct diff_queue_struct *q) +{ +int i; + +memset (diffstat,0 ,sizeof (struct diffstat_t )); +for (i =0 ; i < q->nr; i++) { +struct diff_filepair *p = q->queue[i]; +if (check_pair_status (p)) +diff_flush_stat (p, options, diffstat); + } +} + void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const struct object_id *oid,
diff.h +19 -0
@@ -240,6 +240,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);void diff_emit_submodule_pipethrough (struct diff_options *o,const char *line,int len); +struct diffstat_t { +int nr; +int alloc; +struct diffstat_file { +char *from_name; +char *name; +char *print_name; +const char *comments; +unsigned is_unmerged:1 ; +unsigned is_binary:1 ; +unsigned is_renamed:1 ; +unsigned is_interesting:1 ; +uintmax_t added, deleted; + } **files; +}; +enum color_diff {DIFF_RESET =0 ,DIFF_CONTEXT =1 ,
@@ -328,6 +344,9 @@ void diff_change(struct diff_options *,struct diff_filepair *diff_unmerge (struct diff_options *,const char *path); +void compute_diffstat (struct diff_options *options,struct diffstat_t *diffstat, +struct diff_queue_struct *q); +#define DIFF_SETUP_REVERSE 1#define DIFF_SETUP_USE_SIZE_CACHE 4
Signature · 1 line
gitgitgadget
v2 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 weeks already (it is so nice to not suffer over one second startup until the MSYS2 Perl finally shows me anything, instead it feels instantaneous), I integrated these patch series into Git for Windows' master already, as an opt-in feature guarded by the config variable add.interactive.useBuiltin (and Git for Windows' installer is prepared to detect this version and offer the option in the graphical user interface).
I had planned on submitting this before v2.22.0-rc0, but there was such a backlog of builds from a big pushout that I had to wait ;-)
Changes since v1:
* The config machinery was reworked completely, to not use a callback to git_config(), but instead to query the config via the repo_config_get_*() functions. This also prevents a future "Huh???" moment: the internal add --interactive API accepts a parameter of type struct repository *r, but the previous configuration did not use that to query the config (and could in the future be a repository other than the_repository).
* As a consequence, the color sequences are no longer stored in file-local variables, but passed around via a struct.
* Instead of using the magical constant -2 to quit the main loop, it is now defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is defined as -1 and used where appropriate).
* Improved the add_prefix_item() function by avoiding buffer overruns, not reusing the struct that is used for lookup also for adding the new item, and by strengthening the bug check.
Daniel Ferreira (2):
diff: export diffstat interface
built-in add -i: implement the status command
Johannes Schindelin (6):
Start to implement a built-in version of git add --interactive built-in add -i: refresh the index before running status built-in add -i: color the header in the status command built-in add -i: implement the main loop
built-in add -i: support ? (prompt help)
built-in add -i: implement the help command
Slavica Djukic (3):
Add a function to determine unique prefixes for a list of strings built-in add -i: show unique prefixes of the commands built-in add -i: use color in the main loop
Documentation/config/add.txt | 5 + Makefile | 3 + add-interactive.c | 558 +++++++++++++++++++++++++++++++++++ add-interactive.h | 8 + builtin/add.c | 10 + diff.c | 37 +-- diff.h | 19 ++ prefix-map.c | 109 +++++++ prefix-map.h | 40 +++ repository.c | 19 ++ repository.h | 7 + t/README | 4 + t/helper/test-prefix-map.c | 58 ++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0016-prefix-map.sh | 10 + t/t3701-add-interactive.sh | 24 ++ 17 files changed, 891 insertions(+), 22 deletions(-) create mode 100644 add-interactive.c create mode 100644 add-interactive.h create mode 100644 prefix-map.c create mode 100644 prefix-map.h create mode 100644 t/helper/test-prefix-map.c create mode 100755 t/t0016-prefix-map.sh
base-commit: 8104ec994ea3849a968b4667d072fedd1e688642
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/170
Range-diff vs v1:
1: 12978dc248 ! 1: ed53346b92 Start to implement a built-in version of `git add --interactive`
@@ -62,12 +62,6 @@
@@
+#include "cache.h"
+#include "add-interactive.h"
-+#include "config.h"
-+
-+int add_i_config(const char *var, const char *value, void *cb)
-+{
-+ return git_default_config(var, value, cb);
-+}
+
+int run_add_i(struct repository *r, const struct pathspec *ps)
+{
@@ -82,8 +76,6 @@
+#ifndef ADD_INTERACTIVE_H
+#define ADD_INTERACTIVE_H
+
-+int add_i_config(const char *var, const char *value, void *cb);
-+
+struct repository;
+struct pathspec;
+int run_add_i(struct repository *r, const struct pathspec *ps);
@@ -102,50 +94,28 @@
static const char * const builtin_add_usage[] = {
N_("git add [<options>] [--] <pathspec>..."),
@@
- static int patch_interactive, add_interactive, edit_interactive;
- static int take_worktree_changes;
- static int add_renormalize;
-+static int use_builtin_add_i;
-
- struct update_callback_data {
- int flags;
-@@
+ {
int status, i;
struct argv_array argv = ARGV_ARRAY_INIT;
-
-+ if (use_builtin_add_i && !patch_mode)
-+ return !!run_add_i(the_repository, pathspec);
++ int use_builtin_add_i =
++ git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
++ if (use_builtin_add_i < 0)
++ git_config_get_bool("add.interactive.usebuiltin",
++ &use_builtin_add_i);
+
++ if (use_builtin_add_i == 1 && !patch_mode)
++ return !!run_add_i(the_repository, pathspec);
+
argv_array_push(&argv, "add--interactive");
if (patch_mode)
- argv_array_push(&argv, patch_mode);
@@
ignore_add_errors = git_config_bool(var, value);
return 0;
}
-- return git_default_config(var, value, cb);
-+ if (!strcmp(var, "add.interactive.usebuiltin")) {
-+ use_builtin_add_i = git_config_bool(var, value);
-+ return 0;
-+ }
+
-+ return add_i_config(var, value, cb);
+ return git_default_config(var, value, cb);
} - static const char embedded_advice[] = N_(
-@@
- int require_pathspec;
- char *seen = NULL;
- struct lock_file lock_file = LOCK_INIT;
-+ int use_builtin_add_i_env =
-+ git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
-
- git_config(add_config, NULL);
-+ if (use_builtin_add_i_env >= 0)
-+ use_builtin_add_i = use_builtin_add_i_env;
-
- argc = parse_options(argc, argv, prefix, builtin_add_options,
- builtin_add_usage, PARSE_OPT_KEEP_ARGV0);
diff --git a/t/README b/t/README
--- a/t/README
2: 06ba1ae344 = 2: bc99009fbf diff: export diffstat interface
3: be9ab904d1 ! 3: 5e23c0756b built-in add -i: implement the `status` command
@@ -30,16 +30,10 @@
@@
#include "cache.h"
#include "add-interactive.h"
- #include "config.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "refs.h"
-
- int add_i_config(const char *var, const char *value, void *cb)
- {
- return git_default_config(var, value, cb);
- }
-
++
+struct item {
+ const char *name;
+};
@@ -276,7 +270,7 @@
+
+ return 0;
+}
-+
+
int run_add_i(struct repository *r, const struct pathspec *ps)
{
- die(_("No commands are available in the built-in `git add -i` yet!"));
4: a512e14609 = 4: 8cafc6ae8d built-in add -i: refresh the index before running `status`
5: f7c8df058c < -: ---------- built-in add -i: color the header in the `status` command
-: ---------- > 5: 83d92a9762 built-in add -i: color the header in the `status` command
6: 93b3151b6c ! 6: 3eec219124 built-in add -i: implement the main loop
@@ -53,8 +53,8 @@
void (*print_item)(int i, struct item *item, void *print_item_data);
void *print_item_data;
@@
-
- static void list(struct item **list, size_t nr, struct list_options *opts)
+ static void list(struct item **list, size_t nr,
+ struct add_i_state *s, struct list_options *opts)
{
- int i;
+ int i, last_lf = 0;
@@ -85,28 +85,35 @@
+ const char *prompt;
+};
+
++#define LIST_AND_CHOOSE_ERROR (-1)
++#define LIST_AND_CHOOSE_QUIT (-2)
++
+/*
+ * Returns the selected index.
++ *
++ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
++ * `LIST_AND_CHOOSE_QUIT` is returned.
+ */
+static ssize_t list_and_choose(struct item **items, size_t nr,
++ struct add_i_state *s,
+ struct list_and_choose_options *opts)
+{
+ struct strbuf input = STRBUF_INIT;
-+ ssize_t res = -1;
++ ssize_t res = LIST_AND_CHOOSE_ERROR;
+
+ for (;;) {
+ char *p, *endp;
+
+ strbuf_reset(&input);
+
-+ list(items, nr, &opts->list_opts);
++ list(items, nr, s, &opts->list_opts);
+
+ printf("%s%s", opts->prompt, "> ");
+ fflush(stdout);
+
+ if (strbuf_getline(&input, stdin) == EOF) {
+ putchar('\n');
-+ res = -2;
++ res = LIST_AND_CHOOSE_QUIT;
+ break;
+ }
+ strbuf_trim(&input);
@@ -143,7 +150,7 @@
+ p += sep + 1;
+ }
+
-+ if (res >= 0)
++ if (res != LIST_AND_CHOOSE_ERROR)
+ break;
}
+
@@ -164,12 +171,13 @@
+
+struct command_item {
+ struct item item;
-+ int (*command)(struct repository *r, const struct pathspec *ps,
++ int (*command)(struct add_i_state *s, const struct pathspec *ps,
+ struct file_list *files, struct list_options *opts);
+};
+
int run_add_i(struct repository *r, const struct pathspec *ps)
{
+ struct add_i_state s = { NULL };
+ struct list_and_choose_options main_loop_opts = {
+ { 4, N_("*** Commands ***"), print_command_item, NULL },
+ N_("What now")
@@ -192,21 +200,21 @@
+ ssize_t i;
int res = 0;
- strbuf_addstr(&header, " ");
+ if (init_add_i_state(r, &s))
@@
- if (run_status(r, ps, &files, &opts) < 0)
+ if (run_status(&s, ps, &files, &opts) < 0)
res = -1;
+ for (;;) {
+ i = list_and_choose((struct item **)commands,
-+ ARRAY_SIZE(commands), &main_loop_opts);
-+ if (i < -1) {
++ ARRAY_SIZE(commands), &s, &main_loop_opts);
++ if (i == LIST_AND_CHOOSE_QUIT) {
+ printf(_("Bye.\n"));
+ res = 0;
+ break;
+ }
-+ if (i >= 0)
-+ res = commands[i]->command(r, ps, &files, &opts);
++ if (i != LIST_AND_CHOOSE_ERROR)
++ res = commands[i]->command(&s, ps, &files, &opts);
+ }
+
release_file_list(&files);
7: db1ede3636 ! 7: e02a52c3ac Add a function to determine unique prefixes for a list of strings
@@ -92,40 +92,40 @@
+static void add_prefix_item(struct prefix_map *prefix_map,
+ struct prefix_item *item)
+{
-+ struct prefix_map_entry *e = xmalloc(sizeof(*e)), *e2;
++ struct prefix_map_entry e = { { NULL } }, *e2;
+ int j;
+
-+ e->item = item;
-+ e->name = e->item->name;
++ e.item = item;
++ e.name = item->name;
+
-+ for (j = prefix_map->min_length; j <= prefix_map->max_length; j++) {
-+ if (!isascii(e->name[j])) {
-+ free(e);
++ for (j = prefix_map->min_length;
++ j <= prefix_map->max_length && e.name[j]; j++) {
++ /* Avoid breaking UTF-8 multi-byte sequences */
++ if (!isascii(e.name[j]))
+ break;
-+ }
+
-+ e->prefix_length = j;
-+ hashmap_entry_init(e, memhash(e->name, j));
-+ e2 = hashmap_get(&prefix_map->map, e, NULL);
++ e.prefix_length = j;
++ hashmap_entry_init(&e, memhash(e.name, j));
++ e2 = hashmap_get(&prefix_map->map, &e, NULL);
+ if (!e2) {
-+ /* prefix is unique so far */
-+ e->item->prefix_length = j;
-+ hashmap_add(&prefix_map->map, e);
++ /* prefix is unique at this stage */
++ item->prefix_length = j;
++ add_prefix_entry(&prefix_map->map, e.name, j, item);
+ break;
+ }
+
+ if (!e2->item)
+ continue; /* non-unique prefix */
+
-+ if (j != e2->item->prefix_length)
-+ BUG("unexpected prefix length: %d != %d",
-+ (int)j, (int)e2->item->prefix_length);
++ if (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))
++ BUG("unexpected prefix length: %d != %d (%s != %s)",
++ j, (int)e2->item->prefix_length, e.name, e2->name);
+
+ /* skip common prefix */
-+ for (; j < prefix_map->max_length && e->name[j]; j++) {
-+ if (e->item->name[j] != e2->item->name[j])
++ for (; j < prefix_map->max_length && e.name[j]; j++) {
++ if (e.item->name[j] != e2->item->name[j])
+ break;
-+ add_prefix_entry(&prefix_map->map, e->name, j + 1,
++ add_prefix_entry(&prefix_map->map, e.name, j + 1,
+ NULL);
+ }
+
@@ -140,16 +140,14 @@
+ e2->item->prefix_length = 0;
+ e2->item = NULL;
+
-+ if (j < prefix_map->max_length && e->name[j]) {
++ if (j < prefix_map->max_length && e.name[j]) {
+ /* found a unique prefix for the item */
-+ e->item->prefix_length = j + 1;
-+ add_prefix_entry(&prefix_map->map, e->name, j + 1,
-+ e->item);
-+ } else {
++ e.item->prefix_length = j + 1;
++ add_prefix_entry(&prefix_map->map, e.name, j + 1,
++ e.item);
++ } else
+ /* item has no (short enough) unique prefix */
-+ e->item->prefix_length = 0;
-+ free(e);
-+ }
++ e.item->prefix_length = 0;
+
+ break;
+ }
8: 56acc31e96 ! 8: ced9b6aced built-in add -i: show unique prefixes of the commands
@@ -22,10 +22,10 @@
#include "refs.h"
+#include "prefix-map.h"
- static int use_color = -1;
-
+ struct add_i_state {
+ struct repository *r;
@@
- return git_color_default_config(var, value, cb);
+ return 0;
}
-struct item {
@@ -57,22 +57,22 @@
void *print_item_data;
};
--static void list(struct item **list, size_t nr, struct list_options *opts)
+-static void list(struct item **list, size_t nr,
+static void list(struct prefix_item **list, size_t nr,
-+ struct list_options *opts)
+ struct add_i_state *s, struct list_options *opts)
{
int i, last_lf = 0;
-
@@
- /*
- * Returns the selected index.
+ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
+ * `LIST_AND_CHOOSE_QUIT` is returned.
*/
-static ssize_t list_and_choose(struct item **items, size_t nr,
+static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
+ struct add_i_state *s,
struct list_and_choose_options *opts)
{
struct strbuf input = STRBUF_INIT;
- ssize_t res = -1;
+ ssize_t res = LIST_AND_CHOOSE_ERROR;
+ find_unique_prefixes(items, nr, 1, 4);
+
@@ -133,8 +133,8 @@
return -1;
if (files->nr)
-- list((struct item **)files->file, files->nr, opts);
-+ list((struct prefix_item **)files->file, files->nr, opts);
+- list((struct item **)files->file, files->nr, s, opts);
++ list((struct prefix_item **)files->file, files->nr, s, opts);
putchar('\n');
return 0;
@@ -157,7 +157,7 @@
struct command_item {
- struct item item;
+ struct prefix_item item;
- int (*command)(struct repository *r, const struct pathspec *ps,
+ int (*command)(struct add_i_state *s, const struct pathspec *ps,
struct file_list *files, struct list_options *opts);
};
@@
@@ -166,6 +166,6 @@
for (;;) {
- i = list_and_choose((struct item **)commands,
+ i = list_and_choose((struct prefix_item **)commands,
- ARRAY_SIZE(commands), &main_loop_opts);
- if (i < -1) {
+ ARRAY_SIZE(commands), &s, &main_loop_opts);
+ if (i == LIST_AND_CHOOSE_QUIT) {
printf(_("Bye.\n"));
9: ddd0ee51f6 ! 9: 7378af60ad built-in add -i: support `?` (prompt help)
@@ -12,41 +12,35 @@
--- a/add-interactive.c
+++ b/add-interactive.c
@@
-
- enum color_add_i {
- COLOR_HEADER = 0,
-+ COLOR_HELP,
- };
-
- static char list_colors[][COLOR_MAXLEN] = {
- GIT_COLOR_BOLD, /* Header */
-+ GIT_COLOR_BOLD_RED, /* Help */
+ struct repository *r;
+ int use_color;
+ char header_color[COLOR_MAXLEN];
++ char help_color[COLOR_MAXLEN];
};
- static const char *get_add_i_color(enum color_add_i ix)
+ static void init_color(struct repository *r, struct add_i_state *s,
@@
- {
- if (!strcasecmp(slot, "header"))
- return COLOR_HEADER;
-+ if (!strcasecmp(slot, "help"))
-+ return COLOR_HELP;
+ s->use_color = want_color(s->use_color);
+
+ init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
++ init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
- return -1;
+ return 0;
}
@@
struct list_options list_opts;
const char *prompt;
-+ void (*print_help)(void);
++ void (*print_help)(struct add_i_state *s);
};
- /*
+ #define LIST_AND_CHOOSE_ERROR (-1)
@@
if (!input.len)
break;
+ if (!strcmp(input.buf, "?")) {
-+ opts->print_help();
++ opts->print_help(s);
+ continue;
+ }
+
@@ -57,9 +51,9 @@
struct file_list *files, struct list_options *opts);
};
-+static void command_prompt_help(void)
++static void command_prompt_help(struct add_i_state *s)
+{
-+ const char *help_color = get_add_i_color(COLOR_HELP);
++ const char *help_color = s->help_color;
+ color_fprintf_ln(stdout, help_color, "%s", _("Prompt help:"));
+ color_fprintf_ln(stdout, help_color, "1 - %s",
+ _("select a numbered item"));
@@ -71,6 +65,7 @@
+
int run_add_i(struct repository *r, const struct pathspec *ps)
{
+ struct add_i_state s = { NULL };
struct list_and_choose_options main_loop_opts = {
{ 4, N_("*** Commands ***"), print_command_item, NULL },
- N_("What now")
10: 3e1a88d9c0 ! 10: 36edef85e6 built-in add -i: use color in the main loop
@@ -12,51 +12,31 @@
--- a/add-interactive.c
+++ b/add-interactive.c
@@
- enum color_add_i {
- COLOR_HEADER = 0,
- COLOR_HELP,
-+ COLOR_PROMPT,
-+ COLOR_ERROR,
-+ COLOR_RESET,
+ int use_color;
+ char header_color[COLOR_MAXLEN];
+ char help_color[COLOR_MAXLEN];
++ char prompt_color[COLOR_MAXLEN];
++ char error_color[COLOR_MAXLEN];
++ char reset_color[COLOR_MAXLEN];
};
- static char list_colors[][COLOR_MAXLEN] = {
- GIT_COLOR_BOLD, /* Header */
- GIT_COLOR_BOLD_RED, /* Help */
-+ GIT_COLOR_BOLD_BLUE, /* Prompt */
-+ GIT_COLOR_BOLD_RED, /* Error */
-+ GIT_COLOR_RESET, /* Reset */
- };
-
- static const char *get_add_i_color(enum color_add_i ix)
+ static void init_color(struct repository *r, struct add_i_state *s,
@@
- return COLOR_HEADER;
- if (!strcasecmp(slot, "help"))
- return COLOR_HELP;
-+ if (!strcasecmp(slot, "prompt"))
-+ return COLOR_PROMPT;
-+ if (!strcasecmp(slot, "error"))
-+ return COLOR_ERROR;
-+ if (!strcasecmp(slot, "reset"))
-+ return COLOR_RESET;
- return -1;
- }
-@@
- static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
- struct list_and_choose_options *opts)
- {
-+ const char *prompt_color = get_add_i_color(COLOR_PROMPT);
-+ const char *error_color = get_add_i_color(COLOR_ERROR);
- struct strbuf input = STRBUF_INIT;
- ssize_t res = -1;
+ init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
+ init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
++ init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE);
++ init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED);
++ init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET);
+ return 0;
+ }
@@
- list(items, nr, &opts->list_opts);
+ list(items, nr, s, &opts->list_opts);
- printf("%s%s", opts->prompt, "> ");
-+ color_fprintf(stdout, prompt_color, "%s", opts->prompt);
++ color_fprintf(stdout, s->prompt_color, "%s", opts->prompt);
+ fputs("> ", stdout);
fflush(stdout);
@@ -66,7 +46,7 @@
if (index < 0 || index >= nr)
- printf(_("Huh (%s)?\n"), p);
-+ color_fprintf_ln(stdout, error_color,
++ color_fprintf_ln(stdout, s->error_color,
+ _("Huh (%s)?"), p);
else {
res = index;
@@ -96,20 +76,32 @@
}
@@
-
int run_add_i(struct repository *r, const struct pathspec *ps)
{
-+ struct print_command_item_data data = {
-+ /*
-+ * When color was asked for, use the prompt color for
-+ * highlighting, otherwise use square brackets.
-+ */
-+ want_color(use_color) ? get_add_i_color(COLOR_PROMPT) : "[",
-+ want_color(use_color) ? get_add_i_color(COLOR_RESET) : "]"
-+ };
+ struct add_i_state s = { NULL };
++ struct print_command_item_data data;
struct list_and_choose_options main_loop_opts = {
- { 4, N_("*** Commands ***"), print_command_item, NULL },
+ { 4, N_("*** Commands ***"), print_command_item, &data },
N_("What now"), command_prompt_help
};
struct command_item
+@@
+ if (init_add_i_state(r, &s))
+ return error("could not parse `add -i` config");
+
++ /*
++ * When color was asked for, use the prompt color for
++ * highlighting, otherwise use square brackets.
++ */
++ if (s.use_color) {
++ data.color = s.prompt_color;
++ data.reset = s.reset_color;
++ } else {
++ data.color = "[";
++ data.reset = "]";
++ }
++
+ strbuf_addstr(&header, " ");
+ strbuf_addf(&header, print_file_item_data.modified_fmt,
+ _("staged"), _("unstaged"), _("path"));
11: 481e3316d5 ! 11: 266dbf2a6b built-in add -i: implement the `help` command
@@ -18,10 +18,10 @@
return 0;
}
-+static int run_help(struct repository *r, const struct pathspec *ps,
++static int run_help(struct add_i_state *s, const struct pathspec *ps,
+ struct file_list *files, struct list_options *opts)
+{
-+ const char *help_color = get_add_i_color(COLOR_HELP);
++ const char *help_color = s->help_color;
+
+ color_fprintf_ln(stdout, help_color, "status - %s",
+ _("show paths with changes"));Signature · 1 line
gitgitgadget
PATCH v2 4/11 built-in add -i: refresh the index before running `status`
From: Johannes Schindelin <[email protected]>
This is what the Perl version does, and therefore it is what the built-in version should do, too.
--- add-interactive.c | 4 +++- repository.c | 19 +++++++++++++++++++ repository.h | 7 +++++++ 3 files changed, 29 insertions(+), 1 deletion(-)
add-interactive.c +3 -1
@@ -258,7 +258,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps) _("staged"), _("unstaged"), _("path")); opts.header = header.buf; - res =run_status (r, ps, &files, &opts); +repo_refresh_and_write_index (r,REFRESH_QUIET ,1 ); +if (run_status (r, ps, &files, &opts) <0 ) + res =-1 ;release_file_list (&files);strbuf_release (&print_file_item_data.buf);
repository.c +19 -0
@@ -272,3 +272,22 @@ int repo_hold_locked_index(struct repository *repo,BUG ("the repo hasn't been setup" );return hold_lock_file_for_update (lf, repo->index_file, flags); } + +int repo_refresh_and_write_index (struct repository *r, +unsigned int flags,int gentle) +{ +struct lock_file lock_file =LOCK_INIT ; +int fd; + +if (repo_read_index_preload (r,NULL ,0 ) <0 ) +return error (_ ("could not read index" )); + fd =repo_hold_locked_index (r, &lock_file,0 ); +if (!gentle && fd <0 ) +return error (_ ("could not lock index for writing" )); +refresh_index (r->index, flags,NULL ,NULL ,NULL ); +if (0 <= fd) +repo_update_index_if_able (r, &lock_file); +rollback_lock_file (&lock_file); + +return 0 ; +}
repository.h +7 -0
@@ -154,5 +154,12 @@ int repo_read_index_unmerged(struct repository *);*/ void repo_update_index_if_able (struct repository *,struct lock_file *); +/* +* Refresh the index and write it out. If the index file could not be +* locked, error out, except in gentle mode. The flags will be passed +* through to refresh_index(). +*/ +int repo_refresh_and_write_index (struct repository *r, +unsigned int flags,int gentle);#endif /* REPOSITORY_H */
Signature · 1 line
gitgitgadget
PATCH v2 6/11 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.
Apart from the "and choose" part, there are more differences between the way the status command calls the list_and_choose() function in the Perl version of git add -i compared to the other callers of said function. The most important ones:
- The list is not only shown, but the user is also asked to make a choice, possibly selecting multiple entries.
- The list of items is prefixed with a marker indicating what items have been selected, if multi-selection is allowed.
- Initially, for each item a unique prefix (if there exists any within the given parameters) is determined, and shown in the list, and accepted as a shortcut for the selection.
These features will be implemented later, except the part where the user can choose a command. At this stage, though, the built-in git add -i still only supports the status command, with the remaining commands to follow over the course of the next commits.
In addition, we also modify list() to support displaying the commands in columns, even if there is currently only one.
The Perl script git-add--interactive.perl mixed the purposes of the "list" and the "and choose" part into the same function. In the C version, we will keep them separate instead, calling the list() function from the list_and_choose() function.
Note that we only have a prompt ending in a single ">" at this stage; later commits will add commands that display a double ">>" to indicate that the user is in a different loop than the main one.
--- add-interactive.c | 129 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 2 deletions(-)
add-interactive.c +127 -2
@@ -51,6 +51,7 @@ struct item { };struct list_options { +int columns;const char *header;void (*print_item)(int i,struct item *item,void *print_item_data);void *print_item_data;
@@ -59,7 +60,7 @@ struct list_options {static void list (struct item **list,size_t nr,struct add_i_state *s,struct list_options *opts) { -int i; +int i, last_lf =0 ;if (!nr)return ;
@@ -70,8 +71,97 @@ static void list(struct item **list, size_t nr,for (i =0 ; i < nr; i++) { opts->print_item (i, list[i], opts->print_item_data); + +if ((opts->columns) && ((i +1 ) % (opts->columns))) { +putchar ('\t' ); + last_lf =0 ; + } +else { +putchar ('\n' ); + last_lf =1 ; + } + } + +if (!last_lf)putchar ('\n' ); +} +struct list_and_choose_options { +struct list_options list_opts; + +const char *prompt; +}; + +#define LIST_AND_CHOOSE_ERROR (-1) +#define LIST_AND_CHOOSE_QUIT (-2) + +/* +* Returns the selected index. +* +* If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF, +* `LIST_AND_CHOOSE_QUIT` is returned. +*/ +static ssize_t list_and_choose (struct item **items,size_t nr, +struct add_i_state *s, +struct list_and_choose_options *opts) +{ +struct strbuf input =STRBUF_INIT ; +ssize_t res =LIST_AND_CHOOSE_ERROR ; + +for (;;) { +char *p, *endp; + +strbuf_reset (&input); + +list (items, nr, s, &opts->list_opts); + +printf ("%s%s" , opts->prompt,"> " ); +fflush (stdout); + +if (strbuf_getline (&input, stdin) ==EOF ) { +putchar ('\n' ); + res =LIST_AND_CHOOSE_QUIT ; +break ; + } +strbuf_trim (&input); + +if (!input.len) +break ; + + p = input.buf; +for (;;) { +size_t sep =strcspn (p," \t\r\n," ); +ssize_t index =-1 ; + +if (!sep) { +if (!*p) +break ; + p++; +continue ; + } + +if (isdigit (*p)) { + index =strtoul (p, &endp,10 ) -1 ; +if (endp != p + sep) + index =-1 ; + } + + p[sep] ='\0' ; +if (index <0 || index >= nr) +printf (_ ("Huh (%s)?\n" ), p); +else { + res = index; +break ; + } + + p += sep +1 ; + } + +if (res !=LIST_AND_CHOOSE_ERROR ) +break ; } + +strbuf_release (&input); +return res; }struct adddel {
@@ -285,17 +375,40 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return 0 ; } +static void print_command_item (int i,struct item *item, +void *print_command_item_data) +{ +printf (" %2d: %s" , i +1 , item->name); +} + +struct command_item { +struct item item; +int (*command)(struct add_i_state *s,const struct pathspec *ps, +struct file_list *files,struct list_options *opts); +}; +int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL }; +struct list_and_choose_options main_loop_opts = { + {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, +N_ ("What now" ) + }; +struct command_item + status = { {"status" }, run_status }; +struct command_item *commands[] = { + &status + }; +struct print_file_item_data print_file_item_data = {"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT };struct list_options opts = { -NULL , print_file_item, &print_file_item_data +0 ,NULL , print_file_item, &print_file_item_data };struct strbuf header =STRBUF_INIT ;struct file_list files = {NULL }; +ssize_t i;int res =0 ;if (init_add_i_state (r, &s))
@@ -310,6 +423,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)if (run_status (&s, ps, &files, &opts) <0 ) res =-1 ; +for (;;) { + i =list_and_choose ((struct item **)commands, +ARRAY_SIZE (commands), &s, &main_loop_opts); +if (i ==LIST_AND_CHOOSE_QUIT ) { +printf (_ ("Bye.\n" )); + res =0 ; +break ; + } +if (i !=LIST_AND_CHOOSE_ERROR ) + res = commands[i]->command (&s, ps, &files, &opts); + } +release_file_list (&files);strbuf_release (&print_file_item_data.buf);strbuf_release (&print_file_item_data.index);
Signature · 1 line
gitgitgadget
PATCH v2 8/11 built-in add -i: show unique prefixes of the commands
In reply to Johannes Schindelin via GitGitGadgetFrom: Slavica Djukic <[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.
We use the prefix map implementation that we just added in the previous commit for that purpose.
--- add-interactive.c | 69 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 13 deletions(-)
add-interactive.c +56 -13
@@ -5,6 +5,7 @@#include "diffcore.h" #include "revision.h" #include "refs.h" +#include "prefix-map.h" struct add_i_state {struct repository *r;
@@ -46,18 +47,32 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)return 0 ; } -struct item { -const char *name; -}; +static ssize_t find_unique (const char *string, +struct prefix_item **list,size_t nr) +{ +ssize_t found =-1 , i; + +for (i =0 ; i < nr; i++) { +struct prefix_item *item = list[i]; +if (!starts_with (item->name, string)) +continue ; +if (found >=0 ) +return -1 ; + found = i; + } + +return found; +}struct list_options {int columns;const char *header; -void (*print_item)(int i,struct item *item,void *print_item_data); +void (*print_item)(int i,struct prefix_item *item, +void *print_item_data);void *print_item_data; }; -static void list (struct item **list,size_t nr, +static void list (struct prefix_item **list,size_t nr,struct add_i_state *s,struct list_options *opts) {int i, last_lf =0 ;
@@ -100,13 +115,15 @@ struct list_and_choose_options {* If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF, * `LIST_AND_CHOOSE_QUIT` is returned. */ -static ssize_t list_and_choose (struct item **items,size_t nr, +static ssize_t list_and_choose (struct prefix_item **items,size_t nr,struct add_i_state *s,struct list_and_choose_options *opts) {struct strbuf input =STRBUF_INIT ;ssize_t res =LIST_AND_CHOOSE_ERROR ; +find_unique_prefixes (items, nr,1 ,4 ); +for (;;) {char *p, *endp;
@@ -146,6 +163,9 @@ static ssize_t list_and_choose(struct item **items, size_t nr, } p[sep] ='\0' ; +if (index <0 ) + index =find_unique (p, items, nr); +if (index <0 || index >= nr)printf (_ ("Huh (%s)?\n" ), p);else {
@@ -171,7 +191,7 @@ struct adddel {struct file_list {struct file_item { -struct item item; +struct prefix_item item;struct adddel index, worktree; } **file;size_t nr, alloc;
@@ -337,12 +357,29 @@ static void populate_wi_changes(struct strbuf *buf,strbuf_addstr (buf, no_changes); } +/* filters out prefixes which have special meaning to list_and_choose() */ +static int is_valid_prefix (const char *prefix,size_t prefix_len) +{ +return prefix_len && prefix && +/* +* We expect `prefix` to be NUL terminated, therefore this +* `strcspn()` call is okay, even if it might do much more +* work than strictly necessary. +*/ +strcspn (prefix," \t\r\n," ) >= prefix_len &&/* separators */ + *prefix !='-' &&/* deselection */ + !isdigit (*prefix) &&/* selection */ + (prefix_len !=1 || + (*prefix !='*' &&/* "all" wildcard */ + *prefix !='?' ));/* prompt help */ +} +struct print_file_item_data {const char *modified_fmt;struct strbuf buf, index, worktree; }; -static void print_file_item (int i,struct item *item, +static void print_file_item (int i,struct prefix_item *item,void *print_file_item_data) {struct file_item *c = (struct file_item *)item;
@@ -369,20 +406,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return -1 ;if (files->nr) -list ((struct item **)files->file, files->nr, s, opts); +list ((struct prefix_item **)files->file, files->nr, s, opts);putchar ('\n' );return 0 ; } -static void print_command_item (int i,struct item *item, +static void print_command_item (int i,struct prefix_item *item,void *print_command_item_data) { -printf (" %2d: %s" , i +1 , item->name); +if (!item->prefix_length || + !is_valid_prefix (item->name, item->prefix_length)) +printf (" %2d: %s" , i +1 , item->name); +else +printf (" %3d: [%.*s]%s" , i +1 , + (int )item->prefix_length, item->name, + item->name + item->prefix_length); }struct command_item { -struct item item; +struct prefix_item item;int (*command)(struct add_i_state *s,const struct pathspec *ps,struct file_list *files,struct list_options *opts); };
@@ -424,7 +467,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps) res =-1 ;for (;;) { - i =list_and_choose ((struct item **)commands, + i =list_and_choose ((struct prefix_item **)commands,ARRAY_SIZE (commands), &s, &main_loop_opts);if (i ==LIST_AND_CHOOSE_QUIT ) {printf (_ ("Bye.\n" ));
Signature · 1 line
gitgitgadget
PATCH v2 11/11 built-in add -i: implement the `help` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[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 | 27 +++++++++++++++++++++++++-- t/t3701-add-interactive.sh | 24 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-)
add-interactive.c +25 -2
@@ -428,6 +428,27 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return 0 ; } +static int run_help (struct add_i_state *s,const struct pathspec *ps, +struct file_list *files,struct list_options *opts) +{ +const char *help_color = s->help_color; + +color_fprintf_ln (stdout, help_color,"status - %s" , +_ ("show paths with changes" )); +color_fprintf_ln (stdout, help_color,"update - %s" , +_ ("add working tree state to the staged set of changes" )); +color_fprintf_ln (stdout, help_color,"revert - %s" , +_ ("revert staged set of changes back to the HEAD version" )); +color_fprintf_ln (stdout, help_color,"patch - %s" , +_ ("pick hunks and update selectively" )); +color_fprintf_ln (stdout, help_color,"diff - %s" , +_ ("view diff between HEAD and index" )); +color_fprintf_ln (stdout, help_color,"add untracked - %s" , +_ ("add contents of untracked files to the staged set of changes" )); + +return 0 ; +} +struct print_command_item_data {const char *color, *reset; };
@@ -473,9 +494,11 @@ int run_add_i(struct repository *r, const struct pathspec *ps)N_ ("What now" ), command_prompt_help };struct command_item - status = { {"status" }, run_status }; + status = { {"status" }, run_status }, + help = { {"help" }, run_help };struct command_item *commands[] = { - &status + &status, + &help }; struct print_file_item_data print_file_item_data = {
t/t3701-add-interactive.sh +24 -0
@@ -639,4 +639,28 @@ test_expect_success 'add -p patch editing works with pathological context lines' test_cmp expected-2 actual ' +test_expect_success 'show help from add--helper' ' + git reset --hard && + cat >expect <<-EOF && + + <BOLD>*** Commands ***<RESET> + 1: <BOLD;BLUE>s<RESET>tatus 2: <BOLD;BLUE>u<RESET>pdate 3: <BOLD;BLUE>r<RESET>evert 4: <BOLD;BLUE>a<RESET>dd untracked + 5: <BOLD;BLUE>p<RESET>atch 6: <BOLD;BLUE>d<RESET>iff 7: <BOLD;BLUE>q<RESET>uit 8: <BOLD;BLUE>h<RESET>elp + <BOLD;BLUE>What now<RESET>> <BOLD;RED>status - show paths with changes<RESET> + <BOLD;RED>update - add working tree state to the staged set of changes<RESET> + <BOLD;RED>revert - revert staged set of changes back to the HEAD version<RESET> + <BOLD;RED>patch - pick hunks and update selectively<RESET> + <BOLD;RED>diff - view diff between HEAD and index<RESET> + <BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET> + <BOLD>*** Commands ***<RESET> + 1: <BOLD;BLUE>s<RESET>tatus 2: <BOLD;BLUE>u<RESET>pdate 3: <BOLD;BLUE>r<RESET>evert 4: <BOLD;BLUE>a<RESET>dd untracked + 5: <BOLD;BLUE>p<RESET>atch 6: <BOLD;BLUE>d<RESET>iff 7: <BOLD;BLUE>q<RESET>uit 8: <BOLD;BLUE>h<RESET>elp + <BOLD;BLUE>What now<RESET>>$SP + Bye. + EOF + test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored && + test_decode_color <actual.colored >actual && + test_i18ncmp expect actual +' + test_done
Signature · 1 line
gitgitgadget
PATCH v2 10/11 built-in add -i: use color in the main loop
In reply to Johannes Schindelin via GitGitGadgetFrom: Slavica Djukic <[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 | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-)
add-interactive.c +32 -5
@@ -12,6 +12,9 @@ struct add_i_state {int use_color;char header_color[COLOR_MAXLEN ];char help_color[COLOR_MAXLEN ]; +char prompt_color[COLOR_MAXLEN ]; +char error_color[COLOR_MAXLEN ]; +char reset_color[COLOR_MAXLEN ]; }; static void init_color(struct repository *r, struct add_i_state *s,
@@ -45,6 +48,9 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD );init_color (r, s,"help" , s->help_color,GIT_COLOR_BOLD_RED ); +init_color (r, s,"prompt" , s->prompt_color,GIT_COLOR_BOLD_BLUE ); +init_color (r, s,"error" , s->error_color,GIT_COLOR_BOLD_RED ); +init_color (r, s,"reset" , s->reset_color,GIT_COLOR_RESET );return 0 ; }
@@ -134,7 +140,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,list (items, nr, s, &opts->list_opts); -printf ("%s%s" , opts->prompt,"> " ); +color_fprintf (stdout, s->prompt_color,"%s" , opts->prompt); +fputs ("> " , stdout);fflush (stdout);if (strbuf_getline (&input, stdin) ==EOF ) {
@@ -175,7 +182,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr, index =find_unique (p, items, nr);if (index <0 || index >= nr) -printf (_ ("Huh (%s)?\n" ), p); +color_fprintf_ln (stdout, s->error_color, +_ ("Huh (%s)?" ), p);else { res = index;break ;
@@ -420,15 +428,21 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return 0 ; } +struct print_command_item_data { +const char *color, *reset; +}; +static void print_command_item (int i,struct prefix_item *item,void *print_command_item_data) { +struct print_command_item_data *d = print_command_item_data; +if (!item->prefix_length || !is_valid_prefix (item->name, item->prefix_length))printf (" %2d: %s" , i +1 , item->name);else -printf (" %3d: [%.*s]%s" , i +1 , - (int )item->prefix_length, item->name, +printf (" %2d: %s%.*s%s%s" , i +1 , + d->color, (int )item->prefix_length, item->name, d->reset, item->name + item->prefix_length); }
@@ -453,8 +467,9 @@ static void command_prompt_help(struct add_i_state *s)int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL }; +struct print_command_item_data data;struct list_and_choose_options main_loop_opts = { - {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, + {4 ,N_ ("*** Commands ***" ), print_command_item, &data },N_ ("What now" ), command_prompt_help };struct command_item
@@ -477,6 +492,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)if (init_add_i_state (r, &s))return error ("could not parse `add -i` config" ); +/* +* When color was asked for, use the prompt color for +* highlighting, otherwise use square brackets. +*/ +if (s.use_color) { + data.color = s.prompt_color; + data.reset = s.reset_color; + }else { + data.color ="[" ; + data.reset ="]" ; + } +strbuf_addstr (&header," " );strbuf_addf (&header, print_file_item_data.modified_fmt,_ ("staged" ),_ ("unstaged" ),_ ("path" ));
Signature · 1 line
gitgitgadget
PATCH v2 5/11 built-in add -i: color the header in the `status` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[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 | 60 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-)
add-interactive.c +54 -6
@@ -1,9 +1,51 @@#include "cache.h" #include "add-interactive.h" +#include "color.h" +#include "config.h" #include "diffcore.h" #include "revision.h" #include "refs.h" +struct add_i_state { +struct repository *r; +int use_color; +char header_color[COLOR_MAXLEN ]; +}; + +static void init_color (struct repository *r,struct add_i_state *s, +const char *slot_name,char *dst, +const char *default_color) +{ +char *key =xstrfmt ("color.interactive.%s" , slot_name); +const char *value; + +if (!s->use_color) + dst[0 ] ='\0' ; +else if (repo_config_get_value (r, key, &value) || +color_parse (value, dst)) +strlcpy (dst, default_color,COLOR_MAXLEN ); + +free (key); +} + +static int init_add_i_state (struct repository *r,struct add_i_state *s) +{ +const char *value; + + s->r = r; + +if (repo_config_get_value (r,"color.interactive" , &value)) + s->use_color =-1 ; +else + s->use_color = +git_config_colorbool ("color.interactive" , value); + s->use_color =want_color (s->use_color); + +init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD ); + +return 0 ; +} +struct item {const char *name; };
@@ -14,7 +56,8 @@ struct list_options {void *print_item_data; }; -static void list (struct item **list,size_t nr,struct list_options *opts) +static void list (struct item **list,size_t nr, +struct add_i_state *s,struct list_options *opts) {int i;
@@ -22,7 +65,8 @@ static void list(struct item **list, size_t nr, struct list_options *opts)return ;if (opts->header) -printf ("%s\n" , opts->header); +color_fprintf_ln (stdout, s->header_color, +"%s" , opts->header);for (i =0 ; i < nr; i++) { opts->print_item (i, list[i], opts->print_item_data);
@@ -226,16 +270,16 @@ static void print_file_item(int i, struct item *item,printf (" %2d: %s" , i +1 , d->buf.buf); } -static int run_status (struct repository *r,const struct pathspec *ps, +static int run_status (struct add_i_state *s,const struct pathspec *ps,struct file_list *files,struct list_options *opts) {reset_file_list (files); -if (get_modified_files (r, files, ps) <0 ) +if (get_modified_files (s->r, files, ps) <0 )return -1 ;if (files->nr) -list ((struct item **)files->file, files->nr, opts); +list ((struct item **)files->file, files->nr, s, opts);putchar ('\n' );return 0 ;
@@ -243,6 +287,7 @@ static int run_status(struct repository *r, const struct pathspec *ps,int run_add_i (struct repository *r,const struct pathspec *ps) { +struct add_i_state s = {NULL };struct print_file_item_data print_file_item_data = {"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT };
@@ -253,13 +298,16 @@ int run_add_i(struct repository *r, const struct pathspec *ps)struct file_list files = {NULL };int res =0 ; +if (init_add_i_state (r, &s)) +return error ("could not parse `add -i` config" ); +strbuf_addstr (&header," " );strbuf_addf (&header, print_file_item_data.modified_fmt,_ ("staged" ),_ ("unstaged" ),_ ("path" )); opts.header = header.buf;repo_refresh_and_write_index (r,REFRESH_QUIET ,1 ); -if (run_status (r, ps, &files, &opts) <0 ) +if (run_status (&s, ps, &files, &opts) <0 ) res =-1 ;release_file_list (&files);
Signature · 1 line
gitgitgadget
PATCH v2 7/11 Add a function to determine unique prefixes for a list of strings
In reply to Johannes Schindelin via GitGitGadgetFrom: Slavica Djukic <[email protected]>
In the git add -i command, we show unique prefixes of the commands and files, to give an indication what prefix would select them.
Naturally, the C implementation looks a lot different than the Perl
implementation: in Perl, a trie is much easier implemented, while we already have a pretty neat hashmap implementation in C that we use for the purpose of storing (not necessarily unique) prefixes.
The idea: for each item that we add, we generate prefixes starting with the first letter, then the first two letters, then three, etc, until we find a prefix that is unique (or until the prefix length would be longer than we want). If we encounter a previously-unique prefix on the way, we adjust that item's prefix to make it unique again (or we mark it as having no unique prefix if we failed to find one). These partial prefixes are stored in a hash map (for quick lookup times).
To make sure that this function works as expected, we add a test using a special-purpose test helper that was added for that purpose.
Note: We expect the list of prefix items to be passed in as a list of pointers rather than as regular list to avoid having to copy information (the actual items will most likely contain more information than just the name and the length of the unique prefix, but passing in struct prefix_item * would not allow for that).
--- Makefile | 2 + prefix-map.c | 109 +++++++++++++++++++++++++++++++++++++ prefix-map.h | 40 ++++++++++++++ t/helper/test-prefix-map.c | 58 ++++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0016-prefix-map.sh | 10 ++++ 7 files changed, 221 insertions(+) create mode 100644 prefix-map.c create mode 100644 prefix-map.h create mode 100644 t/helper/test-prefix-map.c create mode 100755 t/t0016-prefix-map.sh
Makefile +2 -0
@@ -754,6 +754,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o TEST_BUILTINS_OBJS += test-parse-options.o TEST_BUILTINS_OBJS += test-path-utils.o TEST_BUILTINS_OBJS += test-pkt-line.o +TEST_BUILTINS_OBJS += test-prefix-map.o TEST_BUILTINS_OBJS += test-prio-queue.o TEST_BUILTINS_OBJS += test-reach.o TEST_BUILTINS_OBJS += test-read-cache.o
@@ -967,6 +968,7 @@ LIB_OBJS += patch-ids.o LIB_OBJS += path.o LIB_OBJS += pathspec.o LIB_OBJS += pkt-line.o +LIB_OBJS += prefix-map.o LIB_OBJS += preload-index.o LIB_OBJS += pretty.o LIB_OBJS += prio-queue.o
prefix-map.c new file +109 -0
109 lines not on this page · raw message
prefix-map.h new file +40 -0
@@ -0,0 +1,40 @@ +#ifndef PREFIX_MAP_H +#define PREFIX_MAP_H + +#include "hashmap.h" + +struct prefix_item { +const char *name; +size_t prefix_length; +}; + +struct prefix_map_entry { +struct hashmap_entry e; +const char *name; +size_t prefix_length; +/* if item is NULL, the prefix is not unique */ +struct prefix_item *item; +}; + +struct prefix_map { +struct hashmap map; +int min_length, max_length; +}; + +/* +* Find unique prefixes in a given list of strings. +* +* Typically, the `struct prefix_item` information will be but a field in the +* actual item struct; For this reason, the `list` parameter is specified as a +* list of pointers to the items. +* +* The `min_length`/`max_length` parameters define what length the unique +* prefixes should have. +* +* If no unique prefix could be found for a given item, its `prefix_length` +* will be set to 0. +*/ +void find_unique_prefixes (struct prefix_item **list,size_t nr, +int min_length,int max_length); + +#endif
t/helper/test-prefix-map.c new file +58 -0
58 lines not on this page · raw message
t/helper/test-tool.c +1 -0
@@ -32,6 +32,7 @@ static struct test_cmd cmds[] = { { "parse-options", cmd__parse_options }, { "path-utils", cmd__path_utils }, { "pkt-line", cmd__pkt_line }, + { "prefix-map", cmd__prefix_map }, { "prio-queue", cmd__prio_queue }, { "reach", cmd__reach }, { "read-cache", cmd__read_cache },
t/helper/test-tool.h +1 -0
@@ -29,6 +29,7 @@ int cmd__online_cpus(int argc, const char **argv);int cmd__parse_options (int argc,const char **argv);int cmd__path_utils (int argc,const char **argv);int cmd__pkt_line (int argc,const char **argv); +int cmd__prefix_map (int argc,const char **argv);int cmd__prio_queue (int argc,const char **argv);int cmd__reach (int argc,const char **argv);int cmd__read_cache (int argc,const char **argv);
t/t0016-prefix-map.sh new file +10 -0
@@ -0,0 +1,10 @@ +#!/bin/sh + +test_description='basic tests for prefix map' +. ./test-lib.sh + +test_expect_success 'prefix map' ' + test-tool prefix-map +' + +test_done
Signature · 1 line
gitgitgadget
PATCH v2 9/11 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 int init_add_i_state(struct repository *r, struct add_i_state *s) s->use_color =want_color (s->use_color);init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD ); +init_color (r, s,"help" , s->help_color,GIT_COLOR_BOLD_RED );return 0 ; }
@@ -104,6 +106,7 @@ struct list_and_choose_options {struct list_options list_opts;const char *prompt; +void (*print_help)(struct add_i_state *s); };#define LIST_AND_CHOOSE_ERROR (-1)
@@ -144,6 +147,11 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,if (!input.len)break ; +if (!strcmp (input.buf,"?" )) { + opts->print_help (s); +continue ; + } + p = input.buf;for (;;) {size_t sep =strcspn (p," \t\r\n," );
@@ -430,12 +438,24 @@ struct command_item { struct file_list *files, struct list_options *opts); }; +static void command_prompt_help (struct add_i_state *s) +{ +const char *help_color = s->help_color; +color_fprintf_ln (stdout, help_color,"%s" ,_ ("Prompt help:" )); +color_fprintf_ln (stdout, help_color,"1 - %s" , +_ ("select a numbered item" )); +color_fprintf_ln (stdout, help_color,"foo - %s" , +_ ("select item based on unique prefix" )); +color_fprintf_ln (stdout, help_color," - %s" , +_ ("(empty) select nothing" )); +} +int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL };struct list_and_choose_options main_loop_opts = { {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, -N_ ("What now" ) +N_ ("What now" ), command_prompt_help };struct command_item status = { {"status" }, run_status };
Signature · 1 line
gitgitgadget
PATCH v2 1/11 Start to implement a built-in version of `git add --interactive`
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This is hardly the first conversion of a Git command that is implemented as a script to a built-in. So far, the most successful strategy for such conversions has been to add a built-in helper and call that for more and more functionality from the script, as more and more parts are converted.
With the interactive add, we choose a different strategy. The sole reason for this is that on Windows (where such a conversion has the most benefits in terms of speed and robustness) we face the very specific problem that a system() call in Perl seems to close stdin in the parent process when the spawned process consumes even one character from stdin. And that just does not work for us here, as it would stop the main loop as soon as any interactive command was performed by the helper. Which is almost all of the commands in git add -i.
It is almost as if Perl told us once again that it does not want us to use it on Windows.
Instead, we follow the opposite route where we start with a bare-bones version of the built-in interactive add, guarded by the new add.interactive.useBuiltin config variable, and then add more and more functionality to it, until it is feature complete.
At this point, the built-in version of git add -i only states that it cannot do anything yet ;-)
--- Documentation/config/add.txt | 5 +++++ Makefile | 1 + add-interactive.c | 7 +++++++ add-interactive.h | 8 ++++++++ builtin/add.c | 10 ++++++++++ t/README | 4 ++++ 6 files changed, 35 insertions(+) create mode 100644 add-interactive.c create mode 100644 add-interactive.h
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
@@ -848,6 +848,7 @@ LIB_H = $(shell $(FIND) . \ -name '*.h' -print) LIB_OBJS += abspath.o +LIB_OBJS += add-interactive.o LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.o
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 +10 -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,14 @@ int run_add_interactive(const char *revision, const char *patch_mode, {int status, i;struct argv_array argv =ARGV_ARRAY_INIT ; +int use_builtin_add_i = +git_env_bool ("GIT_TEST_ADD_I_USE_BUILTIN" ,-1 ); +if (use_builtin_add_i <0 ) +git_config_get_bool ("add.interactive.usebuiltin" , + &use_builtin_add_i); + +if (use_builtin_add_i ==1 && !patch_mode) +return !!run_add_i (the_repository, pathspec);argv_array_push (&argv,"add--interactive" );if (patch_mode)
@@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb) ignore_add_errors =git_config_bool (var, value);return 0 ; } +return git_default_config (var, value, cb); }
t/README +4 -0
@@ -383,6 +383,10 @@ GIT_TEST_REBASE_USE_BUILTIN=<boolean>, when false, disables the builtin version of git-rebase. See 'rebase.useBuiltin' in git-config(1). +GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the +builtin version of git add -i. See 'add.interactive.useBuiltin' in +git-config(1). + GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading of the index for the whole test suite by bypassing the default number of cache entries and thread minimums. Setting this to 1 will make the
Signature · 1 line
gitgitgadget
PATCH v2 3/11 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 as needed later.
At this point, we re-implement only part of the list_and_choose() function of the Perl script git-add--interactive.perl and call it list(). It does not yet color anything, or do columns, or allow user input.
Over the course of the next commits, we will introduce a list_and_choose() function that uses list() to display the list of options and let the user choose one or more of the displayed items. This will be used to implement the main loop of the built-in git add -i, at which point the new status command can actually be used.
Note that we pass the list of items as a struct item ** as opposed to a struct item *, to allow for the actual items to contain much more information than merely the name.
--- add-interactive.c | 265 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 264 insertions(+), 1 deletion(-)
add-interactive.c +264 -1
@@ -1,7 +1,270 @@#include "cache.h" #include "add-interactive.h" +#include "diffcore.h" +#include "revision.h" +#include "refs.h" + +struct item { +const char *name; +}; + +struct list_options { +const char *header; +void (*print_item)(int i,struct item *item,void *print_item_data); +void *print_item_data; +}; + +static void list (struct item **list,size_t nr,struct list_options *opts) +{ +int i; + +if (!nr) +return ; + +if (opts->header) +printf ("%s\n" , opts->header); + +for (i =0 ; i < nr; i++) { + opts->print_item (i, list[i], opts->print_item_data); +putchar ('\n' ); + } +} + +struct adddel { +uintmax_t add, del; +unsigned seen:1 , binary:1 ; +}; + +struct file_list { +struct file_item { +struct item item; +struct adddel index, worktree; + } **file; +size_t nr, alloc; +}; + +static void add_file_item (struct file_list *list,const char *name) +{ +struct file_item *item; + +FLEXPTR_ALLOC_STR (item, item.name, name); + +ALLOC_GROW (list->file, list->nr +1 , list->alloc); + list->file[list->nr++] = item; +} + +static void reset_file_list (struct file_list *list) +{ +size_t i; + +for (i =0 ; i < list->nr; i++) +free (list->file[i]); + list->nr =0 ; +} + +static void release_file_list (struct file_list *list) +{ +reset_file_list (list); +FREE_AND_NULL (list->file); + list->alloc =0 ; +} + +static int file_item_cmp (const void *a,const void *b) +{ +const struct file_item *const *f1 = a; +const struct file_item *const *f2 = b; + +return strcmp ((*f1)->item.name, (*f2)->item.name); +} + +struct pathname_entry { +struct hashmap_entry ent; +size_t index; +char pathname[FLEX_ARRAY ]; +}; + +static int pathname_entry_cmp (const void *unused_cmp_data, +const void *entry,const void *entry_or_key, +const void *pathname) +{ +const struct pathname_entry *e1 = entry, *e2 = entry_or_key; + +return strcmp (e1->pathname, + pathname ? (const char *)pathname : e2->pathname); +} + +struct collection_status { +enum {FROM_WORKTREE =0 ,FROM_INDEX =1 } phase; + +const char *reference; + +struct file_list *list; +struct hashmap file_map; +}; + +static void collect_changes_cb (struct diff_queue_struct *q, +struct diff_options *options, +void *data) +{ +struct collection_status *s = data; +struct diffstat_t stat = {0 }; +int i; + +if (!q->nr) +return ; + +compute_diffstat (options, &stat, q); + +for (i =0 ; i < stat.nr; i++) { +const char *name = stat.files[i]->name; +int hash =strhash (name); +struct pathname_entry *entry; +size_t file_index; +struct file_item *file; +struct adddel *adddel; + + entry =hashmap_get_from_hash (&s->file_map, hash, name); +if (entry) + file_index = entry->index; +else { +FLEX_ALLOC_STR (entry, pathname, name); +hashmap_entry_init (entry, hash); + entry->index = file_index = s->list->nr; +hashmap_add (&s->file_map, entry); + +add_file_item (s->list, name); + } + file = s->list->file[file_index]; + + adddel = s->phase ==FROM_INDEX ? &file->index : &file->worktree; + adddel->seen =1 ; + adddel->add = stat.files[i]->added; + adddel->del = stat.files[i]->deleted; +if (stat.files[i]->is_binary) + adddel->binary =1 ; + } +} + +static int get_modified_files (struct repository *r,struct file_list *list, +const struct pathspec *ps) +{ +struct object_id head_oid; +int is_initial = !resolve_ref_unsafe ("HEAD" ,RESOLVE_REF_READING , + &head_oid,NULL ); +struct collection_status s = {FROM_WORKTREE }; + +if (repo_read_index_preload (r, ps,0 ) <0 ) +return error (_ ("could not read index" )); + + s.list = list; +hashmap_init (&s.file_map, pathname_entry_cmp,NULL ,0 ); + +for (s.phase =FROM_WORKTREE ; s.phase <=FROM_INDEX ; s.phase++) { +struct rev_info rev; +struct setup_revision_opt opt = {0 }; + + opt.def = is_initial ? +empty_tree_oid_hex () :oid_to_hex (&head_oid); + +init_revisions (&rev,NULL ); +setup_revisions (0 ,NULL , &rev, &opt); + + rev.diffopt.output_format =DIFF_FORMAT_CALLBACK ; + rev.diffopt.format_callback = collect_changes_cb; + rev.diffopt.format_callback_data = &s; + +if (ps) +copy_pathspec (&rev.prune_data, ps); + +if (s.phase ==FROM_INDEX ) +run_diff_index (&rev,1 ); +else { + rev.diffopt.flags.ignore_dirty_submodules =1 ; +run_diff_files (&rev,0 ); + } + } +hashmap_free (&s.file_map,1 ); + +/* While the diffs are ordered already, we ran *two* diffs... */ +QSORT (list->file, list->nr, file_item_cmp); + +return 0 ; +} + +static void populate_wi_changes (struct strbuf *buf, +struct adddel *ad,const char *no_changes) +{ +if (ad->binary) +strbuf_addstr (buf,_ ("binary" )); +else if (ad->seen) +strbuf_addf (buf,"+%" PRIuMAX"/-%" PRIuMAX, + (uintmax_t )ad->add, (uintmax_t )ad->del); +else +strbuf_addstr (buf, no_changes); +} + +struct print_file_item_data { +const char *modified_fmt; +struct strbuf buf, index, worktree; +}; + +static void print_file_item (int i,struct item *item, +void *print_file_item_data) +{ +struct file_item *c = (struct file_item *)item; +struct print_file_item_data *d = print_file_item_data; + +strbuf_reset (&d->index); +strbuf_reset (&d->worktree); +strbuf_reset (&d->buf); + +populate_wi_changes (&d->worktree, &c->worktree,_ ("nothing" )); +populate_wi_changes (&d->index, &c->index,_ ("unchanged" )); +strbuf_addf (&d->buf, d->modified_fmt, + d->index.buf, d->worktree.buf, item->name); + +printf (" %2d: %s" , i +1 , d->buf.buf); +} + +static int run_status (struct repository *r,const struct pathspec *ps, +struct file_list *files,struct list_options *opts) +{ +reset_file_list (files); + +if (get_modified_files (r, files, ps) <0 ) +return -1 ; + +if (files->nr) +list ((struct item **)files->file, files->nr, opts); +putchar ('\n' ); + +return 0 ; +}int run_add_i (struct repository *r,const struct pathspec *ps) { -die (_ ("No commands are available in the built-in `git add -i` yet!" )); +struct print_file_item_data print_file_item_data = { +"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT + }; +struct list_options opts = { +NULL , print_file_item, &print_file_item_data + }; +struct strbuf header =STRBUF_INIT ; +struct file_list files = {NULL }; +int res =0 ; + +strbuf_addstr (&header," " ); +strbuf_addf (&header, print_file_item_data.modified_fmt, +_ ("staged" ),_ ("unstaged" ),_ ("path" )); + opts.header = header.buf; + + res =run_status (r, ps, &files, &opts); + +release_file_list (&files); +strbuf_release (&print_file_item_data.buf); +strbuf_release (&print_file_item_data.index); +strbuf_release (&print_file_item_data.worktree); +strbuf_release (&header); + +return res; }
Signature · 1 line
gitgitgadget
v3 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 weeks already (it is so nice to not suffer over one second startup until the MSYS2 Perl finally shows me anything, instead it feels instantaneous), I integrated these patch series into Git for Windows' master already, as an opt-in feature guarded by the config variable add.interactive.useBuiltin (and Git for Windows' installer is prepared to detect this version and offer the option in the graphical user interface).
I had planned on submitting this before v2.22.0-rc0, but there was such a backlog of builds from a big pushout that I had to wait ;-)
Changes since v2:
* Rebased to master to avoid merge conflicts.
* Renumbered the prefix-map test to avoid conflicts with two patch series that are currently in-flight in pu.
Changes since v1:
* The config machinery was reworked completely, to not use a callback to git_config(), but instead to query the config via the repo_config_get_*() functions. This also prevents a future "Huh???" moment: the internal add --interactive API accepts a parameter of type struct repository *r, but the previous configuration did not use that to query the config (and could in the future be a repository other than the_repository).
* As a consequence, the color sequences are no longer stored in file-local variables, but passed around via a struct.
* Instead of using the magical constant -2 to quit the main loop, it is now defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is defined as -1 and used where appropriate).
* Improved the add_prefix_item() function by avoiding buffer overruns, not reusing the struct that is used for lookup also for adding the new item, and by strengthening the bug check.
Daniel Ferreira (2):
diff: export diffstat interface
built-in add -i: implement the status command
Johannes Schindelin (6):
Start to implement a built-in version of git add --interactive built-in add -i: refresh the index before running status built-in add -i: color the header in the status command built-in add -i: implement the main loop
built-in add -i: support ? (prompt help)
built-in add -i: implement the help command
Slavica Djukic (3):
Add a function to determine unique prefixes for a list of strings built-in add -i: show unique prefixes of the commands built-in add -i: use color in the main loop
Documentation/config/add.txt | 5 + Makefile | 3 + add-interactive.c | 558 +++++++++++++++++++++++++++++++++++ add-interactive.h | 8 + builtin/add.c | 10 + diff.c | 37 +-- diff.h | 19 ++ prefix-map.c | 109 +++++++ prefix-map.h | 40 +++ repository.c | 19 ++ repository.h | 7 + t/README | 4 + t/helper/test-prefix-map.c | 58 ++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0018-prefix-map.sh | 10 + t/t3701-add-interactive.sh | 25 ++ 17 files changed, 892 insertions(+), 22 deletions(-) create mode 100644 add-interactive.c create mode 100644 add-interactive.h create mode 100644 prefix-map.c create mode 100644 prefix-map.h create mode 100644 t/helper/test-prefix-map.c create mode 100755 t/t0018-prefix-map.sh
base-commit: 9d418600f4d10dcbbfb0b5fdbc71d509e03ba719
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/170
Range-diff vs v2:
1: ed53346b92 ! 1: 0a5ec9345d Start to implement a built-in version of git add --interactive
@@ -47,7 +47,7 @@
--- a/Makefile
+++ b/Makefile
@@
- -name '*.h' -print)
+ -name '*.h' -print))
LIB_OBJS += abspath.o
+LIB_OBJS += add-interactive.o
@@ -121,7 +121,7 @@
--- a/t/README
+++ b/t/README
@@
- builtin version of git-rebase. See 'rebase.useBuiltin' in
+ built-in version of git-stash. See 'stash.useBuiltin' in
git-config(1).
+GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the
2: bc99009fbf = 2: c7a377890d diff: export diffstat interface
3: 5e23c0756b = 3: b93b055ebe built-in add -i: implement the `status` command
4: 8cafc6ae8d = 4: daff24074a built-in add -i: refresh the index before running `status`
5: 83d92a9762 = 5: 15f18f5b3e built-in add -i: color the header in the `status` command
6: 3eec219124 = 6: 175409aaae built-in add -i: implement the main loop
7: e02a52c3ac ! 7: 3000d7d08d Add a function to determine unique prefixes for a list of strings
@@ -299,10 +299,10 @@
int cmd__reach(int argc, const char **argv);
int cmd__read_cache(int argc, const char **argv);
- diff --git a/t/t0016-prefix-map.sh b/t/t0016-prefix-map.sh
+ diff --git a/t/t0018-prefix-map.sh b/t/t0018-prefix-map.sh
new file mode 100755
--- /dev/null
- +++ b/t/t0016-prefix-map.sh
+ +++ b/t/t0018-prefix-map.sh
@@
+#!/bin/sh
+
8: ced9b6aced = 8: e23ddebfbf built-in add -i: show unique prefixes of the commands
9: 7378af60ad = 9: d8c012fce8 built-in add -i: support `?` (prompt help)
10: 36edef85e6 = 10: 8121a3ca1b built-in add -i: use color in the main loop
11: 266dbf2a6b ! 11: db70c6475d built-in add -i: implement the `help` command
@@ -61,9 +61,10 @@
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@
- test_cmp expected-2 actual
+ test_write_lines a b a b a a b a b a >expect &&
+ test_cmp expect a
'
-
++
+test_expect_success 'show help from add--helper' '
+ git reset --hard &&
+ cat >expect <<-EOF &&Signature · 1 line
gitgitgadget
PATCH v3 1/11 Start to implement a built-in version of `git add --interactive`
From: Johannes Schindelin <[email protected]>
This is hardly the first conversion of a Git command that is implemented as a script to a built-in. So far, the most successful strategy for such conversions has been to add a built-in helper and call that for more and more functionality from the script, as more and more parts are converted.
With the interactive add, we choose a different strategy. The sole reason for this is that on Windows (where such a conversion has the most benefits in terms of speed and robustness) we face the very specific problem that a system() call in Perl seems to close stdin in the parent process when the spawned process consumes even one character from stdin. And that just does not work for us here, as it would stop the main loop as soon as any interactive command was performed by the helper. Which is almost all of the commands in git add -i.
It is almost as if Perl told us once again that it does not want us to use it on Windows.
Instead, we follow the opposite route where we start with a bare-bones version of the built-in interactive add, guarded by the new add.interactive.useBuiltin config variable, and then add more and more functionality to it, until it is feature complete.
At this point, the built-in version of git add -i only states that it cannot do anything yet ;-)
--- Documentation/config/add.txt | 5 +++++ Makefile | 1 + add-interactive.c | 7 +++++++ add-interactive.h | 8 ++++++++ builtin/add.c | 10 ++++++++++ t/README | 4 ++++ 6 files changed, 35 insertions(+) create mode 100644 add-interactive.c create mode 100644 add-interactive.h
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
@@ -824,6 +824,7 @@ LIB_H := $(sort $(shell git ls-files '*.h' ':!t/' ':!Documentation/' 2>/dev/null -name '*.h' -print)) LIB_OBJS += abspath.o +LIB_OBJS += add-interactive.o LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.o
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 +10 -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,14 @@ int run_add_interactive(const char *revision, const char *patch_mode, {int status, i;struct argv_array argv =ARGV_ARRAY_INIT ; +int use_builtin_add_i = +git_env_bool ("GIT_TEST_ADD_I_USE_BUILTIN" ,-1 ); +if (use_builtin_add_i <0 ) +git_config_get_bool ("add.interactive.usebuiltin" , + &use_builtin_add_i); + +if (use_builtin_add_i ==1 && !patch_mode) +return !!run_add_i (the_repository, pathspec);argv_array_push (&argv,"add--interactive" );if (patch_mode)
@@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb) ignore_add_errors =git_config_bool (var, value);return 0 ; } +return git_default_config (var, value, cb); }
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 +builtin version of git add -i. See 'add.interactive.useBuiltin' in +git-config(1). + GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading of the index for the whole test suite by bypassing the default number of cache entries and thread minimums. Setting this to 1 will make the
Signature · 1 line
gitgitgadget
PATCH v3 2/11 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 | 37 +++++++++++++++---------------------- diff.h | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+), 22 deletions(-)
diff.c +15 -22
@@ -2489,22 +2489,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b) } } -struct diffstat_t { -int nr; -int alloc; -struct diffstat_file { -char *from_name; -char *name; -char *print_name; -const char *comments; -unsigned is_unmerged:1 ; -unsigned is_binary:1 ; -unsigned is_renamed:1 ; -unsigned is_interesting:1 ; -uintmax_t added, deleted; - } **files; -}; -static struct diffstat_file *diffstat_add (struct diffstat_t *diffstat,const char *name_a,const char *name_b)
@@ -6273,12 +6257,7 @@ void diff_flush(struct diff_options *options) dirstat_by_line) {struct diffstat_t diffstat; -memset (&diffstat,0 ,sizeof (struct diffstat_t )); -for (i =0 ; i < q->nr; i++) { -struct diff_filepair *p = q->queue[i]; -if (check_pair_status (p)) -diff_flush_stat (p, options, &diffstat); - } +compute_diffstat (options, &diffstat, q);if (output_format &DIFF_FORMAT_NUMSTAT )show_numstat (&diffstat, options);if (output_format &DIFF_FORMAT_DIFFSTAT )
@@ -6611,6 +6590,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)return ignored; } +void compute_diffstat (struct diff_options *options, +struct diffstat_t *diffstat, +struct diff_queue_struct *q) +{ +int i; + +memset (diffstat,0 ,sizeof (struct diffstat_t )); +for (i =0 ; i < q->nr; i++) { +struct diff_filepair *p = q->queue[i]; +if (check_pair_status (p)) +diff_flush_stat (p, options, diffstat); + } +} + void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const struct object_id *oid,
diff.h +19 -0
@@ -244,6 +244,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);void diff_emit_submodule_pipethrough (struct diff_options *o,const char *line,int len); +struct diffstat_t { +int nr; +int alloc; +struct diffstat_file { +char *from_name; +char *name; +char *print_name; +const char *comments; +unsigned is_unmerged:1 ; +unsigned is_binary:1 ; +unsigned is_renamed:1 ; +unsigned is_interesting:1 ; +uintmax_t added, deleted; + } **files; +}; +enum color_diff {DIFF_RESET =0 ,DIFF_CONTEXT =1 ,
@@ -333,6 +349,9 @@ void diff_change(struct diff_options *,struct diff_filepair *diff_unmerge (struct diff_options *,const char *path); +void compute_diffstat (struct diff_options *options,struct diffstat_t *diffstat, +struct diff_queue_struct *q); +#define DIFF_SETUP_REVERSE 1#define DIFF_SETUP_USE_SIZE_CACHE 4
Signature · 1 line
gitgitgadget
PATCH v3 5/11 built-in add -i: color the header in the `status` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[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 | 60 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-)
add-interactive.c +54 -6
@@ -1,9 +1,51 @@#include "cache.h" #include "add-interactive.h" +#include "color.h" +#include "config.h" #include "diffcore.h" #include "revision.h" #include "refs.h" +struct add_i_state { +struct repository *r; +int use_color; +char header_color[COLOR_MAXLEN ]; +}; + +static void init_color (struct repository *r,struct add_i_state *s, +const char *slot_name,char *dst, +const char *default_color) +{ +char *key =xstrfmt ("color.interactive.%s" , slot_name); +const char *value; + +if (!s->use_color) + dst[0 ] ='\0' ; +else if (repo_config_get_value (r, key, &value) || +color_parse (value, dst)) +strlcpy (dst, default_color,COLOR_MAXLEN ); + +free (key); +} + +static int init_add_i_state (struct repository *r,struct add_i_state *s) +{ +const char *value; + + s->r = r; + +if (repo_config_get_value (r,"color.interactive" , &value)) + s->use_color =-1 ; +else + s->use_color = +git_config_colorbool ("color.interactive" , value); + s->use_color =want_color (s->use_color); + +init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD ); + +return 0 ; +} +struct item {const char *name; };
@@ -14,7 +56,8 @@ struct list_options {void *print_item_data; }; -static void list (struct item **list,size_t nr,struct list_options *opts) +static void list (struct item **list,size_t nr, +struct add_i_state *s,struct list_options *opts) {int i;
@@ -22,7 +65,8 @@ static void list(struct item **list, size_t nr, struct list_options *opts)return ;if (opts->header) -printf ("%s\n" , opts->header); +color_fprintf_ln (stdout, s->header_color, +"%s" , opts->header);for (i =0 ; i < nr; i++) { opts->print_item (i, list[i], opts->print_item_data);
@@ -226,16 +270,16 @@ static void print_file_item(int i, struct item *item,printf (" %2d: %s" , i +1 , d->buf.buf); } -static int run_status (struct repository *r,const struct pathspec *ps, +static int run_status (struct add_i_state *s,const struct pathspec *ps,struct file_list *files,struct list_options *opts) {reset_file_list (files); -if (get_modified_files (r, files, ps) <0 ) +if (get_modified_files (s->r, files, ps) <0 )return -1 ;if (files->nr) -list ((struct item **)files->file, files->nr, opts); +list ((struct item **)files->file, files->nr, s, opts);putchar ('\n' );return 0 ;
@@ -243,6 +287,7 @@ static int run_status(struct repository *r, const struct pathspec *ps,int run_add_i (struct repository *r,const struct pathspec *ps) { +struct add_i_state s = {NULL };struct print_file_item_data print_file_item_data = {"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT };
@@ -253,13 +298,16 @@ int run_add_i(struct repository *r, const struct pathspec *ps)struct file_list files = {NULL };int res =0 ; +if (init_add_i_state (r, &s)) +return error ("could not parse `add -i` config" ); +strbuf_addstr (&header," " );strbuf_addf (&header, print_file_item_data.modified_fmt,_ ("staged" ),_ ("unstaged" ),_ ("path" )); opts.header = header.buf;repo_refresh_and_write_index (r,REFRESH_QUIET ,1 ); -if (run_status (r, ps, &files, &opts) <0 ) +if (run_status (&s, ps, &files, &opts) <0 ) res =-1 ;release_file_list (&files);
Signature · 1 line
gitgitgadget
PATCH v3 4/11 built-in add -i: refresh the index before running `status`
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This is what the Perl version does, and therefore it is what the built-in version should do, too.
--- add-interactive.c | 4 +++- repository.c | 19 +++++++++++++++++++ repository.h | 7 +++++++ 3 files changed, 29 insertions(+), 1 deletion(-)
add-interactive.c +3 -1
@@ -258,7 +258,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps) _("staged"), _("unstaged"), _("path")); opts.header = header.buf; - res =run_status (r, ps, &files, &opts); +repo_refresh_and_write_index (r,REFRESH_QUIET ,1 ); +if (run_status (r, ps, &files, &opts) <0 ) + res =-1 ;release_file_list (&files);strbuf_release (&print_file_item_data.buf);
repository.c +19 -0
@@ -275,3 +275,22 @@ int repo_hold_locked_index(struct repository *repo,BUG ("the repo hasn't been setup" );return hold_lock_file_for_update (lf, repo->index_file, flags); } + +int repo_refresh_and_write_index (struct repository *r, +unsigned int flags,int gentle) +{ +struct lock_file lock_file =LOCK_INIT ; +int fd; + +if (repo_read_index_preload (r,NULL ,0 ) <0 ) +return error (_ ("could not read index" )); + fd =repo_hold_locked_index (r, &lock_file,0 ); +if (!gentle && fd <0 ) +return error (_ ("could not lock index for writing" )); +refresh_index (r->index, flags,NULL ,NULL ,NULL ); +if (0 <= fd) +repo_update_index_if_able (r, &lock_file); +rollback_lock_file (&lock_file); + +return 0 ; +}
repository.h +7 -0
@@ -157,5 +157,12 @@ int repo_read_index_unmerged(struct repository *);*/ void repo_update_index_if_able (struct repository *,struct lock_file *); +/* +* Refresh the index and write it out. If the index file could not be +* locked, error out, except in gentle mode. The flags will be passed +* through to refresh_index(). +*/ +int repo_refresh_and_write_index (struct repository *r, +unsigned int flags,int gentle);#endif /* REPOSITORY_H */
Signature · 1 line
gitgitgadget
PATCH v3 6/11 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.
Apart from the "and choose" part, there are more differences between the way the status command calls the list_and_choose() function in the Perl version of git add -i compared to the other callers of said function. The most important ones:
- The list is not only shown, but the user is also asked to make a choice, possibly selecting multiple entries.
- The list of items is prefixed with a marker indicating what items have been selected, if multi-selection is allowed.
- Initially, for each item a unique prefix (if there exists any within the given parameters) is determined, and shown in the list, and accepted as a shortcut for the selection.
These features will be implemented later, except the part where the user can choose a command. At this stage, though, the built-in git add -i still only supports the status command, with the remaining commands to follow over the course of the next commits.
In addition, we also modify list() to support displaying the commands in columns, even if there is currently only one.
The Perl script git-add--interactive.perl mixed the purposes of the "list" and the "and choose" part into the same function. In the C version, we will keep them separate instead, calling the list() function from the list_and_choose() function.
Note that we only have a prompt ending in a single ">" at this stage; later commits will add commands that display a double ">>" to indicate that the user is in a different loop than the main one.
--- add-interactive.c | 129 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 2 deletions(-)
add-interactive.c +127 -2
@@ -51,6 +51,7 @@ struct item { };struct list_options { +int columns;const char *header;void (*print_item)(int i,struct item *item,void *print_item_data);void *print_item_data;
@@ -59,7 +60,7 @@ struct list_options {static void list (struct item **list,size_t nr,struct add_i_state *s,struct list_options *opts) { -int i; +int i, last_lf =0 ;if (!nr)return ;
@@ -70,8 +71,97 @@ static void list(struct item **list, size_t nr,for (i =0 ; i < nr; i++) { opts->print_item (i, list[i], opts->print_item_data); + +if ((opts->columns) && ((i +1 ) % (opts->columns))) { +putchar ('\t' ); + last_lf =0 ; + } +else { +putchar ('\n' ); + last_lf =1 ; + } + } + +if (!last_lf)putchar ('\n' ); +} +struct list_and_choose_options { +struct list_options list_opts; + +const char *prompt; +}; + +#define LIST_AND_CHOOSE_ERROR (-1) +#define LIST_AND_CHOOSE_QUIT (-2) + +/* +* Returns the selected index. +* +* If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF, +* `LIST_AND_CHOOSE_QUIT` is returned. +*/ +static ssize_t list_and_choose (struct item **items,size_t nr, +struct add_i_state *s, +struct list_and_choose_options *opts) +{ +struct strbuf input =STRBUF_INIT ; +ssize_t res =LIST_AND_CHOOSE_ERROR ; + +for (;;) { +char *p, *endp; + +strbuf_reset (&input); + +list (items, nr, s, &opts->list_opts); + +printf ("%s%s" , opts->prompt,"> " ); +fflush (stdout); + +if (strbuf_getline (&input, stdin) ==EOF ) { +putchar ('\n' ); + res =LIST_AND_CHOOSE_QUIT ; +break ; + } +strbuf_trim (&input); + +if (!input.len) +break ; + + p = input.buf; +for (;;) { +size_t sep =strcspn (p," \t\r\n," ); +ssize_t index =-1 ; + +if (!sep) { +if (!*p) +break ; + p++; +continue ; + } + +if (isdigit (*p)) { + index =strtoul (p, &endp,10 ) -1 ; +if (endp != p + sep) + index =-1 ; + } + + p[sep] ='\0' ; +if (index <0 || index >= nr) +printf (_ ("Huh (%s)?\n" ), p); +else { + res = index; +break ; + } + + p += sep +1 ; + } + +if (res !=LIST_AND_CHOOSE_ERROR ) +break ; } + +strbuf_release (&input); +return res; }struct adddel {
@@ -285,17 +375,40 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return 0 ; } +static void print_command_item (int i,struct item *item, +void *print_command_item_data) +{ +printf (" %2d: %s" , i +1 , item->name); +} + +struct command_item { +struct item item; +int (*command)(struct add_i_state *s,const struct pathspec *ps, +struct file_list *files,struct list_options *opts); +}; +int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL }; +struct list_and_choose_options main_loop_opts = { + {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, +N_ ("What now" ) + }; +struct command_item + status = { {"status" }, run_status }; +struct command_item *commands[] = { + &status + }; +struct print_file_item_data print_file_item_data = {"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT };struct list_options opts = { -NULL , print_file_item, &print_file_item_data +0 ,NULL , print_file_item, &print_file_item_data };struct strbuf header =STRBUF_INIT ;struct file_list files = {NULL }; +ssize_t i;int res =0 ;if (init_add_i_state (r, &s))
@@ -310,6 +423,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)if (run_status (&s, ps, &files, &opts) <0 ) res =-1 ; +for (;;) { + i =list_and_choose ((struct item **)commands, +ARRAY_SIZE (commands), &s, &main_loop_opts); +if (i ==LIST_AND_CHOOSE_QUIT ) { +printf (_ ("Bye.\n" )); + res =0 ; +break ; + } +if (i !=LIST_AND_CHOOSE_ERROR ) + res = commands[i]->command (&s, ps, &files, &opts); + } +release_file_list (&files);strbuf_release (&print_file_item_data.buf);strbuf_release (&print_file_item_data.index);
Signature · 1 line
gitgitgadget
PATCH v3 8/11 built-in add -i: show unique prefixes of the commands
In reply to Johannes Schindelin via GitGitGadgetFrom: Slavica Djukic <[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.
We use the prefix map implementation that we just added in the previous commit for that purpose.
--- add-interactive.c | 69 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 13 deletions(-)
add-interactive.c +56 -13
@@ -5,6 +5,7 @@#include "diffcore.h" #include "revision.h" #include "refs.h" +#include "prefix-map.h" struct add_i_state {struct repository *r;
@@ -46,18 +47,32 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)return 0 ; } -struct item { -const char *name; -}; +static ssize_t find_unique (const char *string, +struct prefix_item **list,size_t nr) +{ +ssize_t found =-1 , i; + +for (i =0 ; i < nr; i++) { +struct prefix_item *item = list[i]; +if (!starts_with (item->name, string)) +continue ; +if (found >=0 ) +return -1 ; + found = i; + } + +return found; +}struct list_options {int columns;const char *header; -void (*print_item)(int i,struct item *item,void *print_item_data); +void (*print_item)(int i,struct prefix_item *item, +void *print_item_data);void *print_item_data; }; -static void list (struct item **list,size_t nr, +static void list (struct prefix_item **list,size_t nr,struct add_i_state *s,struct list_options *opts) {int i, last_lf =0 ;
@@ -100,13 +115,15 @@ struct list_and_choose_options {* If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF, * `LIST_AND_CHOOSE_QUIT` is returned. */ -static ssize_t list_and_choose (struct item **items,size_t nr, +static ssize_t list_and_choose (struct prefix_item **items,size_t nr,struct add_i_state *s,struct list_and_choose_options *opts) {struct strbuf input =STRBUF_INIT ;ssize_t res =LIST_AND_CHOOSE_ERROR ; +find_unique_prefixes (items, nr,1 ,4 ); +for (;;) {char *p, *endp;
@@ -146,6 +163,9 @@ static ssize_t list_and_choose(struct item **items, size_t nr, } p[sep] ='\0' ; +if (index <0 ) + index =find_unique (p, items, nr); +if (index <0 || index >= nr)printf (_ ("Huh (%s)?\n" ), p);else {
@@ -171,7 +191,7 @@ struct adddel {struct file_list {struct file_item { -struct item item; +struct prefix_item item;struct adddel index, worktree; } **file;size_t nr, alloc;
@@ -337,12 +357,29 @@ static void populate_wi_changes(struct strbuf *buf,strbuf_addstr (buf, no_changes); } +/* filters out prefixes which have special meaning to list_and_choose() */ +static int is_valid_prefix (const char *prefix,size_t prefix_len) +{ +return prefix_len && prefix && +/* +* We expect `prefix` to be NUL terminated, therefore this +* `strcspn()` call is okay, even if it might do much more +* work than strictly necessary. +*/ +strcspn (prefix," \t\r\n," ) >= prefix_len &&/* separators */ + *prefix !='-' &&/* deselection */ + !isdigit (*prefix) &&/* selection */ + (prefix_len !=1 || + (*prefix !='*' &&/* "all" wildcard */ + *prefix !='?' ));/* prompt help */ +} +struct print_file_item_data {const char *modified_fmt;struct strbuf buf, index, worktree; }; -static void print_file_item (int i,struct item *item, +static void print_file_item (int i,struct prefix_item *item,void *print_file_item_data) {struct file_item *c = (struct file_item *)item;
@@ -369,20 +406,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return -1 ;if (files->nr) -list ((struct item **)files->file, files->nr, s, opts); +list ((struct prefix_item **)files->file, files->nr, s, opts);putchar ('\n' );return 0 ; } -static void print_command_item (int i,struct item *item, +static void print_command_item (int i,struct prefix_item *item,void *print_command_item_data) { -printf (" %2d: %s" , i +1 , item->name); +if (!item->prefix_length || + !is_valid_prefix (item->name, item->prefix_length)) +printf (" %2d: %s" , i +1 , item->name); +else +printf (" %3d: [%.*s]%s" , i +1 , + (int )item->prefix_length, item->name, + item->name + item->prefix_length); }struct command_item { -struct item item; +struct prefix_item item;int (*command)(struct add_i_state *s,const struct pathspec *ps,struct file_list *files,struct list_options *opts); };
@@ -424,7 +467,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps) res =-1 ;for (;;) { - i =list_and_choose ((struct item **)commands, + i =list_and_choose ((struct prefix_item **)commands,ARRAY_SIZE (commands), &s, &main_loop_opts);if (i ==LIST_AND_CHOOSE_QUIT ) {printf (_ ("Bye.\n" ));
Signature · 1 line
gitgitgadget
PATCH v3 7/11 Add a function to determine unique prefixes for a list of strings
In reply to Johannes Schindelin via GitGitGadgetFrom: Slavica Djukic <[email protected]>
In the git add -i command, we show unique prefixes of the commands and files, to give an indication what prefix would select them.
Naturally, the C implementation looks a lot different than the Perl
implementation: in Perl, a trie is much easier implemented, while we already have a pretty neat hashmap implementation in C that we use for the purpose of storing (not necessarily unique) prefixes.
The idea: for each item that we add, we generate prefixes starting with the first letter, then the first two letters, then three, etc, until we find a prefix that is unique (or until the prefix length would be longer than we want). If we encounter a previously-unique prefix on the way, we adjust that item's prefix to make it unique again (or we mark it as having no unique prefix if we failed to find one). These partial prefixes are stored in a hash map (for quick lookup times).
To make sure that this function works as expected, we add a test using a special-purpose test helper that was added for that purpose.
Note: We expect the list of prefix items to be passed in as a list of pointers rather than as regular list to avoid having to copy information (the actual items will most likely contain more information than just the name and the length of the unique prefix, but passing in struct prefix_item * would not allow for that).
--- Makefile | 2 + prefix-map.c | 109 +++++++++++++++++++++++++++++++++++++ prefix-map.h | 40 ++++++++++++++ t/helper/test-prefix-map.c | 58 ++++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0018-prefix-map.sh | 10 ++++ 7 files changed, 221 insertions(+) create mode 100644 prefix-map.c create mode 100644 prefix-map.h create mode 100644 t/helper/test-prefix-map.c create mode 100755 t/t0018-prefix-map.sh
Makefile +2 -0
@@ -725,6 +725,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o TEST_BUILTINS_OBJS += test-parse-options.o TEST_BUILTINS_OBJS += test-path-utils.o TEST_BUILTINS_OBJS += test-pkt-line.o +TEST_BUILTINS_OBJS += test-prefix-map.o TEST_BUILTINS_OBJS += test-prio-queue.o TEST_BUILTINS_OBJS += test-reach.o TEST_BUILTINS_OBJS += test-read-cache.o
@@ -943,6 +944,7 @@ LIB_OBJS += patch-ids.o LIB_OBJS += path.o LIB_OBJS += pathspec.o LIB_OBJS += pkt-line.o +LIB_OBJS += prefix-map.o LIB_OBJS += preload-index.o LIB_OBJS += pretty.o LIB_OBJS += prio-queue.o
prefix-map.c new file +109 -0
109 lines not on this page · raw message
prefix-map.h new file +40 -0
@@ -0,0 +1,40 @@ +#ifndef PREFIX_MAP_H +#define PREFIX_MAP_H + +#include "hashmap.h" + +struct prefix_item { +const char *name; +size_t prefix_length; +}; + +struct prefix_map_entry { +struct hashmap_entry e; +const char *name; +size_t prefix_length; +/* if item is NULL, the prefix is not unique */ +struct prefix_item *item; +}; + +struct prefix_map { +struct hashmap map; +int min_length, max_length; +}; + +/* +* Find unique prefixes in a given list of strings. +* +* Typically, the `struct prefix_item` information will be but a field in the +* actual item struct; For this reason, the `list` parameter is specified as a +* list of pointers to the items. +* +* The `min_length`/`max_length` parameters define what length the unique +* prefixes should have. +* +* If no unique prefix could be found for a given item, its `prefix_length` +* will be set to 0. +*/ +void find_unique_prefixes (struct prefix_item **list,size_t nr, +int min_length,int max_length); + +#endif
t/helper/test-prefix-map.c new file +58 -0
58 lines not on this page · raw message
t/helper/test-tool.c +1 -0
@@ -39,6 +39,7 @@ static struct test_cmd cmds[] = { { "parse-options", cmd__parse_options }, { "path-utils", cmd__path_utils }, { "pkt-line", cmd__pkt_line }, + { "prefix-map", cmd__prefix_map }, { "prio-queue", cmd__prio_queue }, { "reach", cmd__reach }, { "read-cache", cmd__read_cache },
t/helper/test-tool.h +1 -0
@@ -29,6 +29,7 @@ int cmd__online_cpus(int argc, const char **argv);int cmd__parse_options (int argc,const char **argv);int cmd__path_utils (int argc,const char **argv);int cmd__pkt_line (int argc,const char **argv); +int cmd__prefix_map (int argc,const char **argv);int cmd__prio_queue (int argc,const char **argv);int cmd__reach (int argc,const char **argv);int cmd__read_cache (int argc,const char **argv);
t/t0018-prefix-map.sh new file +10 -0
@@ -0,0 +1,10 @@ +#!/bin/sh + +test_description='basic tests for prefix map' +. ./test-lib.sh + +test_expect_success 'prefix map' ' + test-tool prefix-map +' + +test_done
Signature · 1 line
gitgitgadget
PATCH v3 9/11 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 int init_add_i_state(struct repository *r, struct add_i_state *s) s->use_color =want_color (s->use_color);init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD ); +init_color (r, s,"help" , s->help_color,GIT_COLOR_BOLD_RED );return 0 ; }
@@ -104,6 +106,7 @@ struct list_and_choose_options {struct list_options list_opts;const char *prompt; +void (*print_help)(struct add_i_state *s); };#define LIST_AND_CHOOSE_ERROR (-1)
@@ -144,6 +147,11 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,if (!input.len)break ; +if (!strcmp (input.buf,"?" )) { + opts->print_help (s); +continue ; + } + p = input.buf;for (;;) {size_t sep =strcspn (p," \t\r\n," );
@@ -430,12 +438,24 @@ struct command_item { struct file_list *files, struct list_options *opts); }; +static void command_prompt_help (struct add_i_state *s) +{ +const char *help_color = s->help_color; +color_fprintf_ln (stdout, help_color,"%s" ,_ ("Prompt help:" )); +color_fprintf_ln (stdout, help_color,"1 - %s" , +_ ("select a numbered item" )); +color_fprintf_ln (stdout, help_color,"foo - %s" , +_ ("select item based on unique prefix" )); +color_fprintf_ln (stdout, help_color," - %s" , +_ ("(empty) select nothing" )); +} +int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL };struct list_and_choose_options main_loop_opts = { {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, -N_ ("What now" ) +N_ ("What now" ), command_prompt_help };struct command_item status = { {"status" }, run_status };
Signature · 1 line
gitgitgadget
PATCH v3 3/11 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 as needed later.
At this point, we re-implement only part of the list_and_choose() function of the Perl script git-add--interactive.perl and call it list(). It does not yet color anything, or do columns, or allow user input.
Over the course of the next commits, we will introduce a list_and_choose() function that uses list() to display the list of options and let the user choose one or more of the displayed items. This will be used to implement the main loop of the built-in git add -i, at which point the new status command can actually be used.
Note that we pass the list of items as a struct item ** as opposed to a struct item *, to allow for the actual items to contain much more information than merely the name.
--- add-interactive.c | 265 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 264 insertions(+), 1 deletion(-)
add-interactive.c +264 -1
@@ -1,7 +1,270 @@#include "cache.h" #include "add-interactive.h" +#include "diffcore.h" +#include "revision.h" +#include "refs.h" + +struct item { +const char *name; +}; + +struct list_options { +const char *header; +void (*print_item)(int i,struct item *item,void *print_item_data); +void *print_item_data; +}; + +static void list (struct item **list,size_t nr,struct list_options *opts) +{ +int i; + +if (!nr) +return ; + +if (opts->header) +printf ("%s\n" , opts->header); + +for (i =0 ; i < nr; i++) { + opts->print_item (i, list[i], opts->print_item_data); +putchar ('\n' ); + } +} + +struct adddel { +uintmax_t add, del; +unsigned seen:1 , binary:1 ; +}; + +struct file_list { +struct file_item { +struct item item; +struct adddel index, worktree; + } **file; +size_t nr, alloc; +}; + +static void add_file_item (struct file_list *list,const char *name) +{ +struct file_item *item; + +FLEXPTR_ALLOC_STR (item, item.name, name); + +ALLOC_GROW (list->file, list->nr +1 , list->alloc); + list->file[list->nr++] = item; +} + +static void reset_file_list (struct file_list *list) +{ +size_t i; + +for (i =0 ; i < list->nr; i++) +free (list->file[i]); + list->nr =0 ; +} + +static void release_file_list (struct file_list *list) +{ +reset_file_list (list); +FREE_AND_NULL (list->file); + list->alloc =0 ; +} + +static int file_item_cmp (const void *a,const void *b) +{ +const struct file_item *const *f1 = a; +const struct file_item *const *f2 = b; + +return strcmp ((*f1)->item.name, (*f2)->item.name); +} + +struct pathname_entry { +struct hashmap_entry ent; +size_t index; +char pathname[FLEX_ARRAY ]; +}; + +static int pathname_entry_cmp (const void *unused_cmp_data, +const void *entry,const void *entry_or_key, +const void *pathname) +{ +const struct pathname_entry *e1 = entry, *e2 = entry_or_key; + +return strcmp (e1->pathname, + pathname ? (const char *)pathname : e2->pathname); +} + +struct collection_status { +enum {FROM_WORKTREE =0 ,FROM_INDEX =1 } phase; + +const char *reference; + +struct file_list *list; +struct hashmap file_map; +}; + +static void collect_changes_cb (struct diff_queue_struct *q, +struct diff_options *options, +void *data) +{ +struct collection_status *s = data; +struct diffstat_t stat = {0 }; +int i; + +if (!q->nr) +return ; + +compute_diffstat (options, &stat, q); + +for (i =0 ; i < stat.nr; i++) { +const char *name = stat.files[i]->name; +int hash =strhash (name); +struct pathname_entry *entry; +size_t file_index; +struct file_item *file; +struct adddel *adddel; + + entry =hashmap_get_from_hash (&s->file_map, hash, name); +if (entry) + file_index = entry->index; +else { +FLEX_ALLOC_STR (entry, pathname, name); +hashmap_entry_init (entry, hash); + entry->index = file_index = s->list->nr; +hashmap_add (&s->file_map, entry); + +add_file_item (s->list, name); + } + file = s->list->file[file_index]; + + adddel = s->phase ==FROM_INDEX ? &file->index : &file->worktree; + adddel->seen =1 ; + adddel->add = stat.files[i]->added; + adddel->del = stat.files[i]->deleted; +if (stat.files[i]->is_binary) + adddel->binary =1 ; + } +} + +static int get_modified_files (struct repository *r,struct file_list *list, +const struct pathspec *ps) +{ +struct object_id head_oid; +int is_initial = !resolve_ref_unsafe ("HEAD" ,RESOLVE_REF_READING , + &head_oid,NULL ); +struct collection_status s = {FROM_WORKTREE }; + +if (repo_read_index_preload (r, ps,0 ) <0 ) +return error (_ ("could not read index" )); + + s.list = list; +hashmap_init (&s.file_map, pathname_entry_cmp,NULL ,0 ); + +for (s.phase =FROM_WORKTREE ; s.phase <=FROM_INDEX ; s.phase++) { +struct rev_info rev; +struct setup_revision_opt opt = {0 }; + + opt.def = is_initial ? +empty_tree_oid_hex () :oid_to_hex (&head_oid); + +init_revisions (&rev,NULL ); +setup_revisions (0 ,NULL , &rev, &opt); + + rev.diffopt.output_format =DIFF_FORMAT_CALLBACK ; + rev.diffopt.format_callback = collect_changes_cb; + rev.diffopt.format_callback_data = &s; + +if (ps) +copy_pathspec (&rev.prune_data, ps); + +if (s.phase ==FROM_INDEX ) +run_diff_index (&rev,1 ); +else { + rev.diffopt.flags.ignore_dirty_submodules =1 ; +run_diff_files (&rev,0 ); + } + } +hashmap_free (&s.file_map,1 ); + +/* While the diffs are ordered already, we ran *two* diffs... */ +QSORT (list->file, list->nr, file_item_cmp); + +return 0 ; +} + +static void populate_wi_changes (struct strbuf *buf, +struct adddel *ad,const char *no_changes) +{ +if (ad->binary) +strbuf_addstr (buf,_ ("binary" )); +else if (ad->seen) +strbuf_addf (buf,"+%" PRIuMAX"/-%" PRIuMAX, + (uintmax_t )ad->add, (uintmax_t )ad->del); +else +strbuf_addstr (buf, no_changes); +} + +struct print_file_item_data { +const char *modified_fmt; +struct strbuf buf, index, worktree; +}; + +static void print_file_item (int i,struct item *item, +void *print_file_item_data) +{ +struct file_item *c = (struct file_item *)item; +struct print_file_item_data *d = print_file_item_data; + +strbuf_reset (&d->index); +strbuf_reset (&d->worktree); +strbuf_reset (&d->buf); + +populate_wi_changes (&d->worktree, &c->worktree,_ ("nothing" )); +populate_wi_changes (&d->index, &c->index,_ ("unchanged" )); +strbuf_addf (&d->buf, d->modified_fmt, + d->index.buf, d->worktree.buf, item->name); + +printf (" %2d: %s" , i +1 , d->buf.buf); +} + +static int run_status (struct repository *r,const struct pathspec *ps, +struct file_list *files,struct list_options *opts) +{ +reset_file_list (files); + +if (get_modified_files (r, files, ps) <0 ) +return -1 ; + +if (files->nr) +list ((struct item **)files->file, files->nr, opts); +putchar ('\n' ); + +return 0 ; +}int run_add_i (struct repository *r,const struct pathspec *ps) { -die (_ ("No commands are available in the built-in `git add -i` yet!" )); +struct print_file_item_data print_file_item_data = { +"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT + }; +struct list_options opts = { +NULL , print_file_item, &print_file_item_data + }; +struct strbuf header =STRBUF_INIT ; +struct file_list files = {NULL }; +int res =0 ; + +strbuf_addstr (&header," " ); +strbuf_addf (&header, print_file_item_data.modified_fmt, +_ ("staged" ),_ ("unstaged" ),_ ("path" )); + opts.header = header.buf; + + res =run_status (r, ps, &files, &opts); + +release_file_list (&files); +strbuf_release (&print_file_item_data.buf); +strbuf_release (&print_file_item_data.index); +strbuf_release (&print_file_item_data.worktree); +strbuf_release (&header); + +return res; }
Signature · 1 line
gitgitgadget
PATCH v3 10/11 built-in add -i: use color in the main loop
In reply to Johannes Schindelin via GitGitGadgetFrom: Slavica Djukic <[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 | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-)
add-interactive.c +32 -5
@@ -12,6 +12,9 @@ struct add_i_state {int use_color;char header_color[COLOR_MAXLEN ];char help_color[COLOR_MAXLEN ]; +char prompt_color[COLOR_MAXLEN ]; +char error_color[COLOR_MAXLEN ]; +char reset_color[COLOR_MAXLEN ]; }; static void init_color(struct repository *r, struct add_i_state *s,
@@ -45,6 +48,9 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD );init_color (r, s,"help" , s->help_color,GIT_COLOR_BOLD_RED ); +init_color (r, s,"prompt" , s->prompt_color,GIT_COLOR_BOLD_BLUE ); +init_color (r, s,"error" , s->error_color,GIT_COLOR_BOLD_RED ); +init_color (r, s,"reset" , s->reset_color,GIT_COLOR_RESET );return 0 ; }
@@ -134,7 +140,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,list (items, nr, s, &opts->list_opts); -printf ("%s%s" , opts->prompt,"> " ); +color_fprintf (stdout, s->prompt_color,"%s" , opts->prompt); +fputs ("> " , stdout);fflush (stdout);if (strbuf_getline (&input, stdin) ==EOF ) {
@@ -175,7 +182,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr, index =find_unique (p, items, nr);if (index <0 || index >= nr) -printf (_ ("Huh (%s)?\n" ), p); +color_fprintf_ln (stdout, s->error_color, +_ ("Huh (%s)?" ), p);else { res = index;break ;
@@ -420,15 +428,21 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return 0 ; } +struct print_command_item_data { +const char *color, *reset; +}; +static void print_command_item (int i,struct prefix_item *item,void *print_command_item_data) { +struct print_command_item_data *d = print_command_item_data; +if (!item->prefix_length || !is_valid_prefix (item->name, item->prefix_length))printf (" %2d: %s" , i +1 , item->name);else -printf (" %3d: [%.*s]%s" , i +1 , - (int )item->prefix_length, item->name, +printf (" %2d: %s%.*s%s%s" , i +1 , + d->color, (int )item->prefix_length, item->name, d->reset, item->name + item->prefix_length); }
@@ -453,8 +467,9 @@ static void command_prompt_help(struct add_i_state *s)int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL }; +struct print_command_item_data data;struct list_and_choose_options main_loop_opts = { - {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, + {4 ,N_ ("*** Commands ***" ), print_command_item, &data },N_ ("What now" ), command_prompt_help };struct command_item
@@ -477,6 +492,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)if (init_add_i_state (r, &s))return error ("could not parse `add -i` config" ); +/* +* When color was asked for, use the prompt color for +* highlighting, otherwise use square brackets. +*/ +if (s.use_color) { + data.color = s.prompt_color; + data.reset = s.reset_color; + }else { + data.color ="[" ; + data.reset ="]" ; + } +strbuf_addstr (&header," " );strbuf_addf (&header, print_file_item_data.modified_fmt,_ ("staged" ),_ ("unstaged" ),_ ("path" ));
Signature · 1 line
gitgitgadget
PATCH v3 11/11 built-in add -i: implement the `help` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[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 | 27 +++++++++++++++++++++++++-- t/t3701-add-interactive.sh | 25 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-)
add-interactive.c +25 -2
@@ -428,6 +428,27 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return 0 ; } +static int run_help (struct add_i_state *s,const struct pathspec *ps, +struct file_list *files,struct list_options *opts) +{ +const char *help_color = s->help_color; + +color_fprintf_ln (stdout, help_color,"status - %s" , +_ ("show paths with changes" )); +color_fprintf_ln (stdout, help_color,"update - %s" , +_ ("add working tree state to the staged set of changes" )); +color_fprintf_ln (stdout, help_color,"revert - %s" , +_ ("revert staged set of changes back to the HEAD version" )); +color_fprintf_ln (stdout, help_color,"patch - %s" , +_ ("pick hunks and update selectively" )); +color_fprintf_ln (stdout, help_color,"diff - %s" , +_ ("view diff between HEAD and index" )); +color_fprintf_ln (stdout, help_color,"add untracked - %s" , +_ ("add contents of untracked files to the staged set of changes" )); + +return 0 ; +} +struct print_command_item_data {const char *color, *reset; };
@@ -473,9 +494,11 @@ int run_add_i(struct repository *r, const struct pathspec *ps)N_ ("What now" ), command_prompt_help };struct command_item - status = { {"status" }, run_status }; + status = { {"status" }, run_status }, + help = { {"help" }, run_help };struct command_item *commands[] = { - &status + &status, + &help }; struct print_file_item_data print_file_item_data = {
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
Hi Junio,
Quoted 25 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.
As I am a heavy user of git add -p myself and use a patched version for weeks already (it is so nice to not suffer over one second startup until the MSYS2 Perl finally shows me anything, instead it feels instantaneous), I integrated these patch series into Git for Windows' master already, as an opt-in feature guarded by the config variable add.interactive.useBuiltin (and Git for Windows' installer is prepared to detect this version and offer the option in the graphical user interface).
Quoted 2 lines
I had planned on submitting this before v2.22.0-rc0, but there was such a backlog of builds from a big pushout that I had to wait ;-)
FWIW the v2 round has not seen any objections...
Maybe pick it up in pu this time round?
Thanks,
Dscho
Re: [PATCH v3 01/11] 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 1 line · writes:
+add.interactive.useBuiltin::
I am not sure if three-level name is a good thing to use here.
If we have end-user controllable (like branch or remote names) unbounded number of subcommand/submode to "add", and "interactive" is merely one of it, then three-level name is a perfect fit, but otherwise, not.
builtin/add.c int run_add_interactive(const char *revision, const char *patch_mode, · line 196
Quoted 12 lines
builtin/add.c +8
-0@@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode, {int status, i;struct argv_array argv =ARGV_ARRAY_INIT ; +int use_builtin_add_i = +git_env_bool ("GIT_TEST_ADD_I_USE_BUILTIN" ,-1 ); +if (use_builtin_add_i <0 ) +git_config_get_bool ("add.interactive.usebuiltin" , + &use_builtin_add_i); + +if (use_builtin_add_i ==1 && !patch_mode) +return !!run_add_i (the_repository, pathspec);
I am hoping that eventually "add -p" will also be routed to the new codepath. Would it make sense to have "&& !patch_mode" here, especially at this step where run_add_i() won't do anything useful anyway yet?
builtin/add.c static int add_config(const char *var, const char *value, void *cb) · line 333
Quoted 7 lines
builtin/add.c +1
-0@@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb) ignore_add_errors =git_config_bool (var, value);return 0 ; } +return git_default_config (var, value, cb); }
Good addition while at it.
Re: [PATCH v3 02/11] diff: export diffstat interface
In reply to Daniel Ferreira via GitGitGadgetQuoted 21 lines · Daniel Ferreira via GitGitGadget
diff.c +4
-6@@ -6273,12 +6257,7 @@ void diff_flush(struct diff_options *options) dirstat_by_line) {struct diffstat_t diffstat; -memset (&diffstat,0 ,sizeof (struct diffstat_t )); -for (i =0 ; i < q->nr; i++) { -struct diff_filepair *p = q->queue[i]; -if (check_pair_status (p)) -diff_flush_stat (p, options, &diffstat); - } +compute_diffstat (options, &diffstat, q);if (output_format &DIFF_FORMAT_NUMSTAT )show_numstat (&diffstat, options);if (output_format &DIFF_FORMAT_DIFFSTAT )@@ -6611,6 +6590,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)return ignored; } +void compute_diffstat (struct diff_options *options, +struct diffstat_t *diffstat, +struct diff_queue_struct *q)
diff.c static int is_submodule_ignored(const char *path, struct diff_options *options) · line 6605
Quoted 10 lines
diff.c +10
-0+{ +int i; + +memset (diffstat,0 ,sizeof (struct diffstat_t )); +for (i =0 ; i < q->nr; i++) { +struct diff_filepair *p = q->queue[i]; +if (check_pair_status (p)) +diff_flush_stat (p, options, diffstat); + } +}
Hmm, (1) clearing diffstat struct to initialize, (2) looping over diff_queue to compute stat for each path, (3) using diffstat information and then (4) finally freeing the diffstat info is the bog-standard sequence of the user of this API. Merging step (1) and
(2) may probably be OK (iow, I do not think of a use pattern for future users where being able to do some custom things between steps
(1) and (2) would be useful), which is this function is about. (3) is what the user of this API would do, but shouldn't (4) be exported at the same time, if we are making (1+2) as an external API?
Quoted 39 lines
diff.c +0
-0void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const struct object_id *oid,diff.h +19
-0@@ -244,6 +244,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);void diff_emit_submodule_pipethrough (struct diff_options *o,const char *line,int len); +struct diffstat_t { +int nr; +int alloc; +struct diffstat_file { +char *from_name; +char *name; +char *print_name; +const char *comments; +unsigned is_unmerged:1 ; +unsigned is_binary:1 ; +unsigned is_renamed:1 ; +unsigned is_interesting:1 ; +uintmax_t added, deleted; + } **files; +}; +enum color_diff {DIFF_RESET =0 ,DIFF_CONTEXT =1 ,@@ -333,6 +349,9 @@ void diff_change(struct diff_options *,struct diff_filepair *diff_unmerge (struct diff_options *,const char *path); +void compute_diffstat (struct diff_options *options,struct diffstat_t *diffstat, +struct diff_queue_struct *q); +#define DIFF_SETUP_REVERSE 1#define DIFF_SETUP_USE_SIZE_CACHE 4
Re: [PATCH v3 03/11] built-in add -i: implement the `status` command
In reply to Daniel Ferreira via GitGitGadgetQuoted 18 lines · Daniel Ferreira via GitGitGadget
add-interactive.c +18
-0+struct item { +const char *name; +}; + +struct list_options { +const char *header; +void (*print_item)(int i,struct item *item,void *print_item_data); +void *print_item_data; +}; + +struct adddel { +uintmax_t add, del; +unsigned seen:1 , binary:1 ; +}; + +struct file_list { + struct file_item { + struct item item;
add-interactive.c line 84
Quoted 10 lines
add-interactive.c +10
-0+struct adddel index, worktree; + } **file; +size_t nr, alloc; +}; + +struct pathname_entry { +struct hashmap_entry ent; +size_t index; +char pathname[FLEX_ARRAY ]; +};
All of the above are named too generic but assuming that add-i will stay to be a single file and these names will never leak outside the file to become global, it would be perfectly fine.
add-interactive.c line 204
Quoted 11 lines
add-interactive.c +11
-0+static void populate_wi_changes (struct strbuf *buf, +struct adddel *ad,const char *no_changes) +{ +if (ad->binary) +strbuf_addstr (buf,_ ("binary" )); +else if (ad->seen) +strbuf_addf (buf,"+%" PRIuMAX"/-%" PRIuMAX, + (uintmax_t )ad->add, (uintmax_t )ad->del); +else +strbuf_addstr (buf, no_changes); +}
I offhand do not see the need for (uintmax_t) casts here...
add-interactive.c line 239
Quoted 11 lines
add-interactive.c +11
-0+static int run_status (struct repository *r,const struct pathspec *ps, +struct file_list *files,struct list_options *opts) +{ +reset_file_list (files); + +if (get_modified_files (r, files, ps) <0 ) +return -1 ; + +if (files->nr) +list ((struct item **)files->file, files->nr, opts); +putchar ('\n' );
So, if there is anything to list, we show list() and then add an empty line; if there is nothing to list, we show an empty line anyway?
As long as that matches the current scripted "add -i", it's perfectly fine. It's just that the code structure above looked somewhat odd.
Quoted 32 lines
add-interactive.c +32
-0+static void collect_changes_cb (struct diff_queue_struct *q, +struct diff_options *options, +void *data) +{ +struct collection_status *s = data; +struct diffstat_t stat = {0 }; +int i; + +if (!q->nr) +return ; + +compute_diffstat (options, &stat, q); + +for (i =0 ; i < stat.nr; i++) { +const char *name = stat.files[i]->name; +int hash =strhash (name); +struct pathname_entry *entry; +size_t file_index; +struct file_item *file; +struct adddel *adddel; + + entry =hashmap_get_from_hash (&s->file_map, hash, name); +if (entry) + file_index = entry->index; +else { +FLEX_ALLOC_STR (entry, pathname, name); +hashmap_entry_init (entry, hash); + entry->index = file_index = s->list->nr; +hashmap_add (&s->file_map, entry); + +add_file_item (s->list, name); + }
add-interactive.c line 146
Quoted 10 lines
add-interactive.c +10
-0+ file = s->list->file[file_index]; + + adddel = s->phase ==FROM_INDEX ? &file->index : &file->worktree; + adddel->seen =1 ; + adddel->add = stat.files[i]->added; + adddel->del = stat.files[i]->deleted; +if (stat.files[i]->is_binary) + adddel->binary =1 ; + } +}
Would resources held in the "stat" structure leak at the end of this function?
Re: [PATCH v3 06/11] built-in add -i: implement the main loop
In reply to Johannes Schindelin via GitGitGadget"Johannes Schindelin via GitGitGadget" <[email protected]>
Quoted 4 lines · writes:
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.
Makes sense. After all, I recall writing "add -i" in a few iterations of lunch-time hack. If we are to rewrite it from scratch, we should engineer it the right way ;-)
Re: [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings
In reply to Slavica Djukic via GitGitGadgetQuoted 7 lines · Slavica Djukic via GitGitGadget
The idea: for each item that we add, we generate prefixes starting with the first letter, then the first two letters, then three, etc, until we find a prefix that is unique (or until the prefix length would be longer than we want). If we encounter a previously-unique prefix on the way, we adjust that item's prefix to make it unique again (or we mark it as having no unique prefix if we failed to find one). These partial prefixes are stored in a hash map (for quick lookup times).
OK. I suppose such a machinery that accepts a set of strings and then give us back a set of unique prefix length for each element of the set would be useful in general, even outside the "and choose" part of "list and choose". Nice design.
Quoted 2 lines
To make sure that this function works as expected, we add a test using a special-purpose test helper that was added for that purpose.
Somehow the above repeatedly says how special purpose it is redundantly ;-)
Quoted 36 lines
prefix-map.h new file +30
-0@@ -0,0 +1,40 @@ +#ifndef PREFIX_MAP_H +#define PREFIX_MAP_H + +#include "hashmap.h" + +struct prefix_item { +const char *name; +size_t prefix_length; +}; + +struct prefix_map_entry { +struct hashmap_entry e; +const char *name; +size_t prefix_length; +/* if item is NULL, the prefix is not unique */ +struct prefix_item *item; +}; + +struct prefix_map { +struct hashmap map; +int min_length, max_length; +}; + +/* +* Find unique prefixes in a given list of strings. +* +* Typically, the `struct prefix_item` information will be but a field in the +* actual item struct; For this reason, the `list` parameter is specified as a +* list of pointers to the items. +*
prefix-map.h line 40
Quoted 10 lines
prefix-map.h new file +10
-0+* The `min_length`/`max_length` parameters define what length the unique +* prefixes should have. +* +* If no unique prefix could be found for a given item, its `prefix_length` +* will be set to 0. +*/ +void find_unique_prefixes (struct prefix_item **list,size_t nr, +int min_length,int max_length); + +#endif
Looks like a quite sane interface to me.
Thanks.
Re: [PATCH v3 11/11] built-in add -i: implement the `help` command
In reply to Johannes Schindelin via GitGitGadget"Johannes Schindelin via GitGitGadget" <[email protected]>
Quoted 7 lines · writes:
add-interactive.c +7
-0+static int run_help (struct add_i_state *s,const struct pathspec *ps, +struct file_list *files,struct list_options *opts) +{ +const char *help_color = s->help_color; + +color_fprintf_ln (stdout, help_color,"status - %s" , +_ ("show paths with changes" ));
add-interactive.c static int run_status(struct add_i_state *s, const struct pathspec *ps, · line 447
Quoted 10 lines
add-interactive.c +10
-0+color_fprintf_ln (stdout, help_color,"update - %s" , +_ ("add working tree state to the staged set of changes" )); +color_fprintf_ln (stdout, help_color,"revert - %s" , +_ ("revert staged set of changes back to the HEAD version" )); +color_fprintf_ln (stdout, help_color,"patch - %s" , +_ ("pick hunks and update selectively" )); +color_fprintf_ln (stdout, help_color,"diff - %s" , +_ ("view diff between HEAD and index" )); +color_fprintf_ln (stdout, help_color,"add untracked - %s" , +_ ("add contents of untracked files to the staged set of changes" ));
As we do not allow the command names to get translated, this makes sense.
Have we adopted the convention to name callback parameters that have to stay unused (because the callback function must have a function signature that accepts the union of what everybody needs to take) differently from the parameters that actually get used? It may make sense to use it in a function like this, to prevent readers from wasting time by wondering how pathspec is used in this function, for example.
Quoted 22 lines
t/t3701-add-interactive.sh +14
-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>
t/t3701-add-interactive.sh test_expect_success 'checkout -p works with pathological context lines' ' · line 673
Quoted 10 lines
t/t3701-add-interactive.sh +10
-0+ <BOLD>*** Commands ***<RESET> + 1: <BOLD;BLUE>s<RESET>tatus 2: <BOLD;BLUE>u<RESET>pdate 3: <BOLD;BLUE>r<RESET>evert 4: <BOLD;BLUE>a<RESET>dd untracked + 5: <BOLD;BLUE>p<RESET>atch 6: <BOLD;BLUE>d<RESET>iff 7: <BOLD;BLUE>q<RESET>uit 8: <BOLD;BLUE>h<RESET>elp + <BOLD;BLUE>What now<RESET>>$SP + Bye. + EOF + test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored && + test_decode_color <actual.colored >actual && + test_i18ncmp expect actual +'
Nicely done.
Quoted 7 lines · Johannes Schindelin
Quoted 7 lines
As I am a heavy user of git add -p myself and use a patched version for weeks already (it is so nice to not suffer over one second startup until the MSYS2 Perl finally shows me anything, instead it feels instantaneous), I integrated these patch series into Git for Windows' master already, as an opt-in feature guarded by the config variable add.interactive.useBuiltin (and Git for Windows' installer is prepared to detect this version and offer the option in the graphical user interface).
I've sent comments on a few patches in the series; overall it was a pleasant read.
Thanks.
Re: [PATCH v3 11/11] built-in add -i: implement the `help` command
In reply to Junio C HamanoQuoted 7 lines · Junio C Hamano
add-interactive.c static int run_status(struct add_i_state *s, const struct pathspec *ps, · line 432
Quoted 2 lines
add-interactive.c +2
-0+static int run_help (struct add_i_state *s,const struct pathspec *ps, +struct file_list *files,struct list_options *opts)[...]
As we do not allow the command names to get translated, this makes sense.
Quoted 7 lines
Have we adopted the convention to name callback parameters that have to stay unused (because the callback function must have a function signature that accepts the union of what everybody needs to take) differently from the parameters that actually get used? It may make sense to use it in a function like this, to prevent readers from wasting time by wondering how pathspec is used in this function, for example.
I haven't yet[1] polished up the remainder of my patches to make us -Wunused-parameter clean, but the pattern there would look like:
void some_function(const char *foo, void *UNUSED(bar))
{
... use foo but not bar ...
}which both tells the compiler that "bar" may be unused, and renames it behind the scenes to unused_bar so that it cannot be accidentally used (or more importantly, so that we can drop the annotation when it does get used).
All of which is to say that I'm fine if you call it "unused_bar" manually for now, but I'd switch it to the above in my series. So it may not matter all that much in the meantime.
-Peff
[1] The sticking point is a few more cases I found where it's unclear to me whether they should be marked, or if it's a latent bug.
Re: [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings
In reply to Slavica Djukic via GitGitGadgetQuoted 130 lines · Slavica Djukic via GitGitGadget
In the
git add -icommand, we show unique prefixes of the commands and files, to give an indication what prefix would select them.Naturally, the C implementation looks a lot different than the Perl
implementation: in Perl, a trie is much easier implemented, while we already have a pretty neat hashmap implementation in C that we use for the purpose of storing (not necessarily unique) prefixes.The idea: for each item that we add, we generate prefixes starting with the first letter, then the first two letters, then three, etc, until we find a prefix that is unique (or until the prefix length would be longer than we want). If we encounter a previously-unique prefix on the way, we adjust that item's prefix to make it unique again (or we mark it as having no unique prefix if we failed to find one). These partial prefixes are stored in a hash map (for quick lookup times).
To make sure that this function works as expected, we add a test using a special-purpose test helper that was added for that purpose.
Note: We expect the list of prefix items to be passed in as a list of pointers rather than as regular list to avoid having to copy information (the actual items will most likely contain more information than just the name and the length of the unique prefix, but passing in
struct prefix_item *would not allow for that).prefix-map.c new file +99
-0@@ -0,0 +1,109 @@ +#include "cache.h" +#include "prefix-map.h" + +static int map_cmp (const void *unused_cmp_data, +const void *entry, +const void *entry_or_key, +const void *unused_keydata) +{ +const struct prefix_map_entry *a = entry; +const struct prefix_map_entry *b = entry_or_key; + +return a->prefix_length != b->prefix_length || +strncmp (a->name, b->name, a->prefix_length); +} + +static void add_prefix_entry (struct hashmap *map,const char *name, +size_t prefix_length,struct prefix_item *item) +{ +struct prefix_map_entry *result =xmalloc (sizeof (*result)); + result->name = name; + result->prefix_length = prefix_length; + result->item = item; +hashmap_entry_init (result,memhash (name, prefix_length)); +hashmap_add (map, result); +} + +static void init_prefix_map (struct prefix_map *prefix_map, +int min_prefix_length,int max_prefix_length) +{ +hashmap_init (&prefix_map->map, map_cmp,NULL ,0 ); + prefix_map->min_length = min_prefix_length; + prefix_map->max_length = max_prefix_length; +} + +static void add_prefix_item (struct prefix_map *prefix_map, +struct prefix_item *item) +{ +struct prefix_map_entry e = { {NULL } }, *e2; +int j; + + e.item = item; + e.name = item->name; + +for (j = prefix_map->min_length; + j <= prefix_map->max_length && e.name[j]; j++) { +/* Avoid breaking UTF-8 multi-byte sequences */ +if (!isascii (e.name[j])) +break ; + + e.prefix_length = j; +hashmap_entry_init (&e,memhash (e.name, j)); + e2 =hashmap_get (&prefix_map->map, &e,NULL ); +if (!e2) { +/* prefix is unique at this stage */ + item->prefix_length = j; +add_prefix_entry (&prefix_map->map, e.name, j, item); +break ; + } + +if (!e2->item) +continue ;/* non-unique prefix */ + +if (j != e2->item->prefix_length ||memcmp (e.name, e2->name, j)) +BUG ("unexpected prefix length: %d != %d (%s != %s)" , + j, (int )e2->item->prefix_length, e.name, e2->name); + +/* skip common prefix */ +for (; j < prefix_map->max_length && e.name[j]; j++) { +if (e.item->name[j] != e2->item->name[j]) +break ; +add_prefix_entry (&prefix_map->map, e.name, j +1 , +NULL ); + } + +/* e2 no longer refers to a unique prefix */ +if (j < prefix_map->max_length && e2->name[j]) { +/* found a new unique prefix for e2's item */ + e2->item->prefix_length = j +1 ; +add_prefix_entry (&prefix_map->map, e2->name, j +1 , + e2->item); + } +else + e2->item->prefix_length =0 ; + e2->item =NULL ; + +if (j < prefix_map->max_length && e.name[j]) { +/* found a unique prefix for the item */ + e.item->prefix_length = j +1 ; +add_prefix_entry (&prefix_map->map, e.name, j +1 , + e.item); + }else +/* item has no (short enough) unique prefix */ + e.item->prefix_length =0 ; + +break ; + } +} + +void find_unique_prefixes(struct prefix_item **list, size_t nr,
prefix-map.c line 109
Quoted 10 lines
prefix-map.c new file +10
-0+ int min_length, int max_length) +{ + int i; +struct prefix_map prefix_map; + +init_prefix_map (&prefix_map, min_length, max_length); +for (i =0 ; i < nr; i++) +add_prefix_item (&prefix_map, list[i]); +hashmap_free (&prefix_map.map,1 ); +}
Between the commit message, the in-code comment, the names of the new files, and implementation I was left somewhat confused about what this is about and how it works. TBH, I didn't even try to understand how all the above works, in particular the add_prefix_item() function.
However, I think it would be much-much simpler to first sort (a copy of?) the array of prefix item pointers based on their 'name' field, and then look for a unique prefix in each neighboring pair. Perhaps it would even be faster, because it doesn't have to allocate a bunch of hashmap items, though I don't think that it matters much in practice (i.e. I expect the number of items to be fairly small; presumably nobody will run interactive add after a mass refactoring modifying thousands of files).
Quoted 6 lines
prefix-map.h new file +0
-0
prefix-map.h line 9
Quoted 9 lines
prefix-map.h new file +8
-0@@ -0,0 +1,40 @@ +#ifndef PREFIX_MAP_H +#define PREFIX_MAP_H +#include "hashmap.h" + +struct prefix_item { +const char *name; +size_t prefix_length; +};
This struct is part of find_unique_prefixes()'s signature, good.
prefix-map.h line 22
Quoted 12 lines
prefix-map.h new file +12
-0+struct prefix_map_entry { +struct hashmap_entry e; +const char *name; +size_t prefix_length; +/* if item is NULL, the prefix is not unique */ +struct prefix_item *item; +}; + +struct prefix_map { +struct hashmap map; +int min_length, max_length; +};
However, neither of these two structs nor the hashmap appear in the function's signature, but are all implementation details. Therefore, they should not be defined and included here in the header but in the .c source file. (But as mentioned above, I think this could be implemented much simpler without these data structures.)
Furthermore, this is not a map.
A map, in general, is a container of key-value pairs that allows efficient insertion, removal and lookup. This so-called prefix_map does none of that, so it should not be called a map.
Quoted 2 lines
+/*
+ * Find unique prefixes in a given list of strings.
... and stores the length of the unique prefixes in the 'prefix_length' field of the elements of the given array.
Quoted 2 lines
+ *
+ * Typically, thestruct prefix_iteminformation will be but a field in the
s/but //, perhaps?
Quoted 10 lines
+ * actual item struct; For this reason, the
listparameter is specified as a
+ * list of pointers to the items.
+ *
+ * Themin_length/max_lengthparameters define what length the unique
+ * prefixes should have.
+ *
+ * If no unique prefix could be found for a given item, itsprefix_length
+ * will be set to 0.
+ */
+void find_unique_prefixes(structprefix_item**list,size_tnr,
The first argument is not a list but an array.
prefix-map.h line 38
Quoted 1 line
prefix-map.h new file +1
-0+ int min_length, int max_length);
size_t, perhaps? These are closely related to 'prefix_item.prefix_length', which is already (rightfully) size_t.
Quoted 32 lines
prefix-map.h new file +2
-0+ +#endif t/helper/test-prefix-map.c new file +24
-0@@ -0,0 +1,58 @@ +#include "test-tool.h" +#include "cache.h" +#include "prefix-map.h" + +static size_t test_count, failed_count; + +static void check (int succeeded,const char *file,size_t line_no, +const char *fmt, ...) +{ +va_list ap; + + test_count++; +if (succeeded) +return ; + +va_start (ap, fmt); +fprintf (stderr,"%s:%d: " , file, (int )line_no); +vfprintf (stderr, fmt, ap); +fputc ('\n' , stderr); +va_end (ap); + + failed_count++; +} +
t/helper/test-prefix-map.c line 34
Quoted 10 lines
t/helper/test-prefix-map.c new file +10
-0+#define EXPECT_SIZE_T_EQUALS (expect, actual, hint) \ + check(expect == actual, __FILE__, __LINE__, \ + "size_t's do not match: %" \ + PRIdMAX " != %" PRIdMAX " (%s) (%s)", \ + (intmax_t)expect, (intmax_t)actual, #actual, hint) + +int cmd__prefix_map (int argc,const char **argv) +{ +#define NR 5 +struct prefix_item items[NR ] = {
You don't have to tell the compiler how many elements this array will contain, it will figure that out on its own.
t/helper/test-prefix-map.c line 41
Quoted 7 lines
t/helper/test-prefix-map.c new file +7
-0+ { "unique" }, + { "hell" }, + { "hello" }, + { "wok" }, + { "world" }, + }; + struct prefix_item *list[NR] = {
Likewise.
t/helper/test-prefix-map.c line 45
Quoted 4 lines
t/helper/test-prefix-map.c new file +4
-0+ items, items + 1, items + 2, items + 3, items + 4 + }; + +find_unique_prefixes (list,NR ,1 ,3 );
This could be find_unique_prefixes(list, ARRAY_SIZE(list), 1, 3), and then there is no need for that NR macro anymore.
Quoted 13 lines
t/helper/test-prefix-map.c new file +13
-0+ +#define EXPECT_PREFIX_LENGTH_EQUALS (expect, index) \ + EXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \ + list[index]->name) + +EXPECT_PREFIX_LENGTH_EQUALS (1 ,0 ); +EXPECT_PREFIX_LENGTH_EQUALS (0 ,1 ); +EXPECT_PREFIX_LENGTH_EQUALS (0 ,2 ); +EXPECT_PREFIX_LENGTH_EQUALS (3 ,3 ); +EXPECT_PREFIX_LENGTH_EQUALS (3 ,4 ); + +return !!failed_count; +}
Re: [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`
In reply to Junio C HamanoHi Junio,
Quoted 11 lines · Junio C Hamano
"Johannes Schindelin via GitGitGadget" <[email protected]>
Quoted 1 line · writes:
+add.interactive.useBuiltin::
I am not sure if three-level name is a good thing to use here.
If we have end-user controllable (like branch or remote names) unbounded number of subcommand/submode to "add", and "interactive" is merely one of it, then three-level name is a perfect fit, but otherwise, not.
Well, my thinking was that add.useBuiltin would be misleading (because the non-interactive part of git add is _already_ built-in, even git add -e is built-in). And addInteractive.useBuiltin, to me, would pretend that add-interactive is the name of the command.
Besides, I really hope that this would be only temporary, as I already have a fully-built-in git add -i and git add -p in Git for Windows, as an experimental opt-in, and so far it looks like it could replace the scripted version relatively soon, so maybe that particular part is not worth all that much worry ;-)
Quoted 9 lines
Quoted 9 lines
builtin/add.c +5
-0@@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode, {int status, i;struct argv_array argv =ARGV_ARRAY_INIT ; +int use_builtin_add_i = +git_env_bool ("GIT_TEST_ADD_I_USE_BUILTIN" ,-1 ); +if (use_builtin_add_i <0 ) +git_config_get_bool ("add.interactive.usebuiltin" , + &use_builtin_add_i);
Quoted 8 lines
builtin/add.c int run_add_interactive(const char *revision, const char *patch_mode, · line 196
Quoted 3 lines
builtin/add.c +3
-0+ +if (use_builtin_add_i ==1 && !patch_mode) +return !!run_add_i (the_repository, pathspec);I am hoping that eventually "add -p" will also be routed to the new codepath. Would it make sense to have "&& !patch_mode" here, especially at this step where
run_add_i()won't do anything useful anyway yet?
The && !patch_mode is here to allow for a gradual adoption of the built-in parts. I don't want users who opted in to using the built-in git add -i to be stopped from using git add -p, so I don't want to print even a warning, let alone an error message, when the patch mode needs to run under add.interactive.useBuiltin = true, even if that part is still scripted-only.
Of course, eventually this will be handled. See
https://github.com/gitgitgadget/git/pull/173 for the yet-to-be-contributed patch series.
I just don't want to send a multi-dozen patch series. I really don't think there is any effective way to review such a long patch series, let alone an efficient way to develop it incrementally based on feedback on the mailing list, hance I broke things up into 6 separate patch series (as indicated by the cover letter), and this one is the first of them.
Quoted 9 lines
builtin/add.c static int add_config(const char *var, const char *value, void *cb) · line 333
Quoted 7 lines
builtin/add.c +1
-0@@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb) ignore_add_errors =git_config_bool (var, value);return 0 ; } +return git_default_config (var, value, cb); }Good addition while at it.
:-)
This was actually an oversight, sorry... But since you're in favor ;-)
Ciao,
Dscho
Re: [PATCH v3 02/11] diff: export diffstat interface
In reply to Junio C HamanoHi Junio,
Quoted 35 lines · Junio C Hamano
Quoted 21 lines · Daniel Ferreira via GitGitGadget
diff.c +4
-6@@ -6273,12 +6257,7 @@ void diff_flush(struct diff_options *options) dirstat_by_line) {struct diffstat_t diffstat; -memset (&diffstat,0 ,sizeof (struct diffstat_t )); -for (i =0 ; i < q->nr; i++) { -struct diff_filepair *p = q->queue[i]; -if (check_pair_status (p)) -diff_flush_stat (p, options, &diffstat); - } +compute_diffstat (options, &diffstat, q);if (output_format &DIFF_FORMAT_NUMSTAT )show_numstat (&diffstat, options);if (output_format &DIFF_FORMAT_DIFFSTAT )@@ -6611,6 +6590,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)return ignored; } +void compute_diffstat (struct diff_options *options, +struct diffstat_t *diffstat, +struct diff_queue_struct *q)diff.c static int is_submodule_ignored(const char *path, struct diff_options *options) · line 6605
Quoted 10 lines
diff.c +10
-0+{ +int i; + +memset (diffstat,0 ,sizeof (struct diffstat_t )); +for (i =0 ; i < q->nr; i++) { +struct diff_filepair *p = q->queue[i]; +if (check_pair_status (p)) +diff_flush_stat (p, options, diffstat); + } +}Hmm, (1) clearing diffstat struct to initialize, (2) looping over
Quoted 8 lines
diff_queueto compute stat for each path, (3) using diffstat information and then (4) finally freeing the diffstat info is the bog-standard sequence of the user of this API. Merging step (1) and
(2) may probably be OK (iow, I do not think of a use pattern for future users where being able to do some custom things between steps
(1) and (2) would be useful), which is this function is about. (3) is what the user of this API would do, but shouldn't (4) be exported at the same time, if we are making (1+2) as an external API?
Good point.
It _also_ hints at the fact that we're not releasing the memory properly after running the diffstat in the built-in add -i.
Will fix,
Dscho
Quoted 41 lines
Quoted 40 lines
diff.c +0
-0void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const struct object_id *oid,diff.h +19
-0@@ -244,6 +244,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err);void diff_emit_submodule_pipethrough (struct diff_options *o,const char *line,int len); +struct diffstat_t { +int nr; +int alloc; +struct diffstat_file { +char *from_name; +char *name; +char *print_name; +const char *comments; +unsigned is_unmerged:1 ; +unsigned is_binary:1 ; +unsigned is_renamed:1 ; +unsigned is_interesting:1 ; +uintmax_t added, deleted; + } **files; +}; +enum color_diff {DIFF_RESET =0 ,DIFF_CONTEXT =1 ,@@ -333,6 +349,9 @@ void diff_change(struct diff_options *,struct diff_filepair *diff_unmerge (struct diff_options *,const char *path); +void compute_diffstat (struct diff_options *options,struct diffstat_t *diffstat, +struct diff_queue_struct *q); +#define DIFF_SETUP_REVERSE 1#define DIFF_SETUP_USE_SIZE_CACHE 4
Re: [PATCH v3 03/11] built-in add -i: implement the `status` command
In reply to Junio C HamanoHi Junio,
Quoted 26 lines · Junio C Hamano
Quoted 24 lines · Daniel Ferreira via GitGitGadget
add-interactive.c +24
-0+struct item { +const char *name; +}; + +struct list_options { +const char *header; +void (*print_item)(int i,struct item *item,void *print_item_data); +void *print_item_data; +}; + +struct adddel { +uintmax_t add, del; +unsigned seen:1 , binary:1 ; +}; + +struct file_list { +struct file_item { +struct item item; +struct adddel index, worktree; + } **file; +size_t nr, alloc; +}; + +struct pathname_entry {
Quoted 8 lines
add-interactive.c line 84
Quoted 4 lines
add-interactive.c +4
-0+struct hashmap_entry ent; +size_t index; +char pathname[FLEX_ARRAY ]; +};All of the above are named too generic but assuming that add-i will stay to be a single file and these names will never leak outside the file to become global, it would be perfectly fine.
Yep.
Quoted 5 lines
Quoted 5 lines
add-interactive.c +5
-0+static void populate_wi_changes (struct strbuf *buf, +struct adddel *ad,const char *no_changes) +{ +if (ad->binary) +strbuf_addstr (buf,_ ("binary" ));
Quoted 8 lines
add-interactive.c line 204
Quoted 6 lines
add-interactive.c +6
-0+else if (ad->seen) +strbuf_addf (buf,"+%" PRIuMAX"/-%" PRIuMAX, + (uintmax_t )ad->add, (uintmax_t )ad->del); +else +strbuf_addstr (buf, no_changes); +}I offhand do not see the need for (
uintmax_t) casts here...
Ah, that's my mistake. I had converted the add and del fields from the unsigned long data type that the 20th century wanted back.
Quoted 16 lines
add-interactive.c line 239
Quoted 11 lines
add-interactive.c +11
-0+static int run_status (struct repository *r,const struct pathspec *ps, +struct file_list *files,struct list_options *opts) +{ +reset_file_list (files); + +if (get_modified_files (r, files, ps) <0 ) +return -1 ; + +if (files->nr) +list ((struct item **)files->file, files->nr, opts); +putchar ('\n' );So, if there is anything to list, we show
list()and then add an empty line; if there is nothing to list, we show an empty line anyway?
Quoted 3 lines
As long as that matches the current scripted "add -i", it's perfectly fine. It's just that the code structure above looked somewhat odd.
Yep, this is the Perl version:
sub status_cmd {
list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },
list_modified());
print "\n";
}Quoted 37 lines
Quoted 37 lines
add-interactive.c +37
-0+static void collect_changes_cb (struct diff_queue_struct *q, +struct diff_options *options, +void *data) +{ +struct collection_status *s = data; +struct diffstat_t stat = {0 }; +int i; + +if (!q->nr) +return ; + +compute_diffstat (options, &stat, q); + +for (i =0 ; i < stat.nr; i++) { +const char *name = stat.files[i]->name; +int hash =strhash (name); +struct pathname_entry *entry; +size_t file_index; +struct file_item *file; +struct adddel *adddel; + + entry =hashmap_get_from_hash (&s->file_map, hash, name); +if (entry) + file_index = entry->index; +else { +FLEX_ALLOC_STR (entry, pathname, name); +hashmap_entry_init (entry, hash); + entry->index = file_index = s->list->nr; +hashmap_add (&s->file_map, entry); + +add_file_item (s->list, name); + } + file = s->list->file[file_index]; + + adddel = s->phase ==FROM_INDEX ? &file->index : &file->worktree; + adddel->seen =1 ; + adddel->add = stat.files[i]->added;
Quoted 8 lines
add-interactive.c line 146
Quoted 5 lines
add-interactive.c +5
-0+ adddel->del = stat.files[i]->deleted; +if (stat.files[i]->is_binary) + adddel->binary =1 ; + } +}Would resources held in the "stat" structure leak at the end of this function?
Yep, I added a call to the now-public free_diffstat_info() function.
Thanks,
Dscho
Re: [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings
In reply to SZEDER GáborHi Gábor,
Quoted 138 lines · SZEDER Gábor
Quoted 137 lines · Slavica Djukic via GitGitGadget
In the
git add -icommand, we show unique prefixes of the commands and files, to give an indication what prefix would select them.Naturally, the C implementation looks a lot different than the Perl
implementation: in Perl, a trie is much easier implemented, while we already have a pretty neat hashmap implementation in C that we use for the purpose of storing (not necessarily unique) prefixes.The idea: for each item that we add, we generate prefixes starting with the first letter, then the first two letters, then three, etc, until we find a prefix that is unique (or until the prefix length would be longer than we want). If we encounter a previously-unique prefix on the way, we adjust that item's prefix to make it unique again (or we mark it as having no unique prefix if we failed to find one). These partial prefixes are stored in a hash map (for quick lookup times).
To make sure that this function works as expected, we add a test using a special-purpose test helper that was added for that purpose.
Note: We expect the list of prefix items to be passed in as a list of pointers rather than as regular list to avoid having to copy information (the actual items will most likely contain more information than just the name and the length of the unique prefix, but passing in
struct prefix_item *would not allow for that).prefix-map.c new file +106
-0@@ -0,0 +1,109 @@ +#include "cache.h" +#include "prefix-map.h" + +static int map_cmp (const void *unused_cmp_data, +const void *entry, +const void *entry_or_key, +const void *unused_keydata) +{ +const struct prefix_map_entry *a = entry; +const struct prefix_map_entry *b = entry_or_key; + +return a->prefix_length != b->prefix_length || +strncmp (a->name, b->name, a->prefix_length); +} + +static void add_prefix_entry (struct hashmap *map,const char *name, +size_t prefix_length,struct prefix_item *item) +{ +struct prefix_map_entry *result =xmalloc (sizeof (*result)); + result->name = name; + result->prefix_length = prefix_length; + result->item = item; +hashmap_entry_init (result,memhash (name, prefix_length)); +hashmap_add (map, result); +} + +static void init_prefix_map (struct prefix_map *prefix_map, +int min_prefix_length,int max_prefix_length) +{ +hashmap_init (&prefix_map->map, map_cmp,NULL ,0 ); + prefix_map->min_length = min_prefix_length; + prefix_map->max_length = max_prefix_length; +} + +static void add_prefix_item (struct prefix_map *prefix_map, +struct prefix_item *item) +{ +struct prefix_map_entry e = { {NULL } }, *e2; +int j; + + e.item = item; + e.name = item->name; + +for (j = prefix_map->min_length; + j <= prefix_map->max_length && e.name[j]; j++) { +/* Avoid breaking UTF-8 multi-byte sequences */ +if (!isascii (e.name[j])) +break ; + + e.prefix_length = j; +hashmap_entry_init (&e,memhash (e.name, j)); + e2 =hashmap_get (&prefix_map->map, &e,NULL ); +if (!e2) { +/* prefix is unique at this stage */ + item->prefix_length = j; +add_prefix_entry (&prefix_map->map, e.name, j, item); +break ; + } + +if (!e2->item) +continue ;/* non-unique prefix */ + +if (j != e2->item->prefix_length ||memcmp (e.name, e2->name, j)) +BUG ("unexpected prefix length: %d != %d (%s != %s)" , + j, (int )e2->item->prefix_length, e.name, e2->name); + +/* skip common prefix */ +for (; j < prefix_map->max_length && e.name[j]; j++) { +if (e.item->name[j] != e2->item->name[j]) +break ; +add_prefix_entry (&prefix_map->map, e.name, j +1 , +NULL ); + } + +/* e2 no longer refers to a unique prefix */ +if (j < prefix_map->max_length && e2->name[j]) { +/* found a new unique prefix for e2's item */ + e2->item->prefix_length = j +1 ; +add_prefix_entry (&prefix_map->map, e2->name, j +1 , + e2->item); + } +else + e2->item->prefix_length =0 ; + e2->item =NULL ; + +if (j < prefix_map->max_length && e.name[j]) { +/* found a unique prefix for the item */ + e.item->prefix_length = j +1 ; +add_prefix_entry (&prefix_map->map, e.name, j +1 , + e.item); + }else +/* item has no (short enough) unique prefix */ + e.item->prefix_length =0 ; + +break ; + } +} + +void find_unique_prefixes (struct prefix_item **list,size_t nr, +int min_length,int max_length) +{ +int i; +struct prefix_map prefix_map; + +init_prefix_map (&prefix_map, min_length, max_length); +for (i =0 ; i < nr; i++)
Quoted 8 lines
prefix-map.c line 109
Quoted 3 lines
prefix-map.c new file +3
-0+add_prefix_item (&prefix_map, list[i]); +hashmap_free (&prefix_map.map,1 ); +}Between the commit message, the in-code comment, the names of the new files, and implementation I was left somewhat confused about what this is about and how it works. TBH, I didn't even try to understand how all the above works, in particular the
add_prefix_item()function.
Let me try to explain it here, and maybe you can help me by suggesting an improved commit message and/or code comments?
The problem is this: given a set of items with labels (e.g. file names), find, for each item, the unique prefix that identifies it. Example:
given the files hello.txt, heaven.txt and hell.txt, the items' unique prefixes would be hello, hea and hell., respectively.
In git add -i, we actually only want to allow alphanumerical prefixes, and we also want at least one, and at most three characters, so only the second item would have an admissible unique prefix: hea.
Quoted 8 lines
However, I think it would be much-much simpler to first sort (a copy of?) the array of prefix item pointers based on their 'name' field, and then look for a unique prefix in each neighboring pair. Perhaps it would even be faster, because it doesn't have to allocate a bunch of hashmap items, though I don't think that it matters much in practice (i.e. I expect the number of items to be fairly small; presumably nobody will run interactive add after a mass refactoring modifying thousands of files).
The time complexity of the sorted list would be O(n*log(n)), while the hashmap-based complexity would be an amortized O(n).
And yes, you would not _want_ to run interactive add after a mass refactoring. But it happens. It happens to me more times than I care to admit. And you know what? I really appreciate that even the Perl version is relatively snappy in those circumstances.
Quoted 28 lines
Quoted 6 lines
prefix-map.h new file +0
-0prefix-map.h line 9
Quoted 9 lines
prefix-map.h new file +8
-0@@ -0,0 +1,40 @@ +#ifndef PREFIX_MAP_H +#define PREFIX_MAP_H +#include "hashmap.h" + +struct prefix_item { +const char *name; +size_t prefix_length; +};This struct is part of find_unique_prefixes()'s signature, good.
Quoted 10 lines
prefix-map.h new file +10
-0+struct prefix_map_entry { +struct hashmap_entry e; +const char *name; +size_t prefix_length; +/* if item is NULL, the prefix is not unique */ +struct prefix_item *item; +}; + +struct prefix_map { +struct hashmap map;
Quoted 8 lines
prefix-map.h line 22
Quoted 2 lines
prefix-map.h new file +2
-0+int min_length, max_length; +};However, neither of these two structs nor the hashmap appear in the function's signature, but are all implementation details. Therefore, they should not be defined and included here in the header but in the .c source file. (But as mentioned above, I think this could be implemented much simpler without these data structures.)
Right you are!
Quoted 4 lines
Furthermore, this is not a map.
A map, in general, is a container of key-value pairs that allows efficient insertion, removal and lookup. This so-calledprefix_mapdoes none of that, so it should not be called a map.
What would you call it instead?
(I went with "map" because the underlying data structure is a "hash map", I know, not the best argument, but I failed to find a better name...)
I also have to admit that I thought that I could fix the design where git-add--interactive.perl creates this trie, but then still performs a linear search when searching by prefix. That seems not to be possible, though, as the unique prefixes are limited to certain character ranges, while the lookup-by-prefix is not limited in that way.
Quoted 5 lines
Quoted 2 lines
+/*
+ * Find unique prefixes in a given list of strings.... and stores the length of the unique prefixes in the '
prefix_length' field of the elements of the given array.
Good idea. I changed it to also explain what is meant by "unique prefix":
* Given a list of names, find unique prefixes (i.e. the first <n> characters
* that uniquely identify the names) and store the lengths of the unique
* prefixes in the 'prefix_length' field of the elements of the given array..
Quoted 4 lines
Quoted 2 lines
+ *
+ * Typically, thestruct prefix_iteminformation will be but a field in thes/but //, perhaps?
Sure. I am relatively certain that it is correct grammar, but it is probably a good idea to remove it.
Quoted 11 lines
Quoted 10 lines
+ * actual item struct; For this reason, the
listparameter is specified as a
+ * list of pointers to the items.
+ *
+ * Themin_length/max_lengthparameters define what length the unique
+ * prefixes should have.
+ *
+ * If no unique prefix could be found for a given item, itsprefix_length
+ * will be set to 0.
+ */
+void find_unique_prefixes(structprefix_item**list,size_tnr,
Quoted 1 line
The first argument is not a list but an array.
Indeed.
Quoted 4 lines
prefix-map.h line 38
Quoted 1 line
prefix-map.h new file +1
-0+ int min_length, int max_length);
size_t, perhaps? These are closely related to 'prefix_item.prefix_length', which is already (rightfully)size_t.
True.
Quoted 45 lines
Quoted 31 lines
prefix-map.h new file +1
-0+#endif t/helper/test-prefix-map.c new file +24
-0@@ -0,0 +1,58 @@ +#include "test-tool.h" +#include "cache.h" +#include "prefix-map.h" + +static size_t test_count, failed_count; + +static void check (int succeeded,const char *file,size_t line_no, +const char *fmt, ...) +{ +va_list ap; + + test_count++; +if (succeeded) +return ; + +va_start (ap, fmt); +fprintf (stderr,"%s:%d: " , file, (int )line_no); +vfprintf (stderr, fmt, ap); +fputc ('\n' , stderr); +va_end (ap); + + failed_count++; +} +t/helper/test-prefix-map.c line 34
Quoted 10 lines
t/helper/test-prefix-map.c new file +10
-0+#define EXPECT_SIZE_T_EQUALS (expect, actual, hint) \ + check(expect == actual, __FILE__, __LINE__, \ + "size_t's do not match: %" \ + PRIdMAX " != %" PRIdMAX " (%s) (%s)", \ + (intmax_t)expect, (intmax_t)actual, #actual, hint) + +int cmd__prefix_map (int argc,const char **argv) +{ +#define NR 5 +struct prefix_item items[NR ] = {You don't have to tell the compiler how many elements this array will contain, it will figure that out on its own.
Quoted 9 lines
t/helper/test-prefix-map.c line 41
Quoted 7 lines
t/helper/test-prefix-map.c new file +7
-0+ { "unique" }, + { "hell" }, + { "hello" }, + { "wok" }, + { "world" }, + }; + struct prefix_item *list[NR] = {Likewise.
That is correct.
What the compiler _cannot_ figure out, on its own, however, is that items and list _need_ to contain the same number of items.
Hence the need for NR.
Quoted 8 lines
t/helper/test-prefix-map.c line 45
Quoted 4 lines
t/helper/test-prefix-map.c new file +4
-0+ items, items + 1, items + 2, items + 3, items + 4 + }; + +find_unique_prefixes (list,NR ,1 ,3 );This could be
find_unique_prefixes(list, ARRAY_SIZE(list), 1, 3), and then there is no need for that NR macro anymore.
Quoted 13 lines
t/helper/test-prefix-map.c line 58
Quoted 13 lines
t/helper/test-prefix-map.c new file +13
-0+ +#define EXPECT_PREFIX_LENGTH_EQUALS (expect, index) \ + EXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \ + list[index]->name) + +EXPECT_PREFIX_LENGTH_EQUALS (1 ,0 ); +EXPECT_PREFIX_LENGTH_EQUALS (0 ,1 ); +EXPECT_PREFIX_LENGTH_EQUALS (0 ,2 ); +EXPECT_PREFIX_LENGTH_EQUALS (3 ,3 ); +EXPECT_PREFIX_LENGTH_EQUALS (3 ,4 ); + +return !!failed_count; +}
Thank you for your review!
Dscho
v4 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 weeks already (it is so nice to not suffer over one second startup until the MSYS2 Perl finally shows me anything, instead it feels instantaneous), I integrated these patch series into Git for Windows' master already, as an opt-in feature guarded by the config variable add.interactive.useBuiltin (and Git for Windows' installer is prepared to detect this version and offer the option in the graphical user interface).
I had planned on submitting this before v2.22.0-rc0, but there was such a backlog of builds from a big pushout that I had to wait ;-)
Changes since v3:
* Rebased to v2.23.0 to reduce friction.
* free_diffstat_info() is now made public as well, and used, to avoid a memory leak.
* Prepared the patches for ew/hashmap (which is strict about the hashmap entries' type in hashmap_entry_init() and friends).
* The private data types have been moved from prefix-map.h to prefix-map.c.
* A lot of int types were converted to more appropriate size_t in prefix-map.c.
* A misleading parameter name list was renamed to the correct array.
* The code comment above find_unique_prefixes() was (hopefully) improved.
* The run_help() function's signature now reflects that most of the parameters are actually unused.
Changes since v2:
* Rebased to master to avoid merge conflicts.
* Renumbered the prefix-map test to avoid conflicts with two patch series that are currently in-flight in pu.
Changes since v1:
* The config machinery was reworked completely, to not use a callback to git_config(), but instead to query the config via the repo_config_get_*() functions. This also prevents a future "Huh???" moment: the internal add --interactive API accepts a parameter of type struct repository *r, but the previous configuration did not use that to query the config (and could in the future be a repository other than the_repository).
* As a consequence, the color sequences are no longer stored in file-local variables, but passed around via a struct.
* Instead of using the magical constant -2 to quit the main loop, it is now defined as LIST_AND_CHOOSE_QUIT (and likewise, LIST_AND_CHOOSE_ERROR is defined as -1 and used where appropriate).
* Improved the add_prefix_item() function by avoiding buffer overruns, not reusing the struct that is used for lookup also for adding the new item, and by strengthening the bug check.
Daniel Ferreira (2):
diff: export diffstat interface
built-in add -i: implement the status command
Johannes Schindelin (6):
Start to implement a built-in version of git add --interactive built-in add -i: refresh the index before running status built-in add -i: color the header in the status command built-in add -i: implement the main loop
built-in add -i: support ? (prompt help)
built-in add -i: implement the help command
Slavica Djukic (3):
Add a function to determine unique prefixes for a list of strings built-in add -i: show unique prefixes of the commands built-in add -i: use color in the main loop
Documentation/config/add.txt | 5 + Makefile | 3 + add-interactive.c | 558 +++++++++++++++++++++++++++++++++++ add-interactive.h | 8 + builtin/add.c | 10 + diff.c | 39 +-- diff.h | 20 ++ prefix-map.c | 123 ++++++++ prefix-map.h | 29 ++ repository.c | 19 ++ repository.h | 7 + t/README | 4 + t/helper/test-prefix-map.c | 58 ++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0018-prefix-map.sh | 10 + t/t3701-add-interactive.sh | 25 ++ 17 files changed, 897 insertions(+), 23 deletions(-) create mode 100644 add-interactive.c create mode 100644 add-interactive.h create mode 100644 prefix-map.c create mode 100644 prefix-map.h create mode 100644 t/helper/test-prefix-map.c create mode 100755 t/t0018-prefix-map.sh
base-commit: 5fa0f5238b0cd46cfe7f6fa76c3f526ea98148d9
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v4
Pull-Request: https://github.com/gitgitgadget/git/pull/170
Range-diff vs v3:
1: 0a5ec9345d = 1: ad8752eca7 Start to implement a built-in version of `git add --interactive`
2: c7a377890d ! 2: 38cc04c1d9 diff: export diffstat interface
@@ -41,6 +41,15 @@
static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
const char *name_a,
const char *name_b)
+@@
+ gather_dirstat(options, &dir, changed, "", 0);
+ }
+
+-static void free_diffstat_info(struct diffstat_t *diffstat)
++void free_diffstat_info(struct diffstat_t *diffstat)
+ {
+ int i;
+ for (i = 0; i < diffstat->nr; i++) {
@@
dirstat_by_line) {
struct diffstat_t diffstat;
@@ -109,6 +118,7 @@
+void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
+ struct diff_queue_struct *q);
++void free_diffstat_info(struct diffstat_t *diffstat);
+
#define DIFF_SETUP_REVERSE 1
#define DIFF_SETUP_USE_SIZE_CACHE 4
3: b93b055ebe ! 3: ee3e40293c built-in add -i: implement the `status` command
@@ -158,9 +158,9 @@
+ file_index = entry->index;
+ else {
+ FLEX_ALLOC_STR(entry, pathname, name);
-+ hashmap_entry_init(entry, hash);
++ hashmap_entry_init(&entry->ent, hash);
+ entry->index = file_index = s->list->nr;
-+ hashmap_add(&s->file_map, entry);
++ hashmap_add(&s->file_map, &entry->ent);
+
+ add_file_item(s->list, name);
+ }
@@ -173,6 +173,7 @@
+ if (stat.files[i]->is_binary)
+ adddel->binary = 1;
+ }
++ free_diffstat_info(&stat);
+}
+
+static int get_modified_files(struct repository *r, struct file_list *list,
4: daff24074a = 4: 3c855d9fa5 built-in add -i: refresh the index before running `status`
5: 15f18f5b3e = 5: 24737a09f7 built-in add -i: color the header in the `status` command
6: 175409aaae = 6: ac67731cf1 built-in add -i: implement the main loop
7: 3000d7d08d ! 7: c5a699b6b2 Add a function to determine unique prefixes for a list of strings
@@ -19,7 +19,7 @@
prefixes are stored in a hash map (for quick lookup times).
To make sure that this function works as expected, we add a test using a
- special-purpose test helper that was added for that purpose.
+ special-purpose test helper.
Note: We expect the list of prefix items to be passed in as a list of
pointers rather than as regular list to avoid having to copy information
@@ -58,6 +58,19 @@
+#include "cache.h"
+#include "prefix-map.h"
+
++struct prefix_map_entry {
++ struct hashmap_entry e;
++ const char *name;
++ size_t prefix_length;
++ /* if item is NULL, the prefix is not unique */
++ struct prefix_item *item;
++};
++
++struct prefix_map {
++ struct hashmap map;
++ size_t min_length, max_length;
++};
++
+static int map_cmp(const void *unused_cmp_data,
+ const void *entry,
+ const void *entry_or_key,
@@ -77,12 +90,12 @@
+ result->name = name;
+ result->prefix_length = prefix_length;
+ result->item = item;
-+ hashmap_entry_init(result, memhash(name, prefix_length));
-+ hashmap_add(map, result);
++ hashmap_entry_init(&result->e, memhash(name, prefix_length));
++ hashmap_add(map, &result->e);
+}
+
+static void init_prefix_map(struct prefix_map *prefix_map,
-+ int min_prefix_length, int max_prefix_length)
++ size_t min_prefix_length, size_t max_prefix_length)
+{
+ hashmap_init(&prefix_map->map, map_cmp, NULL, 0);
+ prefix_map->min_length = min_prefix_length;
@@ -93,7 +106,7 @@
+ struct prefix_item *item)
+{
+ struct prefix_map_entry e = { { NULL } }, *e2;
-+ int j;
++ size_t j;
+
+ e.item = item;
+ e.name = item->name;
@@ -105,8 +118,8 @@
+ break;
+
+ e.prefix_length = j;
-+ hashmap_entry_init(&e, memhash(e.name, j));
-+ e2 = hashmap_get(&prefix_map->map, &e, NULL);
++ hashmap_entry_init(&e.e, memhash(e.name, j));
++ e2 = hashmap_get(&prefix_map->map, &e.e, NULL);
+ if (!e2) {
+ /* prefix is unique at this stage */
+ item->prefix_length = j;
@@ -119,7 +132,8 @@
+
+ if (j != e2->item->prefix_length || memcmp(e.name, e2->name, j))
+ BUG("unexpected prefix length: %d != %d (%s != %s)",
-+ j, (int)e2->item->prefix_length, e.name, e2->name);
++ (int)j, (int)e2->item->prefix_length,
++ e.name, e2->name);
+
+ /* skip common prefix */
+ for (; j < prefix_map->max_length && e.name[j]; j++) {
@@ -153,15 +167,15 @@
+ }
+}
+
-+void find_unique_prefixes(struct prefix_item **list, size_t nr,
-+ int min_length, int max_length)
++void find_unique_prefixes(struct prefix_item **array, size_t nr,
++ size_t min_length, size_t max_length)
+{
-+ int i;
++ size_t i;
+ struct prefix_map prefix_map;
+
+ init_prefix_map(&prefix_map, min_length, max_length);
+ for (i = 0; i < nr; i++)
-+ add_prefix_item(&prefix_map, list[i]);
++ add_prefix_item(&prefix_map, array[i]);
+ hashmap_free(&prefix_map.map, 1);
+}
@@ -180,25 +194,14 @@
+ size_t prefix_length;
+};
+
-+struct prefix_map_entry {
-+ struct hashmap_entry e;
-+ const char *name;
-+ size_t prefix_length;
-+ /* if item is NULL, the prefix is not unique */
-+ struct prefix_item *item;
-+};
-+
-+struct prefix_map {
-+ struct hashmap map;
-+ int min_length, max_length;
-+};
-+
+/*
-+ * Find unique prefixes in a given list of strings.
++ * Given an array of names, find unique prefixes (i.e. the first <n> characters
++ * that uniquely identify the names) and store the lengths of the unique
++ * prefixes in the 'prefix_length' field of the elements of the given array..
+ *
-+ * Typically, the `struct prefix_item` information will be but a field in the
-+ * actual item struct; For this reason, the `list` parameter is specified as a
-+ * list of pointers to the items.
++ * Typically, the `struct prefix_item` information is a field in the actual
++ * item struct; For this reason, the `array` parameter is specified as an array
++ * of pointers to the items.
+ *
+ * The `min_length`/`max_length` parameters define what length the unique
+ * prefixes should have.
@@ -206,8 +209,8 @@
+ * If no unique prefix could be found for a given item, its `prefix_length`
+ * will be set to 0.
+ */
-+void find_unique_prefixes(struct prefix_item **list, size_t nr,
-+ int min_length, int max_length);
++void find_unique_prefixes(struct prefix_item **array, size_t nr,
++ size_t min_length, size_t max_length);
+
+#endif 8: e23ddebfbf = 8: bc7a74f697 built-in add -i: show unique prefixes of the commands
9: d8c012fce8 = 9: 74f73e26b4 built-in add -i: support `?` (prompt help)
10: 8121a3ca1b = 10: 88001009bc built-in add -i: use color in the main loop
11: db70c6475d ! 11: b27fbe289f built-in add -i: implement the `help` command
@@ -18,22 +18,21 @@
return 0;
}
-+static int run_help(struct add_i_state *s, const struct pathspec *ps,
-+ struct file_list *files, struct list_options *opts)
++static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,
++ struct file_list *unused_files,
++ struct list_options *unused_opts)
+{
-+ const char *help_color = s->help_color;
-+
-+ color_fprintf_ln(stdout, help_color, "status - %s",
++ color_fprintf_ln(stdout, s->help_color, "status - %s",
+ _("show paths with changes"));
-+ color_fprintf_ln(stdout, help_color, "update - %s",
++ color_fprintf_ln(stdout, s->help_color, "update - %s",
+ _("add working tree state to the staged set of changes"));
-+ color_fprintf_ln(stdout, help_color, "revert - %s",
++ color_fprintf_ln(stdout, s->help_color, "revert - %s",
+ _("revert staged set of changes back to the HEAD version"));
-+ color_fprintf_ln(stdout, help_color, "patch - %s",
++ color_fprintf_ln(stdout, s->help_color, "patch - %s",
+ _("pick hunks and update selectively"));
-+ color_fprintf_ln(stdout, help_color, "diff - %s",
++ color_fprintf_ln(stdout, s->help_color, "diff - %s",
+ _("view diff between HEAD and index"));
-+ color_fprintf_ln(stdout, help_color, "add untracked - %s",
++ color_fprintf_ln(stdout, s->help_color, "add untracked - %s",
+ _("add contents of untracked files to the staged set of changes"));
+
+ return 0;Signature · 1 line
gitgitgadget
PATCH v4 1/11 Start to implement a built-in version of `git add --interactive`
From: Johannes Schindelin <[email protected]>
This is hardly the first conversion of a Git command that is implemented as a script to a built-in. So far, the most successful strategy for such conversions has been to add a built-in helper and call that for more and more functionality from the script, as more and more parts are converted.
With the interactive add, we choose a different strategy. The sole reason for this is that on Windows (where such a conversion has the most benefits in terms of speed and robustness) we face the very specific problem that a system() call in Perl seems to close stdin in the parent process when the spawned process consumes even one character from stdin. And that just does not work for us here, as it would stop the main loop as soon as any interactive command was performed by the helper. Which is almost all of the commands in git add -i.
It is almost as if Perl told us once again that it does not want us to use it on Windows.
Instead, we follow the opposite route where we start with a bare-bones version of the built-in interactive add, guarded by the new add.interactive.useBuiltin config variable, and then add more and more functionality to it, until it is feature complete.
At this point, the built-in version of git add -i only states that it cannot do anything yet ;-)
--- Documentation/config/add.txt | 5 +++++ Makefile | 1 + add-interactive.c | 7 +++++++ add-interactive.h | 8 ++++++++ builtin/add.c | 10 ++++++++++ t/README | 4 ++++ 6 files changed, 35 insertions(+) create mode 100644 add-interactive.c create mode 100644 add-interactive.h
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
@@ -826,6 +826,7 @@ LIB_H := $(sort $(shell git ls-files '*.h' ':!t/' ':!Documentation/' 2>/dev/null -name '*.h' -print)) LIB_OBJS += abspath.o +LIB_OBJS += add-interactive.o LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.o
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 +10 -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,14 @@ int run_add_interactive(const char *revision, const char *patch_mode, {int status, i;struct argv_array argv =ARGV_ARRAY_INIT ; +int use_builtin_add_i = +git_env_bool ("GIT_TEST_ADD_I_USE_BUILTIN" ,-1 ); +if (use_builtin_add_i <0 ) +git_config_get_bool ("add.interactive.usebuiltin" , + &use_builtin_add_i); + +if (use_builtin_add_i ==1 && !patch_mode) +return !!run_add_i (the_repository, pathspec);argv_array_push (&argv,"add--interactive" );if (patch_mode)
@@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb) ignore_add_errors =git_config_bool (var, value);return 0 ; } +return git_default_config (var, value, cb); }
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 +builtin version of git add -i. See 'add.interactive.useBuiltin' in +git-config(1). + GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading of the index for the whole test suite by bypassing the default number of cache entries and thread minimums. Setting this to 1 will make the
Signature · 1 line
gitgitgadget
PATCH v4 3/11 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 as needed later.
At this point, we re-implement only part of the list_and_choose() function of the Perl script git-add--interactive.perl and call it list(). It does not yet color anything, or do columns, or allow user input.
Over the course of the next commits, we will introduce a list_and_choose() function that uses list() to display the list of options and let the user choose one or more of the displayed items. This will be used to implement the main loop of the built-in git add -i, at which point the new status command can actually be used.
Note that we pass the list of items as a struct item ** as opposed to a struct item *, to allow for the actual items to contain much more information than merely the name.
--- add-interactive.c | 266 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 265 insertions(+), 1 deletion(-)
add-interactive.c +265 -1
@@ -1,7 +1,271 @@#include "cache.h" #include "add-interactive.h" +#include "diffcore.h" +#include "revision.h" +#include "refs.h" + +struct item { +const char *name; +}; + +struct list_options { +const char *header; +void (*print_item)(int i,struct item *item,void *print_item_data); +void *print_item_data; +}; + +static void list (struct item **list,size_t nr,struct list_options *opts) +{ +int i; + +if (!nr) +return ; + +if (opts->header) +printf ("%s\n" , opts->header); + +for (i =0 ; i < nr; i++) { + opts->print_item (i, list[i], opts->print_item_data); +putchar ('\n' ); + } +} + +struct adddel { +uintmax_t add, del; +unsigned seen:1 , binary:1 ; +}; + +struct file_list { +struct file_item { +struct item item; +struct adddel index, worktree; + } **file; +size_t nr, alloc; +}; + +static void add_file_item (struct file_list *list,const char *name) +{ +struct file_item *item; + +FLEXPTR_ALLOC_STR (item, item.name, name); + +ALLOC_GROW (list->file, list->nr +1 , list->alloc); + list->file[list->nr++] = item; +} + +static void reset_file_list (struct file_list *list) +{ +size_t i; + +for (i =0 ; i < list->nr; i++) +free (list->file[i]); + list->nr =0 ; +} + +static void release_file_list (struct file_list *list) +{ +reset_file_list (list); +FREE_AND_NULL (list->file); + list->alloc =0 ; +} + +static int file_item_cmp (const void *a,const void *b) +{ +const struct file_item *const *f1 = a; +const struct file_item *const *f2 = b; + +return strcmp ((*f1)->item.name, (*f2)->item.name); +} + +struct pathname_entry { +struct hashmap_entry ent; +size_t index; +char pathname[FLEX_ARRAY ]; +}; + +static int pathname_entry_cmp (const void *unused_cmp_data, +const void *entry,const void *entry_or_key, +const void *pathname) +{ +const struct pathname_entry *e1 = entry, *e2 = entry_or_key; + +return strcmp (e1->pathname, + pathname ? (const char *)pathname : e2->pathname); +} + +struct collection_status { +enum {FROM_WORKTREE =0 ,FROM_INDEX =1 } phase; + +const char *reference; + +struct file_list *list; +struct hashmap file_map; +}; + +static void collect_changes_cb (struct diff_queue_struct *q, +struct diff_options *options, +void *data) +{ +struct collection_status *s = data; +struct diffstat_t stat = {0 }; +int i; + +if (!q->nr) +return ; + +compute_diffstat (options, &stat, q); + +for (i =0 ; i < stat.nr; i++) { +const char *name = stat.files[i]->name; +int hash =strhash (name); +struct pathname_entry *entry; +size_t file_index; +struct file_item *file; +struct adddel *adddel; + + entry =hashmap_get_from_hash (&s->file_map, hash, name); +if (entry) + file_index = entry->index; +else { +FLEX_ALLOC_STR (entry, pathname, name); +hashmap_entry_init (&entry->ent, hash); + entry->index = file_index = s->list->nr; +hashmap_add (&s->file_map, &entry->ent); + +add_file_item (s->list, name); + } + file = s->list->file[file_index]; + + adddel = s->phase ==FROM_INDEX ? &file->index : &file->worktree; + adddel->seen =1 ; + adddel->add = stat.files[i]->added; + adddel->del = stat.files[i]->deleted; +if (stat.files[i]->is_binary) + adddel->binary =1 ; + } +free_diffstat_info (&stat); +} + +static int get_modified_files (struct repository *r,struct file_list *list, +const struct pathspec *ps) +{ +struct object_id head_oid; +int is_initial = !resolve_ref_unsafe ("HEAD" ,RESOLVE_REF_READING , + &head_oid,NULL ); +struct collection_status s = {FROM_WORKTREE }; + +if (repo_read_index_preload (r, ps,0 ) <0 ) +return error (_ ("could not read index" )); + + s.list = list; +hashmap_init (&s.file_map, pathname_entry_cmp,NULL ,0 ); + +for (s.phase =FROM_WORKTREE ; s.phase <=FROM_INDEX ; s.phase++) { +struct rev_info rev; +struct setup_revision_opt opt = {0 }; + + opt.def = is_initial ? +empty_tree_oid_hex () :oid_to_hex (&head_oid); + +init_revisions (&rev,NULL ); +setup_revisions (0 ,NULL , &rev, &opt); + + rev.diffopt.output_format =DIFF_FORMAT_CALLBACK ; + rev.diffopt.format_callback = collect_changes_cb; + rev.diffopt.format_callback_data = &s; + +if (ps) +copy_pathspec (&rev.prune_data, ps); + +if (s.phase ==FROM_INDEX ) +run_diff_index (&rev,1 ); +else { + rev.diffopt.flags.ignore_dirty_submodules =1 ; +run_diff_files (&rev,0 ); + } + } +hashmap_free (&s.file_map,1 ); + +/* While the diffs are ordered already, we ran *two* diffs... */ +QSORT (list->file, list->nr, file_item_cmp); + +return 0 ; +} + +static void populate_wi_changes (struct strbuf *buf, +struct adddel *ad,const char *no_changes) +{ +if (ad->binary) +strbuf_addstr (buf,_ ("binary" )); +else if (ad->seen) +strbuf_addf (buf,"+%" PRIuMAX"/-%" PRIuMAX, + (uintmax_t )ad->add, (uintmax_t )ad->del); +else +strbuf_addstr (buf, no_changes); +} + +struct print_file_item_data { +const char *modified_fmt; +struct strbuf buf, index, worktree; +}; + +static void print_file_item (int i,struct item *item, +void *print_file_item_data) +{ +struct file_item *c = (struct file_item *)item; +struct print_file_item_data *d = print_file_item_data; + +strbuf_reset (&d->index); +strbuf_reset (&d->worktree); +strbuf_reset (&d->buf); + +populate_wi_changes (&d->worktree, &c->worktree,_ ("nothing" )); +populate_wi_changes (&d->index, &c->index,_ ("unchanged" )); +strbuf_addf (&d->buf, d->modified_fmt, + d->index.buf, d->worktree.buf, item->name); + +printf (" %2d: %s" , i +1 , d->buf.buf); +} + +static int run_status (struct repository *r,const struct pathspec *ps, +struct file_list *files,struct list_options *opts) +{ +reset_file_list (files); + +if (get_modified_files (r, files, ps) <0 ) +return -1 ; + +if (files->nr) +list ((struct item **)files->file, files->nr, opts); +putchar ('\n' ); + +return 0 ; +}int run_add_i (struct repository *r,const struct pathspec *ps) { -die (_ ("No commands are available in the built-in `git add -i` yet!" )); +struct print_file_item_data print_file_item_data = { +"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT + }; +struct list_options opts = { +NULL , print_file_item, &print_file_item_data + }; +struct strbuf header =STRBUF_INIT ; +struct file_list files = {NULL }; +int res =0 ; + +strbuf_addstr (&header," " ); +strbuf_addf (&header, print_file_item_data.modified_fmt, +_ ("staged" ),_ ("unstaged" ),_ ("path" )); + opts.header = header.buf; + + res =run_status (r, ps, &files, &opts); + +release_file_list (&files); +strbuf_release (&print_file_item_data.buf); +strbuf_release (&print_file_item_data.index); +strbuf_release (&print_file_item_data.worktree); +strbuf_release (&header); + +return res; }
Signature · 1 line
gitgitgadget
PATCH v4 8/11 built-in add -i: show unique prefixes of the commands
In reply to Johannes Schindelin via GitGitGadgetFrom: Slavica Djukic <[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.
We use the prefix map implementation that we just added in the previous commit for that purpose.
--- add-interactive.c | 69 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 13 deletions(-)
add-interactive.c +56 -13
@@ -5,6 +5,7 @@#include "diffcore.h" #include "revision.h" #include "refs.h" +#include "prefix-map.h" struct add_i_state {struct repository *r;
@@ -46,18 +47,32 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)return 0 ; } -struct item { -const char *name; -}; +static ssize_t find_unique (const char *string, +struct prefix_item **list,size_t nr) +{ +ssize_t found =-1 , i; + +for (i =0 ; i < nr; i++) { +struct prefix_item *item = list[i]; +if (!starts_with (item->name, string)) +continue ; +if (found >=0 ) +return -1 ; + found = i; + } + +return found; +}struct list_options {int columns;const char *header; -void (*print_item)(int i,struct item *item,void *print_item_data); +void (*print_item)(int i,struct prefix_item *item, +void *print_item_data);void *print_item_data; }; -static void list (struct item **list,size_t nr, +static void list (struct prefix_item **list,size_t nr,struct add_i_state *s,struct list_options *opts) {int i, last_lf =0 ;
@@ -100,13 +115,15 @@ struct list_and_choose_options {* If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF, * `LIST_AND_CHOOSE_QUIT` is returned. */ -static ssize_t list_and_choose (struct item **items,size_t nr, +static ssize_t list_and_choose (struct prefix_item **items,size_t nr,struct add_i_state *s,struct list_and_choose_options *opts) {struct strbuf input =STRBUF_INIT ;ssize_t res =LIST_AND_CHOOSE_ERROR ; +find_unique_prefixes (items, nr,1 ,4 ); +for (;;) {char *p, *endp;
@@ -146,6 +163,9 @@ static ssize_t list_and_choose(struct item **items, size_t nr, } p[sep] ='\0' ; +if (index <0 ) + index =find_unique (p, items, nr); +if (index <0 || index >= nr)printf (_ ("Huh (%s)?\n" ), p);else {
@@ -171,7 +191,7 @@ struct adddel {struct file_list {struct file_item { -struct item item; +struct prefix_item item;struct adddel index, worktree; } **file;size_t nr, alloc;
@@ -338,12 +358,29 @@ static void populate_wi_changes(struct strbuf *buf,strbuf_addstr (buf, no_changes); } +/* filters out prefixes which have special meaning to list_and_choose() */ +static int is_valid_prefix (const char *prefix,size_t prefix_len) +{ +return prefix_len && prefix && +/* +* We expect `prefix` to be NUL terminated, therefore this +* `strcspn()` call is okay, even if it might do much more +* work than strictly necessary. +*/ +strcspn (prefix," \t\r\n," ) >= prefix_len &&/* separators */ + *prefix !='-' &&/* deselection */ + !isdigit (*prefix) &&/* selection */ + (prefix_len !=1 || + (*prefix !='*' &&/* "all" wildcard */ + *prefix !='?' ));/* prompt help */ +} +struct print_file_item_data {const char *modified_fmt;struct strbuf buf, index, worktree; }; -static void print_file_item (int i,struct item *item, +static void print_file_item (int i,struct prefix_item *item,void *print_file_item_data) {struct file_item *c = (struct file_item *)item;
@@ -370,20 +407,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return -1 ;if (files->nr) -list ((struct item **)files->file, files->nr, s, opts); +list ((struct prefix_item **)files->file, files->nr, s, opts);putchar ('\n' );return 0 ; } -static void print_command_item (int i,struct item *item, +static void print_command_item (int i,struct prefix_item *item,void *print_command_item_data) { -printf (" %2d: %s" , i +1 , item->name); +if (!item->prefix_length || + !is_valid_prefix (item->name, item->prefix_length)) +printf (" %2d: %s" , i +1 , item->name); +else +printf (" %3d: [%.*s]%s" , i +1 , + (int )item->prefix_length, item->name, + item->name + item->prefix_length); }struct command_item { -struct item item; +struct prefix_item item;int (*command)(struct add_i_state *s,const struct pathspec *ps,struct file_list *files,struct list_options *opts); };
@@ -425,7 +468,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps) res =-1 ;for (;;) { - i =list_and_choose ((struct item **)commands, + i =list_and_choose ((struct prefix_item **)commands,ARRAY_SIZE (commands), &s, &main_loop_opts);if (i ==LIST_AND_CHOOSE_QUIT ) {printf (_ ("Bye.\n" ));
Signature · 1 line
gitgitgadget
PATCH v4 6/11 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.
Apart from the "and choose" part, there are more differences between the way the status command calls the list_and_choose() function in the Perl version of git add -i compared to the other callers of said function. The most important ones:
- The list is not only shown, but the user is also asked to make a choice, possibly selecting multiple entries.
- The list of items is prefixed with a marker indicating what items have been selected, if multi-selection is allowed.
- Initially, for each item a unique prefix (if there exists any within the given parameters) is determined, and shown in the list, and accepted as a shortcut for the selection.
These features will be implemented later, except the part where the user can choose a command. At this stage, though, the built-in git add -i still only supports the status command, with the remaining commands to follow over the course of the next commits.
In addition, we also modify list() to support displaying the commands in columns, even if there is currently only one.
The Perl script git-add--interactive.perl mixed the purposes of the "list" and the "and choose" part into the same function. In the C version, we will keep them separate instead, calling the list() function from the list_and_choose() function.
Note that we only have a prompt ending in a single ">" at this stage; later commits will add commands that display a double ">>" to indicate that the user is in a different loop than the main one.
--- add-interactive.c | 129 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 2 deletions(-)
add-interactive.c +127 -2
@@ -51,6 +51,7 @@ struct item { };struct list_options { +int columns;const char *header;void (*print_item)(int i,struct item *item,void *print_item_data);void *print_item_data;
@@ -59,7 +60,7 @@ struct list_options {static void list (struct item **list,size_t nr,struct add_i_state *s,struct list_options *opts) { -int i; +int i, last_lf =0 ;if (!nr)return ;
@@ -70,8 +71,97 @@ static void list(struct item **list, size_t nr,for (i =0 ; i < nr; i++) { opts->print_item (i, list[i], opts->print_item_data); + +if ((opts->columns) && ((i +1 ) % (opts->columns))) { +putchar ('\t' ); + last_lf =0 ; + } +else { +putchar ('\n' ); + last_lf =1 ; + } + } + +if (!last_lf)putchar ('\n' ); +} +struct list_and_choose_options { +struct list_options list_opts; + +const char *prompt; +}; + +#define LIST_AND_CHOOSE_ERROR (-1) +#define LIST_AND_CHOOSE_QUIT (-2) + +/* +* Returns the selected index. +* +* If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF, +* `LIST_AND_CHOOSE_QUIT` is returned. +*/ +static ssize_t list_and_choose (struct item **items,size_t nr, +struct add_i_state *s, +struct list_and_choose_options *opts) +{ +struct strbuf input =STRBUF_INIT ; +ssize_t res =LIST_AND_CHOOSE_ERROR ; + +for (;;) { +char *p, *endp; + +strbuf_reset (&input); + +list (items, nr, s, &opts->list_opts); + +printf ("%s%s" , opts->prompt,"> " ); +fflush (stdout); + +if (strbuf_getline (&input, stdin) ==EOF ) { +putchar ('\n' ); + res =LIST_AND_CHOOSE_QUIT ; +break ; + } +strbuf_trim (&input); + +if (!input.len) +break ; + + p = input.buf; +for (;;) { +size_t sep =strcspn (p," \t\r\n," ); +ssize_t index =-1 ; + +if (!sep) { +if (!*p) +break ; + p++; +continue ; + } + +if (isdigit (*p)) { + index =strtoul (p, &endp,10 ) -1 ; +if (endp != p + sep) + index =-1 ; + } + + p[sep] ='\0' ; +if (index <0 || index >= nr) +printf (_ ("Huh (%s)?\n" ), p); +else { + res = index; +break ; + } + + p += sep +1 ; + } + +if (res !=LIST_AND_CHOOSE_ERROR ) +break ; } + +strbuf_release (&input); +return res; }struct adddel {
@@ -286,17 +376,40 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return 0 ; } +static void print_command_item (int i,struct item *item, +void *print_command_item_data) +{ +printf (" %2d: %s" , i +1 , item->name); +} + +struct command_item { +struct item item; +int (*command)(struct add_i_state *s,const struct pathspec *ps, +struct file_list *files,struct list_options *opts); +}; +int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL }; +struct list_and_choose_options main_loop_opts = { + {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, +N_ ("What now" ) + }; +struct command_item + status = { {"status" }, run_status }; +struct command_item *commands[] = { + &status + }; +struct print_file_item_data print_file_item_data = {"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT };struct list_options opts = { -NULL , print_file_item, &print_file_item_data +0 ,NULL , print_file_item, &print_file_item_data };struct strbuf header =STRBUF_INIT ;struct file_list files = {NULL }; +ssize_t i;int res =0 ;if (init_add_i_state (r, &s))
@@ -311,6 +424,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)if (run_status (&s, ps, &files, &opts) <0 ) res =-1 ; +for (;;) { + i =list_and_choose ((struct item **)commands, +ARRAY_SIZE (commands), &s, &main_loop_opts); +if (i ==LIST_AND_CHOOSE_QUIT ) { +printf (_ ("Bye.\n" )); + res =0 ; +break ; + } +if (i !=LIST_AND_CHOOSE_ERROR ) + res = commands[i]->command (&s, ps, &files, &opts); + } +release_file_list (&files);strbuf_release (&print_file_item_data.buf);strbuf_release (&print_file_item_data.index);
Signature · 1 line
gitgitgadget
PATCH v4 4/11 built-in add -i: refresh the index before running `status`
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This is what the Perl version does, and therefore it is what the built-in version should do, too.
--- add-interactive.c | 4 +++- repository.c | 19 +++++++++++++++++++ repository.h | 7 +++++++ 3 files changed, 29 insertions(+), 1 deletion(-)
add-interactive.c +3 -1
@@ -259,7 +259,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps) _("staged"), _("unstaged"), _("path")); opts.header = header.buf; - res =run_status (r, ps, &files, &opts); +repo_refresh_and_write_index (r,REFRESH_QUIET ,1 ); +if (run_status (r, ps, &files, &opts) <0 ) + res =-1 ;release_file_list (&files);strbuf_release (&print_file_item_data.buf);
repository.c +19 -0
@@ -275,3 +275,22 @@ int repo_hold_locked_index(struct repository *repo,BUG ("the repo hasn't been setup" );return hold_lock_file_for_update (lf, repo->index_file, flags); } + +int repo_refresh_and_write_index (struct repository *r, +unsigned int flags,int gentle) +{ +struct lock_file lock_file =LOCK_INIT ; +int fd; + +if (repo_read_index_preload (r,NULL ,0 ) <0 ) +return error (_ ("could not read index" )); + fd =repo_hold_locked_index (r, &lock_file,0 ); +if (!gentle && fd <0 ) +return error (_ ("could not lock index for writing" )); +refresh_index (r->index, flags,NULL ,NULL ,NULL ); +if (0 <= fd) +repo_update_index_if_able (r, &lock_file); +rollback_lock_file (&lock_file); + +return 0 ; +}
repository.h +7 -0
@@ -157,5 +157,12 @@ int repo_read_index_unmerged(struct repository *);*/ void repo_update_index_if_able (struct repository *,struct lock_file *); +/* +* Refresh the index and write it out. If the index file could not be +* locked, error out, except in gentle mode. The flags will be passed +* through to refresh_index(). +*/ +int repo_refresh_and_write_index (struct repository *r, +unsigned int flags,int gentle);#endif /* REPOSITORY_H */
Signature · 1 line
gitgitgadget
PATCH v4 9/11 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 int init_add_i_state(struct repository *r, struct add_i_state *s) s->use_color =want_color (s->use_color);init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD ); +init_color (r, s,"help" , s->help_color,GIT_COLOR_BOLD_RED );return 0 ; }
@@ -104,6 +106,7 @@ struct list_and_choose_options {struct list_options list_opts;const char *prompt; +void (*print_help)(struct add_i_state *s); };#define LIST_AND_CHOOSE_ERROR (-1)
@@ -144,6 +147,11 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,if (!input.len)break ; +if (!strcmp (input.buf,"?" )) { + opts->print_help (s); +continue ; + } + p = input.buf;for (;;) {size_t sep =strcspn (p," \t\r\n," );
@@ -431,12 +439,24 @@ struct command_item { struct file_list *files, struct list_options *opts); }; +static void command_prompt_help (struct add_i_state *s) +{ +const char *help_color = s->help_color; +color_fprintf_ln (stdout, help_color,"%s" ,_ ("Prompt help:" )); +color_fprintf_ln (stdout, help_color,"1 - %s" , +_ ("select a numbered item" )); +color_fprintf_ln (stdout, help_color,"foo - %s" , +_ ("select item based on unique prefix" )); +color_fprintf_ln (stdout, help_color," - %s" , +_ ("(empty) select nothing" )); +} +int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL };struct list_and_choose_options main_loop_opts = { {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, -N_ ("What now" ) +N_ ("What now" ), command_prompt_help };struct command_item status = { {"status" }, run_status };
Signature · 1 line
gitgitgadget
PATCH v4 11/11 built-in add -i: implement the `help` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[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 | 26 ++++++++++++++++++++++++-- t/t3701-add-interactive.sh | 25 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-)
add-interactive.c +24 -2
@@ -429,6 +429,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return 0 ; } +static int run_help (struct add_i_state *s,const struct pathspec *unused_ps, +struct file_list *unused_files, +struct list_options *unused_opts) +{ +color_fprintf_ln (stdout, s->help_color,"status - %s" , +_ ("show paths with changes" )); +color_fprintf_ln (stdout, s->help_color,"update - %s" , +_ ("add working tree state to the staged set of changes" )); +color_fprintf_ln (stdout, s->help_color,"revert - %s" , +_ ("revert staged set of changes back to the HEAD version" )); +color_fprintf_ln (stdout, s->help_color,"patch - %s" , +_ ("pick hunks and update selectively" )); +color_fprintf_ln (stdout, s->help_color,"diff - %s" , +_ ("view diff between HEAD and index" )); +color_fprintf_ln (stdout, s->help_color,"add untracked - %s" , +_ ("add contents of untracked files to the staged set of changes" )); + +return 0 ; +} +struct print_command_item_data {const char *color, *reset; };
@@ -474,9 +494,11 @@ int run_add_i(struct repository *r, const struct pathspec *ps)N_ ("What now" ), command_prompt_help };struct command_item - status = { {"status" }, run_status }; + status = { {"status" }, run_status }, + help = { {"help" }, run_help };struct command_item *commands[] = { - &status + &status, + &help }; struct print_file_item_data print_file_item_data = {
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 v4 10/11 built-in add -i: use color in the main loop
In reply to Johannes Schindelin via GitGitGadgetFrom: Slavica Djukic <[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 | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-)
add-interactive.c +32 -5
@@ -12,6 +12,9 @@ struct add_i_state {int use_color;char header_color[COLOR_MAXLEN ];char help_color[COLOR_MAXLEN ]; +char prompt_color[COLOR_MAXLEN ]; +char error_color[COLOR_MAXLEN ]; +char reset_color[COLOR_MAXLEN ]; }; static void init_color(struct repository *r, struct add_i_state *s,
@@ -45,6 +48,9 @@ static int init_add_i_state(struct repository *r, struct add_i_state *s)init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD );init_color (r, s,"help" , s->help_color,GIT_COLOR_BOLD_RED ); +init_color (r, s,"prompt" , s->prompt_color,GIT_COLOR_BOLD_BLUE ); +init_color (r, s,"error" , s->error_color,GIT_COLOR_BOLD_RED ); +init_color (r, s,"reset" , s->reset_color,GIT_COLOR_RESET );return 0 ; }
@@ -134,7 +140,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr,list (items, nr, s, &opts->list_opts); -printf ("%s%s" , opts->prompt,"> " ); +color_fprintf (stdout, s->prompt_color,"%s" , opts->prompt); +fputs ("> " , stdout);fflush (stdout);if (strbuf_getline (&input, stdin) ==EOF ) {
@@ -175,7 +182,8 @@ static ssize_t list_and_choose(struct prefix_item **items, size_t nr, index =find_unique (p, items, nr);if (index <0 || index >= nr) -printf (_ ("Huh (%s)?\n" ), p); +color_fprintf_ln (stdout, s->error_color, +_ ("Huh (%s)?" ), p);else { res = index;break ;
@@ -421,15 +429,21 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return 0 ; } +struct print_command_item_data { +const char *color, *reset; +}; +static void print_command_item (int i,struct prefix_item *item,void *print_command_item_data) { +struct print_command_item_data *d = print_command_item_data; +if (!item->prefix_length || !is_valid_prefix (item->name, item->prefix_length))printf (" %2d: %s" , i +1 , item->name);else -printf (" %3d: [%.*s]%s" , i +1 , - (int )item->prefix_length, item->name, +printf (" %2d: %s%.*s%s%s" , i +1 , + d->color, (int )item->prefix_length, item->name, d->reset, item->name + item->prefix_length); }
@@ -454,8 +468,9 @@ static void command_prompt_help(struct add_i_state *s)int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL }; +struct print_command_item_data data;struct list_and_choose_options main_loop_opts = { - {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, + {4 ,N_ ("*** Commands ***" ), print_command_item, &data },N_ ("What now" ), command_prompt_help };struct command_item
@@ -478,6 +493,18 @@ int run_add_i(struct repository *r, const struct pathspec *ps)if (init_add_i_state (r, &s))return error ("could not parse `add -i` config" ); +/* +* When color was asked for, use the prompt color for +* highlighting, otherwise use square brackets. +*/ +if (s.use_color) { + data.color = s.prompt_color; + data.reset = s.reset_color; + }else { + data.color ="[" ; + data.reset ="]" ; + } +strbuf_addstr (&header," " );strbuf_addf (&header, print_file_item_data.modified_fmt,_ ("staged" ),_ ("unstaged" ),_ ("path" ));
Signature · 1 line
gitgitgadget
PATCH v4 2/11 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
@@ -2492,22 +2492,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b) } } -struct diffstat_t { -int nr; -int alloc; -struct diffstat_file { -char *from_name; -char *name; -char *print_name; -const char *comments; -unsigned is_unmerged:1 ; -unsigned is_binary:1 ; -unsigned is_renamed:1 ; -unsigned is_interesting:1 ; -uintmax_t added, deleted; - } **files; -}; -static struct diffstat_file *diffstat_add (struct diffstat_t *diffstat,const char *name_a,const char *name_b)
@@ -3154,7 +3138,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *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++) {
@@ -6278,12 +6262,7 @@ void diff_flush(struct diff_options *options) dirstat_by_line) {struct diffstat_t diffstat; -memset (&diffstat,0 ,sizeof (struct diffstat_t )); -for (i =0 ; i < q->nr; i++) { -struct diff_filepair *p = q->queue[i]; -if (check_pair_status (p)) -diff_flush_stat (p, options, &diffstat); - } +compute_diffstat (options, &diffstat, q);if (output_format &DIFF_FORMAT_NUMSTAT )show_numstat (&diffstat, options);if (output_format &DIFF_FORMAT_DIFFSTAT )
@@ -6616,6 +6595,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)return ignored; } +void compute_diffstat (struct diff_options *options, +struct diffstat_t *diffstat, +struct diff_queue_struct *q) +{ +int i; + +memset (diffstat,0 ,sizeof (struct diffstat_t )); +for (i =0 ; i < q->nr; i++) { +struct diff_filepair *p = q->queue[i]; +if (check_pair_status (p)) +diff_flush_stat (p, options, diffstat); + } +} + void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const struct object_id *oid,
diff.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 v4 7/11 Add a function to determine unique prefixes for a list of strings
In reply to Johannes Schindelin via GitGitGadgetFrom: Slavica Djukic <[email protected]>
In the git add -i command, we show unique prefixes of the commands and files, to give an indication what prefix would select them.
Naturally, the C implementation looks a lot different than the Perl
implementation: in Perl, a trie is much easier implemented, while we already have a pretty neat hashmap implementation in C that we use for the purpose of storing (not necessarily unique) prefixes.
The idea: for each item that we add, we generate prefixes starting with the first letter, then the first two letters, then three, etc, until we find a prefix that is unique (or until the prefix length would be longer than we want). If we encounter a previously-unique prefix on the way, we adjust that item's prefix to make it unique again (or we mark it as having no unique prefix if we failed to find one). These partial prefixes are stored in a hash map (for quick lookup times).
To make sure that this function works as expected, we add a test using a special-purpose test helper.
Note: We expect the list of prefix items to be passed in as a list of pointers rather than as regular list to avoid having to copy information (the actual items will most likely contain more information than just the name and the length of the unique prefix, but passing in struct prefix_item * would not allow for that).
--- Makefile | 2 + prefix-map.c | 123 +++++++++++++++++++++++++++++++++++++ prefix-map.h | 29 +++++++++ t/helper/test-prefix-map.c | 58 +++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0018-prefix-map.sh | 10 +++ 7 files changed, 224 insertions(+) create mode 100644 prefix-map.c create mode 100644 prefix-map.h create mode 100644 t/helper/test-prefix-map.c create mode 100755 t/t0018-prefix-map.sh
Makefile +2 -0
@@ -727,6 +727,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o TEST_BUILTINS_OBJS += test-parse-options.o TEST_BUILTINS_OBJS += test-path-utils.o TEST_BUILTINS_OBJS += test-pkt-line.o +TEST_BUILTINS_OBJS += test-prefix-map.o TEST_BUILTINS_OBJS += test-prio-queue.o TEST_BUILTINS_OBJS += test-reach.o TEST_BUILTINS_OBJS += test-read-cache.o
@@ -945,6 +946,7 @@ LIB_OBJS += patch-ids.o LIB_OBJS += path.o LIB_OBJS += pathspec.o LIB_OBJS += pkt-line.o +LIB_OBJS += prefix-map.o LIB_OBJS += preload-index.o LIB_OBJS += pretty.o LIB_OBJS += prio-queue.o
prefix-map.c new file +123 -0
123 lines not on this page · raw message
prefix-map.h new file +29 -0
@@ -0,0 +1,29 @@ +#ifndef PREFIX_MAP_H +#define PREFIX_MAP_H + +#include "hashmap.h" + +struct prefix_item { +const char *name; +size_t prefix_length; +}; + +/* +* Given an array of names, find unique prefixes (i.e. the first <n> characters +* that uniquely identify the names) and store the lengths of the unique +* prefixes in the 'prefix_length' field of the elements of the given array.. +* +* Typically, the `struct prefix_item` information is a field in the actual +* item struct; For this reason, the `array` parameter is specified as an array +* of pointers to the items. +* +* The `min_length`/`max_length` parameters define what length the unique +* prefixes should have. +* +* If no unique prefix could be found for a given item, its `prefix_length` +* will be set to 0. +*/ +void find_unique_prefixes (struct prefix_item **array,size_t nr, +size_t min_length,size_t max_length); + +#endif
t/helper/test-prefix-map.c new file +58 -0
58 lines not on this page · raw message
t/helper/test-tool.c +1 -0
@@ -41,6 +41,7 @@ static struct test_cmd cmds[] = { { "parse-options", cmd__parse_options }, { "path-utils", cmd__path_utils }, { "pkt-line", cmd__pkt_line }, + { "prefix-map", cmd__prefix_map }, { "prio-queue", cmd__prio_queue }, { "reach", cmd__reach }, { "read-cache", cmd__read_cache },
t/helper/test-tool.h +1 -0
@@ -31,6 +31,7 @@ int cmd__online_cpus(int argc, const char **argv);int cmd__parse_options (int argc,const char **argv);int cmd__path_utils (int argc,const char **argv);int cmd__pkt_line (int argc,const char **argv); +int cmd__prefix_map (int argc,const char **argv);int cmd__prio_queue (int argc,const char **argv);int cmd__reach (int argc,const char **argv);int cmd__read_cache (int argc,const char **argv);
t/t0018-prefix-map.sh new file +10 -0
@@ -0,0 +1,10 @@ +#!/bin/sh + +test_description='basic tests for prefix map' +. ./test-lib.sh + +test_expect_success 'prefix map' ' + test-tool prefix-map +' + +test_done
Signature · 1 line
gitgitgadget
PATCH v4 5/11 built-in add -i: color the header in the `status` command
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[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 | 60 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-)
add-interactive.c +54 -6
@@ -1,9 +1,51 @@#include "cache.h" #include "add-interactive.h" +#include "color.h" +#include "config.h" #include "diffcore.h" #include "revision.h" #include "refs.h" +struct add_i_state { +struct repository *r; +int use_color; +char header_color[COLOR_MAXLEN ]; +}; + +static void init_color (struct repository *r,struct add_i_state *s, +const char *slot_name,char *dst, +const char *default_color) +{ +char *key =xstrfmt ("color.interactive.%s" , slot_name); +const char *value; + +if (!s->use_color) + dst[0 ] ='\0' ; +else if (repo_config_get_value (r, key, &value) || +color_parse (value, dst)) +strlcpy (dst, default_color,COLOR_MAXLEN ); + +free (key); +} + +static int init_add_i_state (struct repository *r,struct add_i_state *s) +{ +const char *value; + + s->r = r; + +if (repo_config_get_value (r,"color.interactive" , &value)) + s->use_color =-1 ; +else + s->use_color = +git_config_colorbool ("color.interactive" , value); + s->use_color =want_color (s->use_color); + +init_color (r, s,"header" , s->header_color,GIT_COLOR_BOLD ); + +return 0 ; +} +struct item {const char *name; };
@@ -14,7 +56,8 @@ struct list_options {void *print_item_data; }; -static void list (struct item **list,size_t nr,struct list_options *opts) +static void list (struct item **list,size_t nr, +struct add_i_state *s,struct list_options *opts) {int i;
@@ -22,7 +65,8 @@ static void list(struct item **list, size_t nr, struct list_options *opts)return ;if (opts->header) -printf ("%s\n" , opts->header); +color_fprintf_ln (stdout, s->header_color, +"%s" , opts->header);for (i =0 ; i < nr; i++) { opts->print_item (i, list[i], opts->print_item_data);
@@ -227,16 +271,16 @@ static void print_file_item(int i, struct item *item,printf (" %2d: %s" , i +1 , d->buf.buf); } -static int run_status (struct repository *r,const struct pathspec *ps, +static int run_status (struct add_i_state *s,const struct pathspec *ps,struct file_list *files,struct list_options *opts) {reset_file_list (files); -if (get_modified_files (r, files, ps) <0 ) +if (get_modified_files (s->r, files, ps) <0 )return -1 ;if (files->nr) -list ((struct item **)files->file, files->nr, opts); +list ((struct item **)files->file, files->nr, s, opts);putchar ('\n' );return 0 ;
@@ -244,6 +288,7 @@ static int run_status(struct repository *r, const struct pathspec *ps,int run_add_i (struct repository *r,const struct pathspec *ps) { +struct add_i_state s = {NULL };struct print_file_item_data print_file_item_data = {"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT };
@@ -254,13 +299,16 @@ int run_add_i(struct repository *r, const struct pathspec *ps)struct file_list files = {NULL };int res =0 ; +if (init_add_i_state (r, &s)) +return error ("could not parse `add -i` config" ); +strbuf_addstr (&header," " );strbuf_addf (&header, print_file_item_data.modified_fmt,_ ("staged" ),_ ("unstaged" ),_ ("path" )); opts.header = header.buf;repo_refresh_and_write_index (r,REFRESH_QUIET ,1 ); -if (run_status (r, ps, &files, &opts) <0 ) +if (run_status (&s, ps, &files, &opts) <0 ) res =-1 ;release_file_list (&files);
Signature · 1 line
gitgitgadget
Re: [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`
In reply to Johannes SchindelinQuoted 1 line · Johannes Schindelin
Besides, I really hope that this would be only temporary,...
Oh, no question about it. This should be temporary knob.
I still do worry about giving a bad example for others to copy. People tend to copy & paste without thinking. Either we come up with and use a two-level name, or we add a comment to explain to developers (not users---as this is merely a temporary thing) why they should never follow suit using three-level names for things like this one written in big red letters, or something, then perhaps we won't have to worry about too much? I dunno.
Quoted 10 lines
Quoted 7 lines
builtin/add.c int run_add_interactive(const char *revision, const char *patch_mode, · line 196
Quoted 2 lines
builtin/add.c +2
-0+if (use_builtin_add_i ==1 && !patch_mode) +return !!run_add_i (the_repository, pathspec);I am hoping that eventually "add -p" will also be routed to the new codepath. Would it make sense to have "&& !patch_mode" here, especially at this step where
run_add_i()won't do anything useful anyway yet?The
&& !patch_modeis here to allow for a gradual adoption of the built-in parts. ...
Ah, so "add.usebuiltin = interactive patch" can (eventually) choose to use the C code for both while "add.usebuiltin = interactive" would not use it for the patch mode, or something? Or even
add.interactive.usebuiltin = yes
add.patch.usebuiltin = no
perhaps?
Quoted 1 line
Of course, eventually this will be handled.
Yup, again, the knob is merely temporary.
Re: [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`
Hi Junio,
Quoted 22 lines · Junio C Hamano
Quoted 1 line · Johannes Schindelin
Besides, I really hope that this would be only temporary,...
Oh, no question about it. This should be temporary knob.
I still do worry about giving a bad example for others to copy. People tend to copy & paste without thinking. Either we come up with and use a two-level name, or we add a comment to explain to developers (not users---as this is merely a temporary thing) why they should never follow suit using three-level names for things like this one written in big red letters, or something, then perhaps we won't have to worry about too much? I dunno.
Quoted 8 lines
Quoted 7 lines
builtin/add.c int run_add_interactive(const char *revision, const char *patch_mode, · line 196
Quoted 2 lines
builtin/add.c +2
-0+if (use_builtin_add_i ==1 && !patch_mode) +return !!run_add_i (the_repository, pathspec);I am hoping that eventually "add -p" will also be routed to the new codepath. Would it make sense to have "&& !patch_mode" here, especially at this step where
run_add_i()won't do anything useful anyway yet?
Quoted 8 lines
Quoted 2 lines
The
&& !patch_modeis here to allow for a gradual adoption of the built-in parts. ...Ah, so "add.usebuiltin = interactive patch" can (eventually) choose to use the C code for both while "add.usebuiltin = interactive" would not use it for the patch mode, or something? Or even
add.interactive.usebuiltin = yes
This is what I had in mind.
Quoted 1 line
add.patch.usebuiltin = no
And this is what I should have done ;-)
But maybe I should do
add.useBuiltin = wheneverPossible
?
Ciao,
Dscho
Quoted 6 lines
perhaps?
Quoted 1 line
Of course, eventually this will be handled.
Yup, again, the knob is merely temporary.
Re: [PATCH v3 01/11] Start to implement a built-in version of `git add --interactive`
Quoted 8 lines · Johannes Schindelin
Quoted 5 lines
Ah, so "add.usebuiltin = interactive patch" can (eventually) choose to use the C code for both while "add.usebuiltin = interactive" would not use it for the patch mode, or something? Or even
add.interactive.usebuiltin = yes
This is what I had in mind.
Quoted 7 lines
Quoted 1 line
add.patch.usebuiltin = no
And this is what I should have done ;-)
But maybe I should do
add.useBuiltin = wheneverPossible
Ah, either is fine, I think, but because this is meant to be temporary and not advertised to end-users for purposes other than purely an escape hatch, I would not spend too much engineering effort (iow, do the easiest to add and then easiest to later yank out).
Thanks.
Re: [PATCH v3 07/11] Add a function to determine unique prefixes for a list of strings
In reply to Johannes SchindelinQuoted 74 lines · Johannes Schindelin
Hi Gábor,
Quoted 70 lines · SZEDER Gábor
Quoted 69 lines · Slavica Djukic via GitGitGadget
In the
git add -icommand, we show unique prefixes of the commands and files, to give an indication what prefix would select them.Naturally, the C implementation looks a lot different than the Perl
implementation: in Perl, a trie is much easier implemented, while we already have a pretty neat hashmap implementation in C that we use for the purpose of storing (not necessarily unique) prefixes.The idea: for each item that we add, we generate prefixes starting with the first letter, then the first two letters, then three, etc, until we find a prefix that is unique (or until the prefix length would be longer than we want). If we encounter a previously-unique prefix on the way, we adjust that item's prefix to make it unique again (or we mark it as having no unique prefix if we failed to find one). These partial prefixes are stored in a hash map (for quick lookup times).
To make sure that this function works as expected, we add a test using a special-purpose test helper that was added for that purpose.
Note: We expect the list of prefix items to be passed in as a list of pointers rather than as regular list to avoid having to copy information (the actual items will most likely contain more information than just the name and the length of the unique prefix, but passing in
struct prefix_item *would not allow for that).prefix-map.c new file +38
-0@@ -0,0 +1,109 @@ +#include "cache.h" +#include "prefix-map.h" + +static int map_cmp (const void *unused_cmp_data, +const void *entry, +const void *entry_or_key, +const void *unused_keydata) +{ +const struct prefix_map_entry *a = entry; +const struct prefix_map_entry *b = entry_or_key; + +return a->prefix_length != b->prefix_length || +strncmp (a->name, b->name, a->prefix_length); +} + +static void add_prefix_entry (struct hashmap *map,const char *name, +size_t prefix_length,struct prefix_item *item) +{ +struct prefix_map_entry *result =xmalloc (sizeof (*result)); + result->name = name; + result->prefix_length = prefix_length; + result->item = item; +hashmap_entry_init (result,memhash (name, prefix_length)); +hashmap_add (map, result); +} + +static void init_prefix_map (struct prefix_map *prefix_map, +int min_prefix_length,int max_prefix_length) +{ +hashmap_init (&prefix_map->map, map_cmp,NULL ,0 ); + prefix_map->min_length = min_prefix_length; + prefix_map->max_length = max_prefix_length; +} + +static void add_prefix_item (struct prefix_map *prefix_map, +struct prefix_item *item) +{ +struct prefix_map_entry e = { {NULL } }, *e2;
Quoted 8 lines
Quoted 8 lines
prefix-map.c line 46
Quoted 8 lines
prefix-map.c new file +8
-0+int j; + + e.item = item; + e.name = item->name; + +for (j = prefix_map->min_length; + j <= prefix_map->max_length && e.name[j]; j++) { +/* Avoid breaking UTF-8 multi-byte sequences */
Ok, that's a good idea, but...
Quoted 2 lines
Quoted 2 lines
prefix-map.c line 48
Quoted 2 lines
prefix-map.c new file +2
-0+if (!isascii (e.name[j])) +break ;
This doesn't seem right. It just breaks the loop, potentially leaving 'item->prefix_length' set from the previous iteration. Try the two strings "123" and "12ő", in this order: they both get prefix_length = 3, i.e. the prefix ends between the two bytes of the multi-byte character.
Furthermore, neither the docstring of find_unique_prefixes() nor the commit message mention what is supposed to happen with paths with multi-byte characters. I think at least one of them should.
Quoted 70 lines
Quoted 58 lines
Quoted 58 lines
prefix-map.c new file +58
-0+ + e.prefix_length = j; +hashmap_entry_init (&e,memhash (e.name, j)); + e2 =hashmap_get (&prefix_map->map, &e,NULL ); +if (!e2) { +/* prefix is unique at this stage */ + item->prefix_length = j; +add_prefix_entry (&prefix_map->map, e.name, j, item); +break ; + } + +if (!e2->item) +continue ;/* non-unique prefix */ + +if (j != e2->item->prefix_length ||memcmp (e.name, e2->name, j)) +BUG ("unexpected prefix length: %d != %d (%s != %s)" , + j, (int )e2->item->prefix_length, e.name, e2->name); + +/* skip common prefix */ +for (; j < prefix_map->max_length && e.name[j]; j++) { +if (e.item->name[j] != e2->item->name[j]) +break ; +add_prefix_entry (&prefix_map->map, e.name, j +1 , +NULL ); + } + +/* e2 no longer refers to a unique prefix */ +if (j < prefix_map->max_length && e2->name[j]) { +/* found a new unique prefix for e2's item */ + e2->item->prefix_length = j +1 ; +add_prefix_entry (&prefix_map->map, e2->name, j +1 , + e2->item); + } +else + e2->item->prefix_length =0 ; + e2->item =NULL ; + +if (j < prefix_map->max_length && e.name[j]) { +/* found a unique prefix for the item */ + e.item->prefix_length = j +1 ; +add_prefix_entry (&prefix_map->map, e.name, j +1 , + e.item); + }else +/* item has no (short enough) unique prefix */ + e.item->prefix_length =0 ; + +break ; + } +} + +void find_unique_prefixes (struct prefix_item **list,size_t nr, +int min_length,int max_length) +{ +int i; +struct prefix_map prefix_map; + +init_prefix_map (&prefix_map, min_length, max_length); +for (i =0 ; i < nr; i++)Quoted 8 lines
prefix-map.c line 109
Quoted 3 lines
prefix-map.c new file +3
-0+add_prefix_item (&prefix_map, list[i]); +hashmap_free (&prefix_map.map,1 ); +}Between the commit message, the in-code comment, the names of the new files, and implementation I was left somewhat confused about what this is about and how it works. TBH, I didn't even try to understand how all the above works, in particular the
add_prefix_item()function.Let me try to explain it here, and maybe you can help me by suggesting an improved commit message and/or code comments?
Quoted 8 lines
The problem is this: given a set of items with labels (e.g. file names), find, for each item, the unique prefix that identifies it. Example:
given the fileshello.txt,heaven.txtandhell.txt, the items' unique prefixes would behello,heaandhell., respectively.In
git add -i, we actually only want to allow alphanumerical prefixes, and we also want at least one, and at most three characters, so only the second item would have an admissible unique prefix:hea.
You say "at most three characters", but I notive that the call added to 'add-interactive.c' in a later commit specifies 4.
I wonder what's the reason for the minimum prefix length, though. I mean, you can't really have a unique prefix shorter than one character...
Quoted 11 lines
Quoted 8 lines
However, I think it would be much-much simpler to first sort (a copy of?) the array of prefix item pointers based on their 'name' field, and then look for a unique prefix in each neighboring pair. Perhaps it would even be faster, because it doesn't have to allocate a bunch of hashmap items, though I don't think that it matters much in practice (i.e. I expect the number of items to be fairly small; presumably nobody will run interactive add after a mass refactoring modifying thousands of files).
The time complexity of the sorted list would be O(n*log(n)), while the hashmap-based complexity would be an amortized O(n).
Oh, so you don't know how/why it works, either!? ;)
This hashmap-based implementation calls hashmap_get() approx. n * (max_length - min_length) times.
Furthermore, depending on the strings and 'max_length', it can call add_prefix_entry() more than n times. E.g. the six strings "a1", "a2", "ab1", "ab2", "abc1", "abc2", in this order, and max_length=4 result in 9 add_prefix_entry() calls. And each add_prefix_entry() call involves a memory allocation.
Ok, that's theory, let's see practice.
For a (very) quick'n'dirty performance test I replaced those five items in 't/helper/test-prefix-map.c' with the output of:
git -C ..../webkit.git/ ls-tree -r --name-only HEAD | sed -e 's/^/{ "/; s/$/" },/'That's 280734 real paths. Then a simple 'time test-tool prefix-map' with the hashmap-based implementation ran for 0.055s, while using my PoC sort-based algorithm it ran for 0.063s. So while the hashmap-based implementation is indeed faster, they are both well below 0.1s, so in the context of 'git add -i' the performance difference between the two approaches doesn't really matter.
If we look beyond 'git add -i's requirements, and specify larger max_length values, the hashmap-based implementation gets progressively slower. Already at max_length=5 it's a tad slower than the sort-based implementation, and at 9 it takes twice as long.
The current hashmap-based implementation is ~50% more lines than my sort-based PoC. I'll send it out in a minute.
Quoted 42 lines
And yes, you would not _want_ to run interactive add after a mass refactoring. But it happens. It happens to me more times than I care to admit. And you know what? I really appreciate that even the Perl version is relatively snappy in those circumstances.
Quoted 28 lines
Quoted 6 lines
prefix-map.h new file +0
-0prefix-map.h line 9
Quoted 9 lines
prefix-map.h new file +8
-0@@ -0,0 +1,40 @@ +#ifndef PREFIX_MAP_H +#define PREFIX_MAP_H +#include "hashmap.h" + +struct prefix_item { +const char *name; +size_t prefix_length; +};This struct is part of find_unique_prefixes()'s signature, good.
Quoted 10 lines
prefix-map.h new file +10
-0+struct prefix_map_entry { +struct hashmap_entry e; +const char *name; +size_t prefix_length; +/* if item is NULL, the prefix is not unique */ +struct prefix_item *item; +}; + +struct prefix_map { +struct hashmap map;Quoted 8 lines
prefix-map.h line 22
Quoted 2 lines
prefix-map.h new file +2
-0+int min_length, max_length; +};However, neither of these two structs nor the hashmap appear in the function's signature, but are all implementation details. Therefore, they should not be defined and included here in the header but in the .c source file. (But as mentioned above, I think this could be implemented much simpler without these data structures.)
Quoted 8 lines
Right you are!
Quoted 4 lines
Furthermore, this is not a map.
A map, in general, is a container of key-value pairs that allows efficient insertion, removal and lookup. This so-calledprefix_mapdoes none of that, so it should not be called a map.What would you call it instead?
Well, I would rather get rid of it in the first place :)
In my PoC I called the files 'unique-prefix.{c,h}'.
Quoted 6 lines
Quoted 5 lines
Quoted 2 lines
+/*
+ * Find unique prefixes in a given list of strings.... and stores the length of the unique prefixes in the '
prefix_length' field of the elements of the given array.
Quoted 6 lines
Good idea. I changed it to also explain what is meant by "unique prefix":
* Given a list of names, find unique prefixes (i.e. the first <n> characters
* that uniquely identify the names) and store the lengths of the unique
* prefixes in the 'prefix_length' field of the elements of the given array..
Sounds good.
Quoted 11 lines
Quoted 11 lines
Quoted 11 lines
+ *
+ * Typically, thestruct prefix_iteminformation will be but a field in the+ * actual item struct; For this reason, the
listparameter is specified as a
+ * list of pointers to the items.
+ *
+ * Themin_length/max_lengthparameters define what length the unique
+ * prefixes should have.
+ *
+ * If no unique prefix could be found for a given item, itsprefix_length
+ * will be set to 0.
I ran into cases where the hashmap-based implementation didn't set 'prefix_length' to 0 when it, in my opinion, should have, but left it untouched. Just try any string shorter than 'min_length'.
May or may not be related: try "ab", "ab", "abc", in this order: both "ab" get their 'prefix_length' set to 0, as they should, but the 'prefix_length' of "abc" is left untouched, although it should be set to 3.
Hmm, now that I've mentioned "in this order" three times already:
another benefit of the sort-based approach is that the order of elements doesn't matter.
Quoted 69 lines
Quoted 4 lines
Quoted 2 lines
+ */
+void find_unique_prefixes(structprefix_item**list,size_tnr,The first argument is not a list but an array.
Indeed.
Quoted 4 lines
prefix-map.h line 38
Quoted 1 line
prefix-map.h new file +1
-0+ int min_length, int max_length);
size_t, perhaps? These are closely related to 'prefix_item.prefix_length', which is already (rightfully)size_t.True.
Quoted 45 lines
Quoted 31 lines
prefix-map.h new file +1
-0+#endif t/helper/test-prefix-map.c new file +24
-0@@ -0,0 +1,58 @@ +#include "test-tool.h" +#include "cache.h" +#include "prefix-map.h" + +static size_t test_count, failed_count; + +static void check (int succeeded,const char *file,size_t line_no, +const char *fmt, ...) +{ +va_list ap; + + test_count++; +if (succeeded) +return ; + +va_start (ap, fmt); +fprintf (stderr,"%s:%d: " , file, (int )line_no); +vfprintf (stderr, fmt, ap); +fputc ('\n' , stderr); +va_end (ap); + + failed_count++; +} +t/helper/test-prefix-map.c line 34
Quoted 10 lines
t/helper/test-prefix-map.c new file +10
-0+#define EXPECT_SIZE_T_EQUALS (expect, actual, hint) \ + check(expect == actual, __FILE__, __LINE__, \ + "size_t's do not match: %" \ + PRIdMAX " != %" PRIdMAX " (%s) (%s)", \ + (intmax_t)expect, (intmax_t)actual, #actual, hint) + +int cmd__prefix_map (int argc,const char **argv) +{ +#define NR 5 +struct prefix_item items[NR ] = {You don't have to tell the compiler how many elements this array will contain, it will figure that out on its own.
Quoted 9 lines
t/helper/test-prefix-map.c line 41
Quoted 7 lines
t/helper/test-prefix-map.c new file +7
-0+ { "unique" }, + { "hell" }, + { "hello" }, + { "wok" }, + { "world" }, + }; + struct prefix_item *list[NR] = {Likewise.
Quoted 6 lines
That is correct.
What the compiler _cannot_ figure out, on its own, however, is that
itemsandlist_need_ to contain the same number of items.Hence the need for
NR.
OK, that's a good point. I'd still like to get rid of that NR with something like this instead:
struct prefix_item items[] = { .... };
struct prefix_item *list[ARRAY_SIZE(items)];
for (i = 0; i < ARRAY_SIZE(items); i++) {
list[i] = &items[i];
list[i]->prefix_length = 12345; /* dummy */
}
find_unique_prefixes(list, ARRAY_SIZE(list), 0, 3);Note the dummy value, to catch cases when 'prefix_length' is left untouched.
This made this test tool more convenient to use, because I didn't have to fiddle with NR and, worse, with those 'items, items+1, items+N'.
It's still a bit PITA (having to re-build each time I change the test vector), though, compared to driving it from stdin and verifying it through its stdout.
Quoted 24 lines
Quoted 8 lines
t/helper/test-prefix-map.c line 45
Quoted 4 lines
t/helper/test-prefix-map.c new file +4
-0+ items, items + 1, items + 2, items + 3, items + 4 + }; + +find_unique_prefixes (list,NR ,1 ,3 );This could be
find_unique_prefixes(list, ARRAY_SIZE(list), 1, 3), and then there is no need for that NR macro anymore.Quoted 13 lines
t/helper/test-prefix-map.c line 58
Quoted 13 lines
t/helper/test-prefix-map.c new file +13
-0+ +#define EXPECT_PREFIX_LENGTH_EQUALS (expect, index) \ + EXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \ + list[index]->name) + +EXPECT_PREFIX_LENGTH_EQUALS (1 ,0 ); +EXPECT_PREFIX_LENGTH_EQUALS (0 ,1 ); +EXPECT_PREFIX_LENGTH_EQUALS (0 ,2 ); +EXPECT_PREFIX_LENGTH_EQUALS (3 ,3 ); +EXPECT_PREFIX_LENGTH_EQUALS (3 ,4 ); + +return !!failed_count; +}Thank you for your review!
Dscho
PATCH 1/1 A simpler find_unique_prefixes() implementation
Implement find_unique_prefixes() by sorting the prefix items lexicographically and then looking for unique prefixes in adjacent pairs. It's definitely shorter than the current hashmap-based implementation (101 vs 149 lines), subjectively easier to follow, and hopefully less buggy (some of the new elements in the test trigger failure with the hashmap).
TODO: deal multi-byte caracter sequences, make the test tool more
developer-friendly
--- Makefile | 2 +- add-interactive.c | 2 +- prefix-map.c | 109 ------------------------------------- prefix-map.h | 40 -------------- t/helper/test-prefix-map.c | 31 +++++++---- unique-prefix.c | 73 +++++++++++++++++++++++++ unique-prefix.h | 28 ++++++++++ 7 files changed, 124 insertions(+), 161 deletions(-) delete mode 100644 prefix-map.c delete mode 100644 prefix-map.h create mode 100644 unique-prefix.c create mode 100644 unique-prefix.h
Makefile +1 -1
@@ -944,7 +944,6 @@ LIB_OBJS += patch-ids.o LIB_OBJS += path.o LIB_OBJS += pathspec.o LIB_OBJS += pkt-line.o -LIB_OBJS += prefix-map.o LIB_OBJS += preload-index.o LIB_OBJS += pretty.o LIB_OBJS += prio-queue.o
@@ -1013,6 +1012,7 @@ LIB_OBJS += transport-helper.o LIB_OBJS += tree-diff.o LIB_OBJS += tree.o LIB_OBJS += tree-walk.o +LIB_OBJS += unique-prefix.o LIB_OBJS += unpack-trees.o LIB_OBJS += upload-pack.o LIB_OBJS += url.o
add-interactive.c +1 -1
@@ -5,7 +5,7 @@#include "diffcore.h" #include "revision.h" #include "refs.h" -#include "prefix-map.h" +#include "unique-prefix.h" struct add_i_state {struct repository *r;
prefix-map.c deleted +0 -109
109 lines not on this page · raw message
prefix-map.h deleted +0 -40
@@ -1,40 +0,0 @@ -#ifndef PREFIX_MAP_H -#define PREFIX_MAP_H - -#include "hashmap.h" - -struct prefix_item { -const char *name; -size_t prefix_length; -}; - -struct prefix_map_entry { -struct hashmap_entry e; -const char *name; -size_t prefix_length; -/* if item is NULL, the prefix is not unique */ -struct prefix_item *item; -}; - -struct prefix_map { -struct hashmap map; -int min_length, max_length; -}; - -/* -* Find unique prefixes in a given list of strings. -* -* Typically, the `struct prefix_item` information will be but a field in the -* actual item struct; For this reason, the `list` parameter is specified as a -* list of pointers to the items. -* -* The `min_length`/`max_length` parameters define what length the unique -* prefixes should have. -* -* If no unique prefix could be found for a given item, its `prefix_length` -* will be set to 0. -*/ -void find_unique_prefixes (struct prefix_item **list,size_t nr, -int min_length,int max_length); - -#endif
t/helper/test-prefix-map.c +21 -10
@@ -1,6 +1,5 @@#include "test-tool.h" -#include "cache.h" -#include "prefix-map.h" +#include "unique-prefix.h" static size_t test_count, failed_count;
@@ -24,25 +23,32 @@ static void check(int succeeded, const char *file, size_t line_no,#define EXPECT_SIZE_T_EQUALS (expect, actual, hint) \ check(expect == actual, __FILE__, __LINE__, \ - "size_t's do not match: %" \ - PRIdMAX " != %" PRIdMAX " (%s) (%s)", \ + "size_t's do not match: expected: %" \ + PRIdMAX " got: %" PRIdMAX " (%s) (%s)", \ (intmax_t)expect, (intmax_t)actual, #actual, hint)int cmd__prefix_map (int argc,const char **argv) { -#define NR 5 -struct prefix_item items[NR ] = { +size_t i; +struct prefix_item items[] = { {"unique" }, {"hell" }, {"hello" }, {"wok" }, {"world" }, + {"a" }, + {"" }, + {"pq" }, + {"pq" }, + {"pqr" }, }; -struct prefix_item *list[NR ] = { - items, items +1 , items +2 , items +3 , items +4 - }; +struct prefix_item *list[ARRAY_SIZE (items)]; +for (i =0 ; i <ARRAY_SIZE (items); i++) { + list[i] = &items[i]; + list[i]->prefix_length =12345 ;/* dummy */ + } -find_unique_prefixes (list,NR ,1 ,3 ); +find_unique_prefixes (list,ARRAY_SIZE (list),1 ,3 );#define EXPECT_PREFIX_LENGTH_EQUALS (expect, index) \ EXPECT_SIZE_T_EQUALS(expect, list[index]->prefix_length, \
@@ -53,6 +59,11 @@ int cmd__prefix_map(int argc, const char **argv)EXPECT_PREFIX_LENGTH_EQUALS (0 ,2 );EXPECT_PREFIX_LENGTH_EQUALS (3 ,3 );EXPECT_PREFIX_LENGTH_EQUALS (3 ,4 ); +EXPECT_PREFIX_LENGTH_EQUALS (1 ,5 ); +EXPECT_PREFIX_LENGTH_EQUALS (0 ,6 ); +EXPECT_PREFIX_LENGTH_EQUALS (0 ,7 ); +EXPECT_PREFIX_LENGTH_EQUALS (0 ,8 ); +EXPECT_PREFIX_LENGTH_EQUALS (3 ,9 );return !!failed_count; }
unique-prefix.c new file +73 -0
73 lines not on this page · raw message
unique-prefix.h new file +28 -0
@@ -0,0 +1,28 @@ +#ifndef UNIQUE_PREFIX_H +#define UNIQUE_PREFIX_H + +struct prefix_item { +const char *name; +size_t prefix_length; +}; + +/* +* Find unique prefixes in a given array of strings. +* Given a list of names, find unique prefixes (i.e. the first <n> characters +* that uniquely identify the names) and store the lengths of the unique +* prefixes in the 'prefix_length' field of the elements of the given array. +* +* Typically, the `struct prefix_item` information will be a field in the +* actual item struct; For this reason, the `items` parameter is specified +* as an array of pointers to the items. +* +* The `min_length`/`max_length` parameters define what length the unique +* prefixes should have. +* +* If no unique prefix could be found for a given item, its `prefix_length` +* will be set to 0. +*/ +void find_unique_prefixes (struct prefix_item **items,size_t nr, +int min_length,int max_length); + +#endif
Signature · 1 line
2.23.0.331.g4e51dcdf11
Re: [PATCH] [PoC] A simpler find_unique_prefixes() implementation
Hi Gábor,
Quoted 9 lines · SZEDER Gábor
Implement
find_unique_prefixes()by sorting the prefix items lexicographically and then looking for unique prefixes in adjacent pairs. It's definitely shorter than the current hashmap-based implementation (101 vs 149 lines), subjectively easier to follow, and hopefully less buggy (some of the new elements in the test trigger failure with the hashmap).TODO: deal multi-byte caracter sequences, make the test tool more
developer-friendly
Sorry for my late response, I am still trying to catch up with an ever-growing inbox after trying to pretend to be on vacation.
Your argument is a strong one, and I will try to re-roll, folding the functionality to find the unique prefixes into add-interactive.c while trying to reuse the already-sorted list for the lookup by prefix.
As to the multi-byte UTF-8 characters: you're right, that code has a bug. The intention was to require only single-byte characters in the prefix, for simplicity.
Thanks,
Dscho
PATCH v5 1/9 Start to implement a built-in version of `git add --interactive`
In reply to Johannes Schindelin via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This is hardly the first conversion of a Git command that is implemented as a script to a built-in. So far, the most successful strategy for such conversions has been to add a built-in helper and call that for more and more functionality from the script, as more and more parts are converted.
With the interactive add, we choose a different strategy. The sole reason for this is that on Windows (where such a conversion has the most benefits in terms of speed and robustness) we face the very specific problem that a system() call in Perl seems to close stdin in the parent process when the spawned process consumes even one character from stdin. And that just does not work for us here, as it would stop the main loop as soon as any interactive command was performed by the helper. Which is almost all of the commands in git add -i.
It is almost as if Perl told us once again that it does not want us to use it on Windows.
Instead, we follow the opposite route where we start with a bare-bones version of the built-in interactive add, guarded by the new add.interactive.useBuiltin config variable, and then add more and more functionality to it, until it is feature complete.
At this point, the built-in version of git add -i only states that it cannot do anything yet ;-)
--- Documentation/config/add.txt | 5 +++++ Makefile | 1 + add-interactive.c | 7 +++++++ add-interactive.h | 8 ++++++++ builtin/add.c | 10 ++++++++++ t/README | 4 ++++ 6 files changed, 35 insertions(+) create mode 100644 add-interactive.c create mode 100644 add-interactive.h
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 +10 -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,14 @@ int run_add_interactive(const char *revision, const char *patch_mode, {int status, i;struct argv_array argv =ARGV_ARRAY_INIT ; +int use_builtin_add_i = +git_env_bool ("GIT_TEST_ADD_I_USE_BUILTIN" ,-1 ); +if (use_builtin_add_i <0 ) +git_config_get_bool ("add.interactive.usebuiltin" , + &use_builtin_add_i); + +if (use_builtin_add_i ==1 && !patch_mode) +return !!run_add_i (the_repository, pathspec);argv_array_push (&argv,"add--interactive" );if (patch_mode)
@@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb) ignore_add_errors =git_config_bool (var, value);return 0 ; } +return git_default_config (var, value, cb); }
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 v5 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 v5 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
PATCH v5 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
v5 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 | 650 +++++++++++++++++++++++++++++++++++ add-interactive.h | 8 + builtin/add.c | 10 + diff.c | 39 +-- diff.h | 20 ++ t/README | 4 + t/t3701-add-interactive.sh | 25 ++ 9 files changed, 739 insertions(+), 23 deletions(-) create mode 100644 add-interactive.c create mode 100644 add-interactive.h
base-commit: 566a1439f6f56c2171b8853ddbca0ad3f5098770
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-170%2Fdscho%2Fadd-i-in-c-status-and-help-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-170/dscho/add-i-in-c-status-and-help-v5
Pull-Request: https://github.com/gitgitgadget/git/pull/170
Range-diff vs v4:
1: ad8752eca7 ! 1: ff59d2d0b3 Start to implement a built-in version of git add --interactive
@@ -47,7 +47,7 @@
--- a/Makefile
+++ b/Makefile
@@
- -name '*.h' -print))
+ -name '*.h' -print)))
LIB_OBJS += abspath.o
+LIB_OBJS += add-interactive.o
@@ -125,7 +125,7 @@
git-config(1).
+GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the
-+builtin version of git add -i. See 'add.interactive.useBuiltin' in
++built-in version of git add -i. See 'add.interactive.useBuiltin' in
+git-config(1).
+
GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading 2: 38cc04c1d9 ! 2: 2fc8cc3546 diff: export diffstat interface
@@ -12,7 +12,7 @@
builtin implementation of git-add--interactive's status.
- Signed-off-by: Slavica Djukic <[email protected]>
+ Signed-off-by: Slavica Đukić <[email protected]>
diff --git a/diff.c b/diff.c
3: ee3e40293c ! 3: 6aaa0de4f4 built-in add -i: implement the status command
@@ -3,7 +3,7 @@
built-in add -i: implement the `status` command
This implements the `status` command of `git add -i`. The data
- structures introduced in this commit will be extended as needed later.
+ structures introduced in this commit will be extended later, as needed.
At this point, we re-implement only part of the `list_and_choose()`
function of the Perl script `git-add--interactive.perl` and call it
@@ -16,12 +16,8 @@
will be used to implement the main loop of the built-in `git add -i`, at
which point the new `status` command can actually be used.
- Note that we pass the list of items as a `struct item **` as opposed to
- a `struct item *`, to allow for the actual items to contain much more
- information than merely the name.
-
Signed-off-by: Daniel Ferreira <[email protected]>
- Signed-off-by: Slavica Djukic <[email protected]>
+ Signed-off-by: Slavica Đukić <[email protected]>
Signed-off-by: Johannes Schindelin <[email protected]>
diff --git a/add-interactive.c b/add-interactive.c
@@ -33,29 +29,35 @@
+#include "diffcore.h"
+#include "revision.h"
+#include "refs.h"
++#include "string-list.h"
+
-+struct item {
-+ const char *name;
++struct add_i_state {
++ struct repository *r;
+};
+
++static void init_add_i_state(struct add_i_state *s, struct repository *r)
++{
++ s->r = r;
++}
++
+struct list_options {
+ const char *header;
-+ void (*print_item)(int i, struct item *item, void *print_item_data);
++ void (*print_item)(int i, struct string_list_item *item, void *print_item_data);
+ void *print_item_data;
+};
+
-+static void list(struct item **list, size_t nr, struct list_options *opts)
++static void list(struct string_list *list, struct list_options *opts)
+{
+ int i;
+
-+ if (!nr)
++ if (!list->nr)
+ return;
+
+ if (opts->header)
+ printf("%s\n", opts->header);
+
-+ for (i = 0; i < nr; i++) {
-+ opts->print_item(i, list[i], opts->print_item_data);
++ for (i = 0; i < list->nr; i++) {
++ opts->print_item(i, list->items + i, opts->print_item_data);
+ putchar('\n');
+ }
+}
@@ -65,62 +67,34 @@
+ unsigned seen:1, binary:1;
+};
+
-+struct file_list {
-+ struct file_item {
-+ struct item item;
-+ struct adddel index, worktree;
-+ } **file;
-+ size_t nr, alloc;
++struct file_item {
++ struct adddel index, worktree;
+};
+
-+static void add_file_item(struct file_list *list, const char *name)
++static void add_file_item(struct string_list *files, const char *name)
+{
-+ struct file_item *item;
-+
-+ FLEXPTR_ALLOC_STR(item, item.name, name);
++ struct file_item *item = xcalloc(sizeof(*item), 1);
+
-+ ALLOC_GROW(list->file, list->nr + 1, list->alloc);
-+ list->file[list->nr++] = item;
-+}
-+
-+static void reset_file_list(struct file_list *list)
-+{
-+ size_t i;
-+
-+ for (i = 0; i < list->nr; i++)
-+ free(list->file[i]);
-+ list->nr = 0;
-+}
-+
-+static void release_file_list(struct file_list *list)
-+{
-+ reset_file_list(list);
-+ FREE_AND_NULL(list->file);
-+ list->alloc = 0;
-+}
-+
-+static int file_item_cmp(const void *a, const void *b)
-+{
-+ const struct file_item * const *f1 = a;
-+ const struct file_item * const *f2 = b;
-+
-+ return strcmp((*f1)->item.name, (*f2)->item.name);
++ string_list_append(files, name)->util = item;
+}
+
+struct pathname_entry {
+ struct hashmap_entry ent;
-+ size_t index;
-+ char pathname[FLEX_ARRAY];
++ const char *name;
++ struct file_item *item;
+};
+
+static int pathname_entry_cmp(const void *unused_cmp_data,
-+ const void *entry, const void *entry_or_key,
-+ const void *pathname)
++ const struct hashmap_entry *he1,
++ const struct hashmap_entry *he2,
++ const void *name)
+{
-+ const struct pathname_entry *e1 = entry, *e2 = entry_or_key;
++ const struct pathname_entry *e1 =
++ container_of(he1, const struct pathname_entry, ent);
++ const struct pathname_entry *e2 =
++ container_of(he2, const struct pathname_entry, ent);
+
-+ return strcmp(e1->pathname,
-+ pathname ? (const char *)pathname : e2->pathname);
++ return strcmp(e1->name, name ? (const char *)name : e2->name);
+}
+
+struct collection_status {
@@ -128,7 +102,7 @@
+
+ const char *reference;
+
-+ struct file_list *list;
++ struct string_list *files;
+ struct hashmap file_map;
+};
+
@@ -149,24 +123,24 @@
+ const char *name = stat.files[i]->name;
+ int hash = strhash(name);
+ struct pathname_entry *entry;
-+ size_t file_index;
-+ struct file_item *file;
++ struct file_item *file_item;
+ struct adddel *adddel;
+
-+ entry = hashmap_get_from_hash(&s->file_map, hash, name);
-+ if (entry)
-+ file_index = entry->index;
-+ else {
-+ FLEX_ALLOC_STR(entry, pathname, name);
++ entry = hashmap_get_entry_from_hash(&s->file_map, hash, name,
++ struct pathname_entry, ent);
++ if (!entry) {
++ add_file_item(s->files, name);
++
++ entry = xcalloc(sizeof(*entry), 1);
+ hashmap_entry_init(&entry->ent, hash);
-+ entry->index = file_index = s->list->nr;
++ entry->name = s->files->items[s->files->nr - 1].string;
++ entry->item = s->files->items[s->files->nr - 1].util;
+ hashmap_add(&s->file_map, &entry->ent);
-+
-+ add_file_item(s->list, name);
+ }
-+ file = s->list->file[file_index];
+
-+ adddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;
++ file_item = entry->item;
++ adddel = s->phase == FROM_INDEX ?
++ &file_item->index : &file_item->worktree;
+ adddel->seen = 1;
+ adddel->add = stat.files[i]->added;
+ adddel->del = stat.files[i]->deleted;
@@ -176,7 +150,7 @@
+ free_diffstat_info(&stat);
+}
+
-+static int get_modified_files(struct repository *r, struct file_list *list,
++static int get_modified_files(struct repository *r, struct string_list *files,
+ const struct pathspec *ps)
+{
+ struct object_id head_oid;
@@ -184,10 +158,12 @@
+ &head_oid, NULL);
+ struct collection_status s = { FROM_WORKTREE };
+
-+ if (repo_read_index_preload(r, ps, 0) < 0)
++ if (discard_index(r->index) < 0 ||
++ repo_read_index_preload(r, ps, 0) < 0)
+ return error(_("could not read index"));
+
-+ s.list = list;
++ string_list_clear(files, 1);
++ s.files = files;
+ hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);
+
+ for (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {
@@ -214,15 +190,15 @@
+ run_diff_files(&rev, 0);
+ }
+ }
-+ hashmap_free(&s.file_map, 1);
++ hashmap_free_entries(&s.file_map, struct pathname_entry, ent);
+
+ /* While the diffs are ordered already, we ran *two* diffs... */
-+ QSORT(list->file, list->nr, file_item_cmp);
++ string_list_sort(files);
+
+ return 0;
+}
+
-+static void populate_wi_changes(struct strbuf *buf,
++static void render_adddel(struct strbuf *buf,
+ struct adddel *ad, const char *no_changes)
+{
+ if (ad->binary)
@@ -239,34 +215,31 @@
+ struct strbuf buf, index, worktree;
+};
+
-+static void print_file_item(int i, struct item *item,
++static void print_file_item(int i, struct string_list_item *item,
+ void *print_file_item_data)
+{
-+ struct file_item *c = (struct file_item *)item;
++ struct file_item *c = item->util;
+ struct print_file_item_data *d = print_file_item_data;
+
+ strbuf_reset(&d->index);
+ strbuf_reset(&d->worktree);
+ strbuf_reset(&d->buf);
+
-+ populate_wi_changes(&d->worktree, &c->worktree, _("nothing"));
-+ populate_wi_changes(&d->index, &c->index, _("unchanged"));
++ render_adddel(&d->worktree, &c->worktree, _("nothing"));
++ render_adddel(&d->index, &c->index, _("unchanged"));
+ strbuf_addf(&d->buf, d->modified_fmt,
-+ d->index.buf, d->worktree.buf, item->name);
++ d->index.buf, d->worktree.buf, item->string);
+
+ printf(" %2d: %s", i + 1, d->buf.buf);
+}
+
-+static int run_status(struct repository *r, const struct pathspec *ps,
-+ struct file_list *files, struct list_options *opts)
++static int run_status(struct add_i_state *s, const struct pathspec *ps,
++ struct string_list *files, struct list_options *opts)
+{
-+ reset_file_list(files);
-+
-+ if (get_modified_files(r, files, ps) < 0)
++ if (get_modified_files(s->r, files, ps) < 0)
+ return -1;
+
-+ if (files->nr)
-+ list((struct item **)files->file, files->nr, opts);
++ list(files, opts);
+ putchar('\n');
+
+ return 0;
@@ -275,6 +248,7 @@
int run_add_i(struct repository *r, const struct pathspec *ps)
{
- die(_("No commands are available in the built-in `git add -i` yet!"));
++ struct add_i_state s = { NULL };
+ struct print_file_item_data print_file_item_data = {
+ "%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+ };
@@ -282,17 +256,24 @@
+ NULL, print_file_item, &print_file_item_data
+ };
+ struct strbuf header = STRBUF_INIT;
-+ struct file_list files = { NULL };
++ struct string_list files = STRING_LIST_INIT_DUP;
+ int res = 0;
+
++ init_add_i_state(&s, r);
+ strbuf_addstr(&header, " ");
+ strbuf_addf(&header, print_file_item_data.modified_fmt,
+ _("staged"), _("unstaged"), _("path"));
+ opts.header = header.buf;
+
-+ res = run_status(r, ps, &files, &opts);
++ if (discard_index(r->index) < 0 ||
++ repo_read_index(r) < 0 ||
++ repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
++ NULL, NULL, NULL) < 0)
++ warning(_("could not refresh index"));
++
++ res = run_status(&s, ps, &files, &opts);
+
-+ release_file_list(&files);
++ string_list_clear(&files, 1);
+ strbuf_release(&print_file_item_data.buf);
+ strbuf_release(&print_file_item_data.index);
+ strbuf_release(&print_file_item_data.worktree);
4: 3c855d9fa5 < -: ---------- built-in add -i: refresh the index before running `status`
5: 24737a09f7 ! 4: e405f07110 built-in add -i: color the header in the `status` command
@@ -1,4 +1,4 @@
-Author: Johannes Schindelin <[email protected]>
+Author: Slavica Đukić <[email protected]>
built-in add -i: color the header in the `status` command
@@ -7,7 +7,7 @@
`git-add--interactive.perl` does.
Original-Patch-By: Daniel Ferreira <[email protected]>
- Signed-off-by: Slavica Djukic <[email protected]>
+ Signed-off-by: Slavica Đukić <[email protected]>
Signed-off-by: Johannes Schindelin <[email protected]>
diff --git a/add-interactive.c b/add-interactive.c
@@ -21,13 +21,14 @@
#include "diffcore.h"
#include "revision.h"
#include "refs.h"
+@@
-+struct add_i_state {
-+ struct repository *r;
+ struct add_i_state {
+ struct repository *r;
+ int use_color;
+ char header_color[COLOR_MAXLEN];
-+};
-+
+ };
+
+static void init_color(struct repository *r, struct add_i_state *s,
+ const char *slot_name, char *dst,
+ const char *default_color)
@@ -44,8 +45,9 @@
+ free(key);
+}
+
-+static int init_add_i_state(struct repository *r, struct add_i_state *s)
-+{
+ static void init_add_i_state(struct add_i_state *s, struct repository *r)
+ {
+- s->r = r;
+ const char *value;
+
+ s->r = r;
@@ -58,20 +60,16 @@
+ s->use_color = want_color(s->use_color);
+
+ init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
-+
-+ return 0;
-+}
-+
- struct item {
- const char *name;
- };
+ }
+
+ struct list_options {
@@
void *print_item_data;
};
--static void list(struct item **list, size_t nr, struct list_options *opts)
-+static void list(struct item **list, size_t nr,
-+ struct add_i_state *s, struct list_options *opts)
+-static void list(struct string_list *list, struct list_options *opts)
++static void list(struct add_i_state *s, struct string_list *list,
++ struct list_options *opts)
{
int i;
@@ -83,51 +81,14 @@
+ color_fprintf_ln(stdout, s->header_color,
+ "%s", opts->header);
- for (i = 0; i < nr; i++) {
- opts->print_item(i, list[i], opts->print_item_data);
+ for (i = 0; i < list->nr; i++) {
+ opts->print_item(i, list->items + i, opts->print_item_data);
@@
- printf(" %2d: %s", i + 1, d->buf.buf);
- }
-
--static int run_status(struct repository *r, const struct pathspec *ps,
-+static int run_status(struct add_i_state *s, const struct pathspec *ps,
- struct file_list *files, struct list_options *opts)
- {
- reset_file_list(files);
-
-- if (get_modified_files(r, files, ps) < 0)
-+ if (get_modified_files(s->r, files, ps) < 0)
+ if (get_modified_files(s->r, files, ps) < 0)
return -1;
- if (files->nr)
-- list((struct item **)files->file, files->nr, opts);
-+ list((struct item **)files->file, files->nr, s, opts);
+- list(files, opts);
++ list(s, files, opts);
putchar('\n');
return 0;
-@@
-
- int run_add_i(struct repository *r, const struct pathspec *ps)
- {
-+ struct add_i_state s = { NULL };
- struct print_file_item_data print_file_item_data = {
- "%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
- };
-@@
- struct file_list files = { NULL };
- int res = 0;
-
-+ if (init_add_i_state(r, &s))
-+ return error("could not parse `add -i` config");
-+
- strbuf_addstr(&header, " ");
- strbuf_addf(&header, print_file_item_data.modified_fmt,
- _("staged"), _("unstaged"), _("path"));
- opts.header = header.buf;
-
- repo_refresh_and_write_index(r, REFRESH_QUIET, 1);
-- if (run_status(r, ps, &files, &opts) < 0)
-+ if (run_status(&s, ps, &files, &opts) < 0)
- res = -1;
-
- release_file_list(&files);
6: ac67731cf1 ! 5: 25590fbbbe built-in add -i: implement the main loop
@@ -45,26 +45,26 @@
--- a/add-interactive.c
+++ b/add-interactive.c
@@
- };
+ }
struct list_options {
+ int columns;
const char *header;
- void (*print_item)(int i, struct item *item, void *print_item_data);
+ void (*print_item)(int i, struct string_list_item *item, void *print_item_data);
void *print_item_data;
@@
- static void list(struct item **list, size_t nr,
- struct add_i_state *s, struct list_options *opts)
+ static void list(struct add_i_state *s, struct string_list *list,
+ struct list_options *opts)
{
- int i;
+ int i, last_lf = 0;
- if (!nr)
+ if (!list->nr)
return;
@@
- for (i = 0; i < nr; i++) {
- opts->print_item(i, list[i], opts->print_item_data);
+ for (i = 0; i < list->nr; i++) {
+ opts->print_item(i, list->items + i, opts->print_item_data);
+
+ if ((opts->columns) && ((i + 1) % (opts->columns))) {
+ putchar('\t');
@@ -94,8 +94,7 @@
+ * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
+ * `LIST_AND_CHOOSE_QUIT` is returned.
+ */
-+static ssize_t list_and_choose(struct item **items, size_t nr,
-+ struct add_i_state *s,
++static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
+ struct list_and_choose_options *opts)
+{
+ struct strbuf input = STRBUF_INIT;
@@ -106,7 +105,7 @@
+
+ strbuf_reset(&input);
+
-+ list(items, nr, s, &opts->list_opts);
++ list(s, items, &opts->list_opts);
+
+ printf("%s%s", opts->prompt, "> ");
+ fflush(stdout);
@@ -140,7 +139,7 @@
+ }
+
+ p[sep] = '\0';
-+ if (index < 0 || index >= nr)
++ if (index < 0 || index >= items->nr)
+ printf(_("Huh (%s)?\n"), p);
+ else {
+ res = index;
@@ -163,17 +162,15 @@
return 0;
}
-+static void print_command_item(int i, struct item *item,
++typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
++ struct string_list *files,
++ struct list_options *opts);
++
++static void print_command_item(int i, struct string_list_item *item,
+ void *print_command_item_data)
+{
-+ printf(" %2d: %s", i + 1, item->name);
++ printf(" %2d: %s", i + 1, item->string);
+}
-+
-+struct command_item {
-+ struct item item;
-+ int (*command)(struct add_i_state *s, const struct pathspec *ps,
-+ struct file_list *files, struct list_options *opts);
-+};
+
int run_add_i(struct repository *r, const struct pathspec *ps)
{
@@ -182,11 +179,13 @@
+ { 4, N_("*** Commands ***"), print_command_item, NULL },
+ N_("What now")
+ };
-+ struct command_item
-+ status = { { "status" }, run_status };
-+ struct command_item *commands[] = {
-+ &status
++ struct {
++ const char *string;
++ command_t command;
++ } command_list[] = {
++ { "status", run_status },
+ };
++ struct string_list commands = STRING_LIST_INIT_NODUP;
+
struct print_file_item_data print_file_item_data = {
"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
@@ -196,27 +195,42 @@
+ 0, NULL, print_file_item, &print_file_item_data
};
struct strbuf header = STRBUF_INIT;
- struct file_list files = { NULL };
+ struct string_list files = STRING_LIST_INIT_DUP;
+ ssize_t i;
int res = 0;
- if (init_add_i_state(r, &s))
++ for (i = 0; i < ARRAY_SIZE(command_list); i++)
++ string_list_append(&commands, command_list[i].string)
++ ->util = command_list[i].command;
++
+ init_add_i_state(&s, r);
++
+ strbuf_addstr(&header, " ");
+ strbuf_addf(&header, print_file_item_data.modified_fmt,
+ _("staged"), _("unstaged"), _("path"));
@@
- if (run_status(&s, ps, &files, &opts) < 0)
- res = -1;
+
+ res = run_status(&s, ps, &files, &opts);
+ for (;;) {
-+ i = list_and_choose((struct item **)commands,
-+ ARRAY_SIZE(commands), &s, &main_loop_opts);
++ i = list_and_choose(&s, &commands, &main_loop_opts);
+ if (i == LIST_AND_CHOOSE_QUIT) {
+ printf(_("Bye.\n"));
+ res = 0;
+ break;
+ }
-+ if (i != LIST_AND_CHOOSE_ERROR)
-+ res = commands[i]->command(&s, ps, &files, &opts);
++ if (i != LIST_AND_CHOOSE_ERROR) {
++ command_t command = commands.items[i].util;
++ res = command(&s, ps, &files, &opts);
++ }
+ }
+
- release_file_list(&files);
+ string_list_clear(&files, 1);
strbuf_release(&print_file_item_data.buf);
strbuf_release(&print_file_item_data.index);
+ strbuf_release(&print_file_item_data.worktree);
+ strbuf_release(&header);
++ string_list_clear(&commands, 0);
+
+ return res;
+ }
7: c5a699b6b2 < -: ---------- Add a function to determine unique prefixes for a list of strings
8: bc7a74f697 ! 6: 57fdc01463 built-in add -i: show unique prefixes of the commands
@@ -1,4 +1,4 @@
-Author: Slavica Djukic <[email protected]>
+Author: Johannes Schindelin <[email protected]>
built-in add -i: show unique prefixes of the commands
@@ -7,97 +7,197 @@
given parameters), and shown in the list, and accepted as a shortcut for
the command.
- We use the prefix map implementation that we just added in the previous
- commit for that purpose.
+ To determine the unique prefixes, as well as to look up the command in
+ question, we use a copy of the list and sort it.
- Signed-off-by: Slavica Djukic <[email protected]>
+ While this might seem like overkill for a single command, it will make
+ much more sense when all the commands are implemented, and when we reuse
+ the same logic to present a list of files to edit, with convenient
+ unique prefixes.
+
+ At the start of the development of this patch series, a dedicated data
+ structure was introduced that imitated the Trie that the Perl version
+ implements. However, this was deemed overkill, and we now simply sort
+ the list before determining the length of the unique prefixes by looking
+ at each item's neighbor. As a bonus, we now use the same sorted list to
+ perform a binary search using the user-provided prefix as search key.
+
+ Original-patch-by: Slavica Đukić <[email protected]>
+ Helped-by: SZEDER Gábor <[email protected]>
Signed-off-by: Johannes Schindelin <[email protected]>
diff --git a/add-interactive.c b/add-interactive.c
--- a/add-interactive.c
+++ b/add-interactive.c
@@
- #include "diffcore.h"
- #include "revision.h"
- #include "refs.h"
-+#include "prefix-map.h"
-
- struct add_i_state {
- struct repository *r;
-@@
- return 0;
+ init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
}
--struct item {
-- const char *name;
--};
-+static ssize_t find_unique(const char *string,
-+ struct prefix_item **list, size_t nr)
++/*
++ * A "prefix item list" is a list of items that are identified by a string, and
++ * a unique prefix (if any) is determined for each item.
++ *
++ * It is implemented in the form of a pair of `string_list`s, the first one
++ * duplicating the strings, with the `util` field pointing at a structure whose
++ * first field must be `size_t prefix_length`.
++ *
++ * That `prefix_length` field will be computed by `find_unique_prefixes()`; It
++ * will be set to zero if no valid, unique prefix could be found.
++ *
++ * The second `string_list` is called `sorted` and does _not_ duplicate the
++ * strings but simply reuses the first one's, with the `util` field pointing at
++ * the `string_item_list` of the first `string_list`. It will be populated and
++ * sorted by `find_unique_prefixes()`.
++ */
++struct prefix_item_list {
++ struct string_list items;
++ struct string_list sorted;
++ size_t min_length, max_length;
++};
++#define PREFIX_ITEM_LIST_INIT \
++ { STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, 1, 4 }
++
++static void prefix_item_list_clear(struct prefix_item_list *list)
++{
++ string_list_clear(&list->items, 1);
++ string_list_clear(&list->sorted, 0);
++}
++
++static void extend_prefix_length(struct string_list_item *p,
++ const char *other_string, size_t max_length)
+{
-+ ssize_t found = -1, i;
-+
-+ for (i = 0; i < nr; i++) {
-+ struct prefix_item *item = list[i];
-+ if (!starts_with(item->name, string))
-+ continue;
-+ if (found >= 0)
-+ return -1;
-+ found = i;
++ size_t *len = p->util;
++
++ if (!*len || memcmp(p->string, other_string, *len))
++ return;
++
++ for (;;) {
++ char c = p->string[*len];
++
++ /*
++ * Is `p` a strict prefix of `other`? Or have we exhausted the
++ * maximal length of the prefix? Or is the current character a
++ * multi-byte UTF-8 one? If so, there is no valid, unique
++ * prefix.
++ */
++ if (!c || ++*len > max_length || !isascii(c)) {
++ *len = 0;
++ break;
++ }
++
++ if (c != other_string[*len - 1])
++ break;
+ }
++}
+
-+ return found;
++static void find_unique_prefixes(struct prefix_item_list *list)
++{
++ size_t i;
++
++ if (list->sorted.nr == list->items.nr)
++ return;
++
++ string_list_clear(&list->sorted, 0);
++ /* Avoid reallocating incrementally */
++ list->sorted.items = xmalloc(st_mult(sizeof(*list->sorted.items),
++ list->items.nr));
++ list->sorted.nr = list->sorted.alloc = list->items.nr;
++
++ for (i = 0; i < list->items.nr; i++) {
++ list->sorted.items[i].string = list->items.items[i].string;
++ list->sorted.items[i].util = list->items.items + i;
++ }
++
++ string_list_sort(&list->sorted);
++
++ for (i = 0; i < list->sorted.nr; i++) {
++ struct string_list_item *sorted_item = list->sorted.items + i;
++ struct string_list_item *item = sorted_item->util;
++ size_t *len = item->util;
++
++ *len = 0;
++ while (*len < list->min_length) {
++ char c = item->string[(*len)++];
++
++ if (!c || !isascii(c)) {
++ *len = 0;
++ break;
++ }
++ }
++
++ if (i > 0)
++ extend_prefix_length(item, sorted_item[-1].string,
++ list->max_length);
++ if (i + 1 < list->sorted.nr)
++ extend_prefix_length(item, sorted_item[1].string,
++ list->max_length);
++ }
+}
-
++
++static ssize_t find_unique(const char *string, struct prefix_item_list *list)
++{
++ int index = string_list_find_insert_index(&list->sorted, string, 1);
++ struct string_list_item *item;
++
++ if (list->items.nr != list->sorted.nr)
++ BUG("prefix_item_list in inconsistent state (%"PRIuMAX
++ " vs %"PRIuMAX")",
++ (uintmax_t)list->items.nr, (uintmax_t)list->sorted.nr);
++
++ if (index < 0)
++ item = list->sorted.items[-1 - index].util;
++ else if (index > 0 &&
++ starts_with(list->sorted.items[index - 1].string, string))
++ return -1;
++ else if (index + 1 < list->sorted.nr &&
++ starts_with(list->sorted.items[index + 1].string, string))
++ return -1;
++ else if (index < list->sorted.nr)
++ item = list->sorted.items[index].util;
++ else
++ return -1;
++ return item - list->items.items;
++}
++
struct list_options {
int columns;
const char *header;
-- void (*print_item)(int i, struct item *item, void *print_item_data);
-+ void (*print_item)(int i, struct prefix_item *item,
-+ void *print_item_data);
- void *print_item_data;
- };
-
--static void list(struct item **list, size_t nr,
-+static void list(struct prefix_item **list, size_t nr,
- struct add_i_state *s, struct list_options *opts)
- {
- int i, last_lf = 0;
@@
* If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
* `LIST_AND_CHOOSE_QUIT` is returned.
*/
--static ssize_t list_and_choose(struct item **items, size_t nr,
-+static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
- struct add_i_state *s,
+-static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
++static ssize_t list_and_choose(struct add_i_state *s,
++ struct prefix_item_list *items,
struct list_and_choose_options *opts)
{
struct strbuf input = STRBUF_INIT;
ssize_t res = LIST_AND_CHOOSE_ERROR;
-+ find_unique_prefixes(items, nr, 1, 4);
++ find_unique_prefixes(items);
+
for (;;) {
char *p, *endp;
+ strbuf_reset(&input);
+
+- list(s, items, &opts->list_opts);
++ list(s, &items->items, &opts->list_opts);
+
+ printf("%s%s", opts->prompt, "> ");
+ fflush(stdout);
@@
}
p[sep] = '\0';
+- if (index < 0 || index >= items->nr)
+ if (index < 0)
-+ index = find_unique(p, items, nr);
++ index = find_unique(p, items);
+
- if (index < 0 || index >= nr)
++ if (index < 0 || index >= items->items.nr)
printf(_("Huh (%s)?\n"), p);
else {
-@@
-
- struct file_list {
- struct file_item {
-- struct item item;
-+ struct prefix_item item;
- struct adddel index, worktree;
- } **file;
- size_t nr, alloc;
+ res = index;
@@
strbuf_addstr(buf, no_changes);
}
@@ -122,50 +222,74 @@
struct print_file_item_data {
const char *modified_fmt;
struct strbuf buf, index, worktree;
- };
-
--static void print_file_item(int i, struct item *item,
-+static void print_file_item(int i, struct prefix_item *item,
- void *print_file_item_data)
- {
- struct file_item *c = (struct file_item *)item;
@@
- return -1;
-
- if (files->nr)
-- list((struct item **)files->file, files->nr, s, opts);
-+ list((struct prefix_item **)files->file, files->nr, s, opts);
- putchar('\n');
+ struct string_list *files,
+ struct list_options *opts);
- return 0;
- }
-
--static void print_command_item(int i, struct item *item,
-+static void print_command_item(int i, struct prefix_item *item,
++struct command_item {
++ size_t prefix_length;
++ command_t command;
++};
++
+ static void print_command_item(int i, struct string_list_item *item,
void *print_command_item_data)
{
-- printf(" %2d: %s", i + 1, item->name);
-+ if (!item->prefix_length ||
-+ !is_valid_prefix(item->name, item->prefix_length))
-+ printf(" %2d: %s", i + 1, item->name);
+- printf(" %2d: %s", i + 1, item->string);
++ struct command_item *util = item->util;
++
++ if (!util->prefix_length ||
++ !is_valid_prefix(item->string, util->prefix_length))
++ printf(" %2d: %s", i + 1, item->string);
+ else
-+ printf(" %3d: [%.*s]%s", i + 1,
-+ (int)item->prefix_length, item->name,
-+ item->name + item->prefix_length);
++ printf(" %2d: [%.*s]%s", i + 1,
++ (int)util->prefix_length, item->string,
++ item->string + util->prefix_length);
}
- struct command_item {
-- struct item item;
-+ struct prefix_item item;
- int (*command)(struct add_i_state *s, const struct pathspec *ps,
- struct file_list *files, struct list_options *opts);
- };
+ int run_add_i(struct repository *r, const struct pathspec *ps)
@@
- res = -1;
+ } command_list[] = {
+ { "status", run_status },
+ };
+- struct string_list commands = STRING_LIST_INIT_NODUP;
++ struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
- for (;;) {
-- i = list_and_choose((struct item **)commands,
-+ i = list_and_choose((struct prefix_item **)commands,
- ARRAY_SIZE(commands), &s, &main_loop_opts);
- if (i == LIST_AND_CHOOSE_QUIT) {
- printf(_("Bye.\n"));
+ struct print_file_item_data print_file_item_data = {
+ "%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+@@
+ ssize_t i;
+ int res = 0;
+
+- for (i = 0; i < ARRAY_SIZE(command_list); i++)
+- string_list_append(&commands, command_list[i].string)
+- ->util = command_list[i].command;
++ for (i = 0; i < ARRAY_SIZE(command_list); i++) {
++ struct command_item *util = xcalloc(sizeof(*util), 1);
++ util->command = command_list[i].command;
++ string_list_append(&commands.items, command_list[i].string)
++ ->util = util;
++ }
+
+ init_add_i_state(&s, r);
+
+@@
+ break;
+ }
+ if (i != LIST_AND_CHOOSE_ERROR) {
+- command_t command = commands.items[i].util;
+- res = command(&s, ps, &files, &opts);
++ struct command_item *util =
++ commands.items.items[i].util;
++ res = util->command(&s, ps, &files, &opts);
+ }
+ }
+
+@@
+ strbuf_release(&print_file_item_data.index);
+ strbuf_release(&print_file_item_data.worktree);
+ strbuf_release(&header);
+- string_list_clear(&commands, 0);
++ prefix_item_list_clear(&commands);
+
+ return res;
+ }
9: 74f73e26b4 ! 7: 77ad5f333a built-in add -i: support `?` (prompt help)
@@ -24,9 +24,9 @@
init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
+ init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
-
- return 0;
}
+
+ /*
@@
struct list_options list_opts;
@@ -48,8 +48,8 @@
for (;;) {
size_t sep = strcspn(p, " \t\r\n,");
@@
- struct file_list *files, struct list_options *opts);
- };
+ item->string + util->prefix_length);
+ }
+static void command_prompt_help(struct add_i_state *s)
+{
@@ -71,5 +71,5 @@
- N_("What now")
+ N_("What now"), command_prompt_help
};
- struct command_item
- status = { { "status" }, run_status };
+ struct {
+ const char *string;
10: 88001009bc ! 8: 3d0b172a7f built-in add -i: use color in the main loop
@@ -1,11 +1,11 @@
-Author: Slavica Djukic <[email protected]>
+Author: Slavica Đukić <[email protected]>
built-in add -i: use color in the main loop
The error messages as well as the unique prefixes are colored in `git
add -i` by default; We need to do the same in the built-in version.
- Signed-off-by: Slavica Djukic <[email protected]>
+ Signed-off-by: Slavica Đukić <[email protected]>
Signed-off-by: Johannes Schindelin <[email protected]>
diff --git a/add-interactive.c b/add-interactive.c
@@ -28,12 +28,12 @@
+ init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE);
+ init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED);
+ init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET);
-
- return 0;
}
+
+ /*
@@
- list(items, nr, s, &opts->list_opts);
+ list(s, &items->items, &opts->list_opts);
- printf("%s%s", opts->prompt, "> ");
+ color_fprintf(stdout, s->prompt_color, "%s", opts->prompt);
@@ -42,9 +42,9 @@
if (strbuf_getline(&input, stdin) == EOF) {
@@
- index = find_unique(p, items, nr);
+ index = find_unique(p, items);
- if (index < 0 || index >= nr)
+ if (index < 0 || index >= items->items.nr)
- printf(_("Huh (%s)?\n"), p);
+ color_fprintf_ln(stdout, s->error_color,
+ _("Huh (%s)?"), p);
@@ -52,43 +52,46 @@
res = index;
break;
@@
- return 0;
- }
+ command_t command;
+ };
+struct print_command_item_data {
+ const char *color, *reset;
+};
+
- static void print_command_item(int i, struct prefix_item *item,
+ static void print_command_item(int i, struct string_list_item *item,
void *print_command_item_data)
{
+ struct print_command_item_data *d = print_command_item_data;
-+
- if (!item->prefix_length ||
- !is_valid_prefix(item->name, item->prefix_length))
- printf(" %2d: %s", i + 1, item->name);
+ struct command_item *util = item->util;
+
+ if (!util->prefix_length ||
+ !is_valid_prefix(item->string, util->prefix_length))
+ printf(" %2d: %s", i + 1, item->string);
else
-- printf(" %3d: [%.*s]%s", i + 1,
-- (int)item->prefix_length, item->name,
+- printf(" %2d: [%.*s]%s", i + 1,
+- (int)util->prefix_length, item->string,
+- item->string + util->prefix_length);
+ printf(" %2d: %s%.*s%s%s", i + 1,
-+ d->color, (int)item->prefix_length, item->name, d->reset,
- item->name + item->prefix_length);
++ d->color, (int)util->prefix_length, item->string,
++ d->reset, item->string + util->prefix_length);
}
+ static void command_prompt_help(struct add_i_state *s)
@@
int run_add_i(struct repository *r, const struct pathspec *ps)
{
struct add_i_state s = { NULL };
-+ struct print_command_item_data data;
++ struct print_command_item_data data = { "[", "]" };
struct list_and_choose_options main_loop_opts = {
- { 4, N_("*** Commands ***"), print_command_item, NULL },
+ { 4, N_("*** Commands ***"), print_command_item, &data },
N_("What now"), command_prompt_help
};
- struct command_item
+ struct {
@@
- if (init_add_i_state(r, &s))
- return error("could not parse `add -i` config");
+
+ init_add_i_state(&s, r);
+ /*
+ * When color was asked for, use the prompt color for
@@ -97,9 +100,6 @@
+ if (s.use_color) {
+ data.color = s.prompt_color;
+ data.reset = s.reset_color;
-+ } else {
-+ data.color = "[";
-+ data.reset = "]";
+ }
+
strbuf_addstr(&header, " "); 11: b27fbe289f ! 9: 85e508ef11 built-in add -i: implement the help command
@@ -1,4 +1,4 @@
-Author: Johannes Schindelin <[email protected]> +Author: Slavica Đukić <[email protected]>
built-in add -i: implement the help command
@@ -8,7 +8,7 @@
To make sure that it renders exactly like the Perl version of git add -i, we also add a test case for that to t3701-add-interactive.sh.
- Signed-off-by: Slavica Djukic <[email protected]>
+ Signed-off-by: Slavica Đukić <[email protected]>
diff --git a/add-interactive.c b/add-interactive.c
@@ -19,7 +19,7 @@
}
+static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,
-+ struct file_list *unused_files,
++ struct string_list *unused_files,
+ struct list_options *unused_opts)
+{
+ color_fprintf_ln(stdout, s->help_color, "status - %s",
@@ -38,23 +38,17 @@
+ return 0;
+}
+
- struct print_command_item_data {
- const char *color, *reset;
- };
+ typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
+ struct string_list *files,
+ struct list_options *opts);
@@
- N_("What now"), command_prompt_help
- };
- struct command_item
-- status = { { "status" }, run_status };
-+ status = { { "status" }, run_status },
-+ help = { { "help" }, run_help };
- struct command_item *commands[] = {
-- &status
-+ &status,
-+ &help
+ command_t command;
+ } command_list[] = {
+ { "status", run_status },
++ { "help", run_help },
};
+ struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
- struct print_file_item_data print_file_item_data = {
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh --- a/t/t3701-add-interactive.sh
Signature · 1 line
gitgitgadget
PATCH v5 5/9 built-in add -i: implement the main loop
From: 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.
Apart from the "and choose" part, there are more differences between the way the status command calls the list_and_choose() function in the Perl version of git add -i compared to the other callers of said function. The most important ones:
- The list is not only shown, but the user is also asked to make a choice, possibly selecting multiple entries.
- The list of items is prefixed with a marker indicating what items have been selected, if multi-selection is allowed.
- Initially, for each item a unique prefix (if there exists any within the given parameters) is determined, and shown in the list, and accepted as a shortcut for the selection.
These features will be implemented later, except the part where the user can choose a command. At this stage, though, the built-in git add -i still only supports the status command, with the remaining commands to follow over the course of the next commits.
In addition, we also modify list() to support displaying the commands in columns, even if there is currently only one.
The Perl script git-add--interactive.perl mixed the purposes of the "list" and the "and choose" part into the same function. In the C version, we will keep them separate instead, calling the list() function from the list_and_choose() function.
Note that we only have a prompt ending in a single ">" at this stage; later commits will add commands that display a double ">>" to indicate that the user is in a different loop than the main one.
--- add-interactive.c | 135 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 2 deletions(-)
add-interactive.c +133 -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,96 @@ static void list(struct add_i_state *s, struct string_list *list,for (i =0 ; i < list->nr; i++) { opts->print_item (i, list->items + i, opts->print_item_data); + +if ((opts->columns) && ((i +1 ) % (opts->columns))) { +putchar ('\t' ); + last_lf =0 ; + } +else { +putchar ('\n' ); + last_lf =1 ; + } + } + +if (!last_lf)putchar ('\n' ); +} +struct list_and_choose_options { +struct list_options list_opts; + +const char *prompt; +}; + +#define LIST_AND_CHOOSE_ERROR (-1) +#define LIST_AND_CHOOSE_QUIT (-2) + +/* +* Returns the selected index. +* +* If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF, +* `LIST_AND_CHOOSE_QUIT` is returned. +*/ +static ssize_t list_and_choose (struct add_i_state *s,struct string_list *items, +struct list_and_choose_options *opts) +{ +struct strbuf input =STRBUF_INIT ; +ssize_t res =LIST_AND_CHOOSE_ERROR ; + +for (;;) { +char *p, *endp; + +strbuf_reset (&input); + +list (s, items, &opts->list_opts); + +printf ("%s%s" , opts->prompt,"> " ); +fflush (stdout); + +if (strbuf_getline (&input, stdin) ==EOF ) { +putchar ('\n' ); + res =LIST_AND_CHOOSE_QUIT ; +break ; + } +strbuf_trim (&input); + +if (!input.len) +break ; + + p = input.buf; +for (;;) { +size_t sep =strcspn (p," \t\r\n," ); +ssize_t index =-1 ; + +if (!sep) { +if (!*p) +break ; + p++; +continue ; + } + +if (isdigit (*p)) { + index =strtoul (p, &endp,10 ) -1 ; +if (endp != p + sep) + index =-1 ; + } + + p[sep] ='\0' ; +if (index <0 || index >= items->nr) +printf (_ ("Huh (%s)?\n" ), p); +else { + res = index; +break ; + } + + p += sep +1 ; + } + +if (res !=LIST_AND_CHOOSE_ERROR ) +break ; } + +strbuf_release (&input); +return res; }struct adddel {
@@ -252,20 +341,48 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return 0 ; } +typedef int (*command_t )(struct add_i_state *s,const struct pathspec *ps, +struct string_list *files, +struct list_options *opts); + +static void print_command_item (int i,struct string_list_item *item, +void *print_command_item_data) +{ +printf (" %2d: %s" , i +1 , item->string); +} +int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL }; +struct list_and_choose_options main_loop_opts = { + {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, +N_ ("What now" ) + }; +struct { +const char *string; +command_t command; + } command_list[] = { + {"status" , run_status }, + }; +struct string_list commands =STRING_LIST_INIT_NODUP ; +struct print_file_item_data print_file_item_data = {"%12s %12s %s" ,STRBUF_INIT ,STRBUF_INIT ,STRBUF_INIT };struct list_options opts = { -NULL , print_file_item, &print_file_item_data +0 ,NULL , print_file_item, &print_file_item_data };struct strbuf header =STRBUF_INIT ;struct string_list files =STRING_LIST_INIT_DUP ; +ssize_t i;int res =0 ; +for (i =0 ; i <ARRAY_SIZE (command_list); i++) +string_list_append (&commands, command_list[i].string) + ->util = command_list[i].command; +init_add_i_state (&s, r); +strbuf_addstr (&header," " );strbuf_addf (&header, print_file_item_data.modified_fmt,_ ("staged" ),_ ("unstaged" ),_ ("path" ));
@@ -279,11 +396,25 @@ int run_add_i(struct repository *r, const struct pathspec *ps) res =run_status (&s, ps, &files, &opts); +for (;;) { + i =list_and_choose (&s, &commands, &main_loop_opts); +if (i ==LIST_AND_CHOOSE_QUIT ) { +printf (_ ("Bye.\n" )); + res =0 ; +break ; + } +if (i !=LIST_AND_CHOOSE_ERROR ) { +command_t command = commands.items[i].util; + res =command (&s, ps, &files, &opts); + } + } +string_list_clear (&files,1 );strbuf_release (&print_file_item_data.buf);strbuf_release (&print_file_item_data.index);strbuf_release (&print_file_item_data.worktree);strbuf_release (&header); +string_list_clear (&commands,0 );return res; }
Signature · 1 line
gitgitgadget
PATCH v5 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," );
@@ -513,12 +521,24 @@ static void print_command_item(int i, struct string_list_item *item, item->string + util->prefix_length); } +static void command_prompt_help (struct add_i_state *s) +{ +const char *help_color = s->help_color; +color_fprintf_ln (stdout, help_color,"%s" ,_ ("Prompt help:" )); +color_fprintf_ln (stdout, help_color,"1 - %s" , +_ ("select a numbered item" )); +color_fprintf_ln (stdout, help_color,"foo - %s" , +_ ("select item based on unique prefix" )); +color_fprintf_ln (stdout, help_color," - %s" , +_ ("(empty) select nothing" )); +} +int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL };struct list_and_choose_options main_loop_opts = { {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, -N_ ("What now" ) +N_ ("What now" ), command_prompt_help };struct {const char *string;
Signature · 1 line
gitgitgadget
PATCH v5 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 ) {
@@ -281,7 +288,8 @@ static ssize_t list_and_choose(struct add_i_state *s, index =find_unique (p, items);if (index <0 || index >= items->items.nr) -printf (_ ("Huh (%s)?\n" ), p); +color_fprintf_ln (stdout, s->error_color, +_ ("Huh (%s)?" ), p);else { res = index;break ;
@@ -507,18 +515,23 @@ struct command_item {command_t command; }; +struct print_command_item_data { +const char *color, *reset; +}; +static void print_command_item (int i,struct string_list_item *item,void *print_command_item_data) { +struct print_command_item_data *d = print_command_item_data;struct command_item *util = item->util;if (!util->prefix_length || !is_valid_prefix (item->string, util->prefix_length))printf (" %2d: %s" , i +1 , item->string);else -printf (" %2d: [%.*s]%s" , i +1 , - (int )util->prefix_length, item->string, - item->string + util->prefix_length); +printf (" %2d: %s%.*s%s%s" , i +1 , + d->color, (int )util->prefix_length, item->string, + d->reset, item->string + util->prefix_length); }static void command_prompt_help (struct add_i_state *s)
@@ -536,8 +549,9 @@ static void command_prompt_help(struct add_i_state *s)int run_add_i (struct repository *r,const struct pathspec *ps) {struct add_i_state s = {NULL }; +struct print_command_item_data data = {"[" ,"]" };struct list_and_choose_options main_loop_opts = { - {4 ,N_ ("*** Commands ***" ), print_command_item,NULL }, + {4 ,N_ ("*** Commands ***" ), print_command_item, &data },N_ ("What now" ), command_prompt_help };struct {
@@ -568,6 +582,15 @@ int run_add_i(struct repository *r, const struct pathspec *ps)init_add_i_state (&s, r); +/* +* When color was asked for, use the prompt color for +* highlighting, otherwise use square brackets. +*/ +if (s.use_color) { + data.color = s.prompt_color; + data.reset = s.reset_color; + } +strbuf_addstr (&header," " );strbuf_addf (&header, print_file_item_data.modified_fmt,_ ("staged" ),_ ("unstaged" ),_ ("path" ));
Signature · 1 line
gitgitgadget
PATCH v5 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, *endp;strbuf_reset (&input); -list (s, items, &opts->list_opts); +list (s, &items->items, &opts->list_opts);printf ("%s%s" , opts->prompt,"> " );fflush (stdout);
@@ -140,7 +269,10 @@ static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items, } p[sep] ='\0' ; -if (index <0 || index >= items->nr) +if (index <0 ) + index =find_unique (p, items); + +if (index <0 || index >= items->items.nr)printf (_ ("Huh (%s)?\n" ), p);else { res = index;
@@ -306,6 +438,23 @@ static void render_adddel(struct strbuf *buf,strbuf_addstr (buf, no_changes); } +/* filters out prefixes which have special meaning to list_and_choose() */ +static int is_valid_prefix (const char *prefix,size_t prefix_len) +{ +return prefix_len && prefix && +/* +* We expect `prefix` to be NUL terminated, therefore this +* `strcspn()` call is okay, even if it might do much more +* work than strictly necessary. +*/ +strcspn (prefix," \t\r\n," ) >= prefix_len &&/* separators */ + *prefix !='-' &&/* deselection */ + !isdigit (*prefix) &&/* selection */ + (prefix_len !=1 || + (*prefix !='*' &&/* "all" wildcard */ + *prefix !='?' ));/* prompt help */ +} +struct print_file_item_data {const char *modified_fmt;struct strbuf buf, index, worktree;
@@ -345,10 +494,23 @@ typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,struct string_list *files, struct list_options *opts); +struct command_item { +size_t prefix_length; +command_t command; +}; +static void print_command_item (int i,struct string_list_item *item,void *print_command_item_data) { -printf (" %2d: %s" , i +1 , item->string); +struct command_item *util = item->util; + +if (!util->prefix_length || + !is_valid_prefix (item->string, util->prefix_length)) +printf (" %2d: %s" , i +1 , item->string); +else +printf (" %2d: [%.*s]%s" , i +1 , + (int )util->prefix_length, item->string, + item->string + util->prefix_length); }int run_add_i (struct repository *r,const struct pathspec *ps)
@@ -364,7 +526,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps) } command_list[] = { { "status", run_status }, }; -struct string_list commands =STRING_LIST_INIT_NODUP ; +struct prefix_item_list commands =PREFIX_ITEM_LIST_INIT ;struct print_file_item_data print_file_item_data = { "%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
@@ -377,9 +539,12 @@ int run_add_i(struct repository *r, const struct pathspec *ps)ssize_t i;int res =0 ; -for (i =0 ; i <ARRAY_SIZE (command_list); i++) -string_list_append (&commands, command_list[i].string) - ->util = command_list[i].command; +for (i =0 ; i <ARRAY_SIZE (command_list); i++) { +struct command_item *util =xcalloc (sizeof (*util),1 ); + util->command = command_list[i].command; +string_list_append (&commands.items, command_list[i].string) + ->util = util; + }init_add_i_state (&s, r);
@@ -404,8 +569,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps)break ; }if (i !=LIST_AND_CHOOSE_ERROR ) { -command_t command = commands.items[i].util; - res =command (&s, ps, &files, &opts); +struct command_item *util = + commands.items.items[i].util; + res = util->command (&s, ps, &files, &opts); } }
@@ -414,7 +580,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)strbuf_release (&print_file_item_data.index);strbuf_release (&print_file_item_data.worktree);strbuf_release (&header); -string_list_clear (&commands,0 ); +prefix_item_list_clear (&commands);return res; }
Signature · 1 line
gitgitgadget
PATCH v5 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
@@ -506,6 +506,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps,return 0 ; } +static int run_help (struct add_i_state *s,const struct pathspec *unused_ps, +struct string_list *unused_files, +struct list_options *unused_opts) +{ +color_fprintf_ln (stdout, s->help_color,"status - %s" , +_ ("show paths with changes" )); +color_fprintf_ln (stdout, s->help_color,"update - %s" , +_ ("add working tree state to the staged set of changes" )); +color_fprintf_ln (stdout, s->help_color,"revert - %s" , +_ ("revert staged set of changes back to the HEAD version" )); +color_fprintf_ln (stdout, s->help_color,"patch - %s" , +_ ("pick hunks and update selectively" )); +color_fprintf_ln (stdout, s->help_color,"diff - %s" , +_ ("view diff between HEAD and index" )); +color_fprintf_ln (stdout, s->help_color,"add untracked - %s" , +_ ("add contents of untracked files to the staged set of changes" )); + +return 0 ; +} +typedef int (*command_t )(struct add_i_state *s,const struct pathspec *ps,struct string_list *files,struct list_options *opts);
@@ -559,6 +579,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)command_t command; } command_list[] = { { "status", run_status }, + { "help", run_help }, }; struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
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
Re: [PATCH v5 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 9 lines · writes:
From: Johannes Schindelin <[email protected]>
This is hardly the first conversion of a Git command that is implemented as a script to a built-in. So far, the most successful strategy for such conversions has been to add a built-in helper and call that for more and more functionality from the script, as more and more parts are converted.
With the interactive add, we choose a different strategy....
This is hardly the first conversion that we took the "build the whole program piece by piece and flip the whole thing on with usebuiltin" conversion successfully. Pratik's rebase-in-c series comes to mind.
Personally, I do not think the first two paragraphs of the proposed log message do not belong here. Cover letter is a different story and it may make sense to explain why the approach was taken there, but here, I'd prefer to see it more succinctly tell what approach is taken and go directly to describe what this step in that approach does to the readers, which is more important.
Documentation/config/add.txt add.ignore-errors (deprecated):: · line 12
Quoted 7 lines
Documentation/config/add.txt +5
-0variables. + +add.interactive.useBuiltin:: + [EXPERIMENTAL] Set to `true` to use the experimental built-in + implementation of the interactive version of linkgit:git-add[1] + instead of the Perl script version. Is `false` by default.
Good.
Makefile LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentat · line 829
Quoted 9 lines
Makefile +1
-0LIB_OBJS += abspath.o +LIB_OBJS += add-interactive.o LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.o
OK.
add-interactive.c line 7
Quoted 13 lines
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!" )); +}
OK, with or without s/commands/sub&/;
add-interactive.h
Quoted 5 lines
add-interactive.h new file +0
-0
OK.
Quoted 13 lines
builtin/add.c +1
-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>..." ),
builtin/add.c int run_add_interactive(const char *revision, const char *patch_mode, · line 190
Quoted 5 lines
builtin/add.c +2
-0@@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode, {int status, i;struct argv_array argv =ARGV_ARRAY_INIT ; +int use_builtin_add_i = +git_env_bool ("GIT_TEST_ADD_I_USE_BUILTIN" ,-1 );
Have blank here at the boundary between decl and stmt ...
builtin/add.c int run_add_interactive(const char *revision, const char *patch_mode, · line 194
Quoted 4 lines
builtin/add.c +4
-0+if (use_builtin_add_i <0 ) +git_config_get_bool ("add.interactive.usebuiltin" , + &use_builtin_add_i); +
... and lose it here (optional).
builtin/add.c int run_add_interactive(const char *revision, const char *patch_mode, · line 197
Quoted 3 lines
builtin/add.c +2
-0+if (use_builtin_add_i ==1 && !patch_mode) +return !!run_add_i (the_repository, pathspec);
Strictly speaking, we can bypass the probing of environment and config when upon the entry of the function, where patch_mode is already known. I do not know offhand if rearranging the code to take advantage of that fact would result in a flow that is also easier to follow, but I suspect it would.
builtin/add.c int run_add_interactive(const char *revision, const char *patch_mode,
Quoted 3 lines
builtin/add.c +3
-0+GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the +built-in version of git add -i. See 'add.interactive.useBuiltin' in +git-config(1).
Makes sense.
Thanks.
Re: [PATCH v5 2/9] diff: export diffstat interface
In reply to Daniel Ferreira via GitGitGadgetQuoted 11 lines · Daniel Ferreira via GitGitGadget
From: Daniel Ferreira <[email protected]>
Make the diffstat interface (namely, the
diffstat_tstruct andcompute_diffstat) no longer be internal todiff.cand 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.
Quoted 7 lines
Signed-off-by Daniel Ferreira <[email protected]>Signed-off-by Slavica Đukić <[email protected]>Signed-off-by Johannes Schindelin <[email protected]>--- diff.c | 39 ++++++++++++++++----------------------- diff.h | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 23 deletions(-)
Looks good. The diffstat API seems to be quite well isolated to allow this with minimum (eh, rather no) changes.
Thanks.
Quoted 109 lines
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
Re: [PATCH v5 3/9] built-in add -i: implement the `status` command
In reply to Daniel Ferreira via GitGitGadgetQuoted 9 lines · Daniel Ferreira via GitGitGadget
From: Daniel Ferreira <[email protected]>
This implements the
statuscommand ofgit 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 scriptgit-add--interactive.perland call itlist(). It does not yet color anything, or do columns, or allow user input.
OK, so that is why we unconditionally do the status thing directly inside run_add_i() and nothing else. Makes sense.
Quoted 5 lines
Over the course of the next commits, we will introduce a
list_and_choose()function that useslist()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-ingit add -i, at which point the newstatuscommand can actually be used.
Very well written.
Re: [PATCH v5 5/9] built-in add -i: implement the main loop
In reply to Johannes Schindelin via GitGitGadget"Johannes Schindelin via GitGitGadget" <[email protected]>
Quoted 7 lines · writes:
From: 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 thelist()function that we conveniently introduced for use by thestatuscommand.
Quoted 7 lines
Apart from the "and choose" part, there are more differences between the way the
statuscommand calls thelist_and_choose()function in the Perl version ofgit add -icompared to the other callers of said function. The most important ones:- The list is not only shown, but the user is also asked to make a choice, possibly selecting multiple entries.
The list_and_choose() we have here shows and lets users choose and returns the choice, but the above makes it sound as if it only shows and the caller is responsible for asking the end-user input. Is this description outdated or something?
Perl allows us to return multiple choices, where it is a bit hard to express it in C (perhaps because we are passing in an array of structs to be shown as choices, list_and_choose could set a bit in these structs to signal "this one, that one and that other one was chosen", returning how many are chosen in total, to extend the version here to bring it to feature-parity?). So at this step, it only lets the user one choice (or abort or ask for help). Isn't the lack of multiple choice the only difference this bullet item wants to highlight?
Quoted 4 lines
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.
That makes sense.
add-interactive.c static void list(struct add_i_state *s, struct string_list *list, · line 105
Quoted 8 lines
add-interactive.c +8
-0+static ssize_t list_and_choose (struct add_i_state *s,struct string_list *items, +struct list_and_choose_options *opts) +{ +struct strbuf input =STRBUF_INIT ; +ssize_t res =LIST_AND_CHOOSE_ERROR ; + +for (;;) { +char *p, *endp;
The scope of endp looks way too wide in this function, isn't it? Even in the final state of the series, it only gets used to parse an integer input using strtoul, inside a block of three lines.
Other than that, the code at this step was a pleasant read overall.
Thanks.
Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
In reply to Junio C HamanoHi Junio,
Quoted 18 lines · Junio C Hamano
"Johannes Schindelin via GitGitGadget" <[email protected]>
Quoted 9 lines · writes:
From: Johannes Schindelin <[email protected]>
This is hardly the first conversion of a Git command that is implemented as a script to a built-in. So far, the most successful strategy for such conversions has been to add a built-in helper and call that for more and more functionality from the script, as more and more parts are converted.
With the interactive add, we choose a different strategy....
This is hardly the first conversion that we took the "build the whole program piece by piece and flip the whole thing on with usebuiltin" conversion successfully. Pratik's rebase-in-c series comes to mind.
Quoted 6 lines
Personally, I do not think the first two paragraphs of the proposed log message do not belong here. Cover letter is a different story and it may make sense to explain why the approach was taken there, but here, I'd prefer to see it more succinctly tell what approach is taken and go directly to describe what this step in that approach does to the readers, which is more important.
I reworded the commit message:
Start to implement a built-in version of git add --interactive
To convert the interactive add to C, we start with a bare-bones version of the built-in interactive add, guarded by the new add.interactive.useBuiltin config variable, and then add more and more functionality to it, until it is feature complete.
This is in contrast to previous conversions to C, where we started with a built-in helper that spawns the script by default, but optionally executes the C code instead. The sole reason for this deviation from previous practice is that on Windows (where such a conversion has the most benefits in terms of speed and robustness) we face the very specific problem that a system() call in Perl seems to close stdin in the parent process when the spawned process consumes even one character from stdin. And that just does not work for us here, as it would stop the main loop as soon as any interactive command was performed by the helper. Which is almost all of the commands in git add -i.
It is almost as if Perl told us once again that it does not want us to use it on Windows.
At this point, the built-in version of git add -i only states that it cannot do anything yet ;-)
Hopefully you like this one better?
Quoted 21 lines
Quoted 13 lines
builtin/add.c +1
-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>..." ),builtin/add.c int run_add_interactive(const char *revision, const char *patch_mode, · line 190
Quoted 5 lines
builtin/add.c +2
-0@@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode, {int status, i;struct argv_array argv =ARGV_ARRAY_INIT ; +int use_builtin_add_i = +git_env_bool ("GIT_TEST_ADD_I_USE_BUILTIN" ,-1 );Have blank here at the boundary between decl and stmt ...
Quoted 6 lines
builtin/add.c int run_add_interactive(const char *revision, const char *patch_mode, · line 194
Quoted 4 lines
builtin/add.c +4
-0+if (use_builtin_add_i <0 ) +git_config_get_bool ("add.interactive.usebuiltin" , + &use_builtin_add_i); +... and lose it here (optional).
Done.
Quoted 10 lines
builtin/add.c int run_add_interactive(const char *revision, const char *patch_mode, · line 197
Quoted 4 lines
builtin/add.c +2
-0+if (use_builtin_add_i ==1 && !patch_mode) +return !!run_add_i (the_repository, pathspec);Strictly speaking, we can bypass the probing of environment and config when upon the entry of the function, where
patch_modeis already known. I do not know offhand if rearranging the code to take advantage of that fact would result in a flow that is also easier to follow, but I suspect it would.
Okay. I changed it to:
if (!patch_mode) {
if (use_builtin_add_i < 0)
git_config_get_bool("add.interactive.usebuiltin",
&use_builtin_add_i);
if (use_builtin_add_i == 1)
return !!run_add_i(the_repository, pathspec);
}Thanks,
Dscho
Re: [PATCH v5 5/9] built-in add -i: implement the main loop
In reply to Junio C HamanoHi Junio,
Quoted 23 lines · Junio C Hamano
"Johannes Schindelin via GitGitGadget" <[email protected]>
Quoted 7 lines · writes:
From: 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 thelist()function that we conveniently introduced for use by thestatuscommand.Quoted 7 lines
Apart from the "and choose" part, there are more differences between the way the
statuscommand calls thelist_and_choose()function in the Perl version ofgit add -icompared to the other callers of said function. The most important ones:- The list is not only shown, but the user is also asked to make a choice, possibly selecting multiple entries.
The
list_and_choose()we have here shows and lets users choose and returns the choice, but the above makes it sound as if it only shows and the caller is responsible for asking the end-user input. Is this description outdated or something?
Quoted 9 lines
Perl allows us to return multiple choices, where it is a bit hard to express it in C (perhaps because we are passing in an array of structs to be shown as choices,
list_and_choosecould set a bit in these structs to signal "this one, that one and that other one was chosen", returning how many are chosen in total, to extend the version here to bring it to feature-parity?). So at this step, it only lets the user one choice (or abort or ask for help). Isn't the lack of multiple choice the only difference this bullet item wants to highlight?
I changed the commit message to:
built-in add -i: implement the main loop
The reason why we did not start with the main loop to begin with is that it is the first user of list_and_choose(), which uses the list() function that we conveniently introduced for use by the status command.
In contrast to the Perl version, in the built-in interactive add, we will keep the list() function (which only displays items) and the list_and_choose() function (which uses list() to display the items, and only takes care of the "and choose" part) separate.
The list_and_choose() function, as implemented in git-add--interactive.perl knows a few more tricks than the function we introduce in this patch:
- There is a flag to let the user select multiple items.
- In multi-select mode, the list of items is prefixed with a marker indicating what items have been selected.
- Initially, for each item a unique prefix is determined (if there exists any within the given parameters), and shown in the list, and accepted as a shortcut for the selection.
These features will be implemented in the C version later.
This patch does not add any new main loop command, of course, the built-in git add -i still only supports the status command. The remaining commands to follow over the course of the next commits.
To accommodate for listing the commands in columns, preparing for the commands that will be implemented over the course of the next patches/patch series, we teach the list() function to do precisely that.
Note that we only have a prompt ending in a single ">" at this stage; later commits will add commands that display a double ">>" to indicate that the user is in a different loop than the main one.
Quoted 7 lines
Quoted 4 lines
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.That makes sense.
Quoted 12 lines
add-interactive.c static void list(struct add_i_state *s, struct string_list *list, · line 105
Quoted 8 lines
add-interactive.c +8
-0+static ssize_t list_and_choose (struct add_i_state *s,struct string_list *items, +struct list_and_choose_options *opts) +{ +struct strbuf input =STRBUF_INIT ; +ssize_t res =LIST_AND_CHOOSE_ERROR ; + +for (;;) { +char *p, *endp;The scope of endp looks way too wide in this function, isn't it? Even in the final state of the series, it only gets used to parse an integer input using strtoul, inside a block of three lines.
True. I moved the declaration of endp into that three-line (now four-line) scope.
Quoted 1 line
Other than that, the code at this step was a pleasant read overall.
Thank you for your review!
Dscho
Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
In reply to Johannes SchindelinQuoted 21 lines · Johannes Schindelin
I reworded the commit message:
Start to implement a built-in version of
git add --interactiveTo convert the interactive
addto C, we start with a bare-bones version of the built-in interactive add, guarded by the newadd.interactive.useBuiltinconfig variable, and then add more and more functionality to it, until it is feature complete.This is in contrast to previous conversions to C, where we started with a built-in helper that spawns the script by default, but optionally executes the C code instead. The sole reason for this deviation from previous practice is that on Windows (where such a conversion has the most benefits in terms of speed and robustness) we face the very specific problem that a
system()call in Perl seems to closestdinin the parent process when the spawned process consumes even one character fromstdin. 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.
Quoted 7 lines
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 -ionly states that it cannot do anything yet ;-)Hopefully you like this one better?
Not really. I find the "we could do the other way but we don't, and I hate Perl" totally irrelevant and misleading.
Unless it is in GSoC or something that wants to avoid a total failure of nothing to show at the end of the period, in which case a piecemeal "we did not finish, but at least we have a handful of subcommands rewritten to show for the consolation prize" might be a way to have "something" that resembles "working". But if we value the quality of the final product over having to have something to show in a set term (like you as a paid programmer working as a professional), building piece by piece in the final framework (i.e. "in C as a builtin") and flipping the "useBuiltin" to turn on the whole thing at the end would be the preferrable way to do this kind of thing, I would think. Also, it is less wasteful, not having to worry about the inter-language glue code. Even if the original is in shell, not in Perl, we have quite a lot of glue code to throw values back and forth across the language boundary to imitate what used to be mere assignments to global shell variables between shell functions and their callers, and always somebody screws up quoting there ;-)
Quoted 12 lines
Quoted 9 lines
builtin/add.c int run_add_interactive(const char *revision, const char *patch_mode, · line 196
Quoted 3 lines
builtin/add.c +2
-0+if (use_builtin_add_i ==1 && !patch_mode) +return !!run_add_i (the_repository, pathspec);Strictly speaking, we can bypass the probing of environment and config when upon the entry of the function, where
patch_modeis already known. I do not know offhand if rearranging the code to take advantage of that fact would result in a flow that is also easier to follow, but I suspect it would.Okay. I changed it to:
Quoted 7 lines
if (!patch_mode) {
if (use_builtin_add_i < 0)
git_config_get_bool("add.interactive.usebuiltin",
&use_builtin_add_i);
if (use_builtin_add_i == 1)
return !!run_add_i(the_repository, pathspec);
}Doesn't look so bad as I feared.
Thanks.
Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
Hi Junio,
Quoted 26 lines · Junio C Hamano
Quoted 24 lines · Johannes Schindelin
I reworded the commit message:
Start to implement a built-in version of
git add --interactiveTo convert the interactive
addto C, we start with a bare-bones version of the built-in interactive add, guarded by the newadd.interactive.useBuiltinconfig variable, and then add more and more functionality to it, until it is feature complete.This is in contrast to previous conversions to C, where we started with a built-in helper that spawns the script by default, but optionally executes the C code instead. The sole reason for this deviation from previous practice is that on Windows (where such a conversion has the most benefits in terms of speed and robustness) we face the very specific problem that a
system()call in Perl seems to closestdinin the parent process when the spawned process consumes even one character fromstdin. 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.
Quoted 7 lines
Quoted 4 lines
At this point, the built-in version of
git add -ionly states that it cannot do anything yet ;-)Hopefully you like this one better?
Not really. I find the "we could do the other way but we don't, and I hate Perl" totally irrelevant and misleading.
Okay, I understand that you take exception at my criticism of Git's use of Perl, and I fully understand that you think I blame you for it because you added most of it.
And I agree that this sidetrack is totally irrelevant for the patch under discussion.
I do think, however, that the discussion of "we wanted to do it the other way, but when we tried, it did not work" is relevant, even if I shortened it to "we use a different approach than previous conversions, because that previous approach would not work".
Truth be told: I would have _much rather_ stayed with the previous --helper approach, as that would have made it possible to have a passing test suite at every step, with and without GIT_TEST_ADD_I_USE_BUILTIN=true.
The "let GIT_TEST_ADD_I_USE_BUILTIN=true use the built-in even for functions we _know_ are not implemented" way only gives us the full comfort of a passing test suite at the very end of all six patch series, if which the patch series we are currently discussing is merely the first.
If I was a reviewer of this patch series rather than the sender, I would be a bit uncomfortable with the fact that GIT_TEST_ADD_I_USE_BUILTIN cannot be added to the CI/PR builds' linux-gcc over-job, not until much, much later. In fact, it can only be added as the very last patch in the very last of the six patch series.
And as I write this, I realize that I never spelled that out in the commit message, and it is a rather important point for reviewers to see addressed pre-emptively, in my opinion.
Therefore I revised the commit message again:
Start to implement a built-in version of git add --interactive
Unlike previous conversions to C, where we started with a built-in helper, we start this conversion by adding an interception in the run_add_interactive() function when the new opt-in add.interactive.useBuiltin config knob is turned on (or the corresponding environment variable GIT_TEST_ADD_I_USE_BUILTIN), and calling the new internal API function run_add_i() that is implemented directly in libgit.a.
At this point, the built-in version of git add -i only states that it cannot do anything yet. In subsequent patches/patch series, the run_add_i() function will gain more and more functionality, until it is feature complete. The whole arc of the conversion can be found in the PRs #170-175 at https://github.com/gitgitgadget/git.
The "--helper approach" can unfortunately not be used here: on Windows we face the very specific problem that a system() call in Perl seems to close stdin in the parent process when the spawned process consumes even one character from stdin. Which prevents us from implementing the main loop in C and still trying to hand off to the Perl script.
The very real downside of the approach we have to take here is that the test suite won't pass with GIT_TEST_ADD_I_USE_BUILTIN=true until the conversion is complete (the --helper approach would have let it pass, even at each of the incremental conversion steps).
Any suggestions how to improve that commit message?
Ciao,
Dscho
Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
Quoted 7 lines · Johannes Schindelin
And I agree that this sidetrack is totally irrelevant for the patch under discussion.
I do think, however, that the discussion of "we wanted to do it the other way, but when we tried, it did not work" is relevant, even if I shortened it to "we use a different approach than previous conversions, because that previous approach would not work".
Regardless of the language the scripted version was written in, I think the '--helper' approach is always the poorer choice between the two [*1*]. It limits the modular decomposition to what suits the original language, the impedance mismatch between the original and target language forces us to unnatural style of inter module communication, and the unnatural interface layer, which we know has to be discarded at the end, must be written [*2*].
So, I'd prefer to see "because this is a better way in the longer term" over "because the --helper approach would not work".
[Footnote]
*1* In only one case I would recommend using "--helper" approach,
though. When you are not expecting the developer to be able to come up with a better split of the program into modules than how the scripted version is, and you want to ensure that the developer have something to show when they faild to complete the project after N weeks. You are a more experienced developer than an average GSoC student, and there is no pencils-down time, so the exception would not apply.
*2* In "git submodule" for example it was quite natural for the
module that gives a list of submodules with its traits the program cares about to be written as a shell function that writes the data to its standard output. And consuming modules sit at the downstream of a pipe, accepting its output. When you are writing these modules both in C, you wouldn't connect them with pipe to carry the list of submodules, but a piecemeal conversion using the "--helper" approach meant that there always remained _some_ consumer that wants to read from the pipe, so long after the module lister was rewritten in C, it still needed to support a mode where it sends its output to the pipe, instead of just passing an array of structures.
Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
Hi Junio,
Quoted 18 lines · Junio C Hamano
Quoted 7 lines · Johannes Schindelin
And I agree that this sidetrack is totally irrelevant for the patch under discussion.
I do think, however, that the discussion of "we wanted to do it the other way, but when we tried, it did not work" is relevant, even if I shortened it to "we use a different approach than previous conversions, because that previous approach would not work".
Regardless of the language the scripted version was written in, I think the '--helper' approach is always the poorer choice between the two [*1*]. It limits the modular decomposition to what suits the original language, the impedance mismatch between the original and target language forces us to unnatural style of inter module communication, and the unnatural interface layer, which we know has to be discarded at the end, must be written [*2*].
Quoted 2 lines
So, I'd prefer to see "because this is a better way in the longer term" over "because the --helper approach would not work".
Hmm. I feel distinctly unheard.
It may appear compelling, conceptually, to shun the --helper approach, but the more important reality is that it is the only one that makes an incremental conversion possible at all.
It took an entire month of 60-hour weeks to complete the conversion of git add -i/git add -p to C, and only at the very end was I able to run the test suite with GIT_TEST_ADD_I_USE_BUILTIN=true and see it pass.
That is an awfully long time, and you know fully well that this amount of work equates to three to four Outreachy/GSoC seasons. That would be an insane amount of time to go without the confidence of a passing test suite.
In contrast, we were able to complete the conversions of the interactive rebase as well as of git stash within _a single season_. I attribute a large part of that success to the ability to keep the tests green during the incremental conversion _because of_ the --helper approach.
So no, I do not think that your suggestion to reword the commit message is something we want to do. Instead, I think the commit message needs to be rephrased until I get the point across clearly.
It is indeed _in spite of_ the success of the --helper approach that we cannot use it here.
Ciao,
Dscho
Quoted 25 lines
[Footnote]
*1* In only one case I would recommend using "--helper" approach,
though. When you are not expecting the developer to be able to come up with a better split of the program into modules than how the scripted version is, and you want to ensure that the developer have something to show when they faild to complete the project after N weeks. You are a more experienced developer than an average GSoC student, and there is no pencils-down time, so the exception would not apply.*2* In "git submodule" for example it was quite natural for the
module that gives a list of submodules with its traits the program cares about to be written as a shell function that writes the data to its standard output. And consuming modules sit at the downstream of a pipe, accepting its output. When you are writing these modules both in C, you wouldn't connect them with pipe to carry the list of submodules, but a piecemeal conversion using the "--helper" approach meant that there always remained _some_ consumer that wants to read from the pipe, so long after the module lister was rewritten in C, it still needed to support a mode where it sends its output to the pipe, instead of just passing an array of structures.
Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
Quoted 8 lines · Johannes Schindelin
Quoted 8 lines
Regardless of the language the scripted version was written in, I think the '--helper' approach is always the poorer choice between the two [*1*]. It limits the modular decomposition to what suits the original language, the impedance mismatch between the original and target language forces us to unnatural style of inter module communication, and the unnatural interface layer, which we know has to be discarded at the end, must be written [*2*].
Quoted 4 lines
Quoted 2 lines
So, I'd prefer to see "because this is a better way in the longer term" over "because the --helper approach would not work".
Hmm. I feel distinctly unheard.
The feeling is mutual ;-)
Quoted 8 lines
It may appear compelling, conceptually, to shun the
--helperapproach, but the more important reality is that it is the only one that makes an incremental conversion possible at all.It took an entire month of 60-hour weeks to complete the conversion of
git add -i/git add -pto C, and only at the very end was I able to run the test suite withGIT_TEST_ADD_I_USE_BUILTIN=trueand see it pass.
Yeah, that is developer comfort, and of course it is nice to have than not to have it.
But compared to the downside impact to the quality of end result that is inherent to the '--helper' approach, I'd prioritize the quality of the end result over developer comfort.
Quoted 2 lines
It is indeed
_inspiteof_the success of the--helperapproach that we cannot use it here.
As I do not see those past '--helper' ones necessarily successes, we must agree to disagree here.
In any case, the log message needs to express why _you_ ended up taking the non-helper approach. Even though it is far less relevant, compared to that, what other approach you instead wanted to take, I do not veto you from having your own opinion.
Quoted 24 lines
Quoted 24 lines
[Footnote]
*1* In only one case I would recommend using "--helper" approach,
though. When you are not expecting the developer to be able to come up with a better split of the program into modules than how the scripted version is, and you want to ensure that the developer have something to show when they faild to complete the project after N weeks. You are a more experienced developer than an average GSoC student, and there is no pencils-down time, so the exception would not apply.*2* In "git submodule" for example it was quite natural for the
module that gives a list of submodules with its traits the program cares about to be written as a shell function that writes the data to its standard output. And consuming modules sit at the downstream of a pipe, accepting its output. When you are writing these modules both in C, you wouldn't connect them with pipe to carry the list of submodules, but a piecemeal conversion using the "--helper" approach meant that there always remained _some_ consumer that wants to read from the pipe, so long after the module lister was rewritten in C, it still needed to support a mode where it sends its output to the pipe, instead of just passing an array of structures.
Re: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive`
Hi Junio,
Quoted 10 lines · Junio C Hamano
Quoted 8 lines · Johannes Schindelin
Quoted 8 lines
Regardless of the language the scripted version was written in, I think the '--helper' approach is always the poorer choice between the two [*1*]. It limits the modular decomposition to what suits the original language, the impedance mismatch between the original and target language forces us to unnatural style of inter module communication, and the unnatural interface layer, which we know has to be discarded at the end, must be written [*2*].
Quoted 6 lines
Quoted 4 lines
Quoted 2 lines
So, I'd prefer to see "because this is a better way in the longer term" over "because the --helper approach would not work".
Hmm. I feel distinctly unheard.
The feeling is mutual ;-)
Maybe I could take your comments more seriously if you acknowledged the fact that I am _very_ familiar with the vagaries of converting scripts to C. Like, very, very, very familiar.
Quoted 11 lines
Quoted 8 lines
It may appear compelling, conceptually, to shun the
--helperapproach, but the more important reality is that it is the only one that makes an incremental conversion possible at all.It took an entire month of 60-hour weeks to complete the conversion of
git add -i/git add -pto C, and only at the very end was I able to run the test suite withGIT_TEST_ADD_I_USE_BUILTIN=trueand see it pass.Yeah, that is developer comfort, and of course it is nice to have than not to have it.
Comfort has little to do with it. Driving out bugs has a lot more to do with it. Which is the point I am trying to get across the entire time.
Quoted 9 lines
But compared to the downside impact to the quality of end result that is inherent to the '--helper' approach, I'd prioritize the quality of the end result over developer comfort.
Quoted 2 lines
It is indeed
_inspiteof_the success of the--helperapproach that we cannot use it here.As I do not see those past '--helper' ones necessarily successes, we must agree to disagree here.
Right. But if I recall, you never even saw the need for the conversions in the first place. Maybe you still don't?
Quoted 4 lines
In any case, the log message needs to express why _you_ ended up taking the non-helper approach. Even though it is far less relevant, compared to that, what other approach you instead wanted to take, I do not veto you from having your own opinion.
Okay. I will take that as an indication that I can go forward with the latest proposal. After all, I described pretty well, I think, why _I_ ended up taking the non-helper approach.
Thanks,
Dscho
Quoted 25 lines
Quoted 24 lines
Quoted 24 lines
[Footnote]
*1* In only one case I would recommend using "--helper" approach,
though. When you are not expecting the developer to be able to come up with a better split of the program into modules than how the scripted version is, and you want to ensure that the developer have something to show when they faild to complete the project after N weeks. You are a more experienced developer than an average GSoC student, and there is no pencils-down time, so the exception would not apply.*2* In "git submodule" for example it was quite natural for the
module that gives a list of submodules with its traits the program cares about to be written as a shell function that writes the data to its standard output. And consuming modules sit at the downstream of a pipe, accepting its output. When you are writing these modules both in C, you wouldn't connect them with pipe to carry the list of submodules, but a piecemeal conversion using the "--helper" approach meant that there always remained _some_ consumer that wants to read from the pipe, so long after the module lister was rewritten in C, it still needed to support a mode where it sends its output to the pipe, instead of just passing an array of structures.