From 53619c7dfa7b0c0ad6b3aba33deb7618b44018bd Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Tue, 16 May 2017 01:00:31 -0300 Subject: [PATCH 01/11] diff: export diffstat interface 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. Mentored-by: Johannes Schindelin Signed-off-by: Daniel Ferreira Signed-off-by: Slavica Djukic --- diff.c | 37 +++++++++++++++---------------------- diff.h | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/diff.c b/diff.c index 5306c48652db59..daa5f3a736fee8 100644 --- a/diff.c +++ b/diff.c @@ -2489,22 +2489,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b) } } -struct diffstat_t { - int nr; - int alloc; - struct diffstat_file { - char *from_name; - char *name; - char *print_name; - const char *comments; - unsigned is_unmerged:1; - unsigned is_binary:1; - unsigned is_renamed:1; - unsigned is_interesting:1; - uintmax_t added, deleted; - } **files; -}; - static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, const char *name_a, const char *name_b) @@ -6001,12 +5985,7 @@ void diff_flush(struct diff_options *options) dirstat_by_line) { struct diffstat_t diffstat; - memset(&diffstat, 0, sizeof(struct diffstat_t)); - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (check_pair_status(p)) - diff_flush_stat(p, options, &diffstat); - } + compute_diffstat(options, &diffstat, q); if (output_format & DIFF_FORMAT_NUMSTAT) show_numstat(&diffstat, options); if (output_format & DIFF_FORMAT_DIFFSTAT) @@ -6306,6 +6285,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options) return ignored; } +void compute_diffstat(struct diff_options *options, + struct diffstat_t *diffstat, + struct diff_queue_struct *q) +{ + int i; + + memset(diffstat, 0, sizeof(struct diffstat_t)); + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (check_pair_status(p)) + diff_flush_stat(p, options, diffstat); + } +} + void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const struct object_id *oid, diff --git a/diff.h b/diff.h index b512d0477ac3a4..ae9bedfab8c9bd 100644 --- a/diff.h +++ b/diff.h @@ -240,6 +240,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err); void diff_emit_submodule_pipethrough(struct diff_options *o, const char *line, int len); +struct diffstat_t { + int nr; + int alloc; + struct diffstat_file { + char *from_name; + char *name; + char *print_name; + const char *comments; + unsigned is_unmerged:1; + unsigned is_binary:1; + unsigned is_renamed:1; + unsigned is_interesting:1; + uintmax_t added, deleted; + } **files; +}; + enum color_diff { DIFF_RESET = 0, DIFF_CONTEXT = 1, @@ -328,6 +344,9 @@ void diff_change(struct diff_options *, struct diff_filepair *diff_unmerge(struct diff_options *, const char *path); +void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat, + struct diff_queue_struct *q); + #define DIFF_SETUP_REVERSE 1 #define DIFF_SETUP_USE_SIZE_CACHE 4 From c2781ad7d0bafb28f9d1d5c0e61cd8471c986150 Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Tue, 16 May 2017 01:00:32 -0300 Subject: [PATCH 02/11] add--helper: create builtin helper for interactive add Create a builtin helper for git-add--interactive, which at this point is not doing anything. This is the first step in an effort to convert git-add--interactive.perl to a C builtin, in search for better portability, expressibility and performance (specially on non-POSIX systems like Windows). Additionally, an eventual complete port of git-add--interactive would remove the last "big" Git script to have Perl as a dependency, allowing most Git users to have a NOPERL build running without big losses. Mentored-by: Johannes Schindelin Signed-off-by: Daniel Ferreira Signed-off-by: Slavica Djukic --- .gitignore | 1 + Makefile | 1 + builtin.h | 1 + builtin/add--helper.c | 6 ++++++ git.c | 1 + 5 files changed, 10 insertions(+) create mode 100644 builtin/add--helper.c diff --git a/.gitignore b/.gitignore index 7374587f9df901..7b6d7681f9b9ff 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ /git /git-add /git-add--interactive +/git-add--helper /git-am /git-annotate /git-apply diff --git a/Makefile b/Makefile index f0b2299172cf63..e64e202debd809 100644 --- a/Makefile +++ b/Makefile @@ -1043,6 +1043,7 @@ LIB_OBJS += xdiff-interface.o LIB_OBJS += zlib.o BUILTIN_OBJS += builtin/add.o +BUILTIN_OBJS += builtin/add--helper.o BUILTIN_OBJS += builtin/am.o BUILTIN_OBJS += builtin/annotate.o BUILTIN_OBJS += builtin/apply.o diff --git a/builtin.h b/builtin.h index 6538932e99a72f..dd811ef7d56304 100644 --- a/builtin.h +++ b/builtin.h @@ -128,6 +128,7 @@ extern void setup_auto_pager(const char *cmd, int def); extern int is_builtin(const char *s); extern int cmd_add(int argc, const char **argv, const char *prefix); +extern int cmd_add__helper(int argc, const char **argv, const char *prefix); extern int cmd_am(int argc, const char **argv, const char *prefix); extern int cmd_annotate(int argc, const char **argv, const char *prefix); extern int cmd_apply(int argc, const char **argv, const char *prefix); diff --git a/builtin/add--helper.c b/builtin/add--helper.c new file mode 100644 index 00000000000000..6a97f0e191acb0 --- /dev/null +++ b/builtin/add--helper.c @@ -0,0 +1,6 @@ +#include "builtin.h" + +int cmd_add__helper(int argc, const char **argv, const char *prefix) +{ + return 0; +} diff --git a/git.c b/git.c index 2dd588674f621e..cb42591f37d733 100644 --- a/git.c +++ b/git.c @@ -444,6 +444,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) static struct cmd_struct commands[] = { { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE }, + { "add--helper", cmd_add__helper, RUN_SETUP | NEED_WORK_TREE }, { "am", cmd_am, RUN_SETUP | NEED_WORK_TREE }, { "annotate", cmd_annotate, RUN_SETUP | NO_PARSEOPT }, { "apply", cmd_apply, RUN_SETUP_GENTLY }, From e9528be737ad89358276b34047ce1e0c6a436033 Mon Sep 17 00:00:00 2001 From: Slavica Djukic Date: Thu, 14 Feb 2019 10:17:10 +0100 Subject: [PATCH 03/11] add-interactive.c: implement list_modified Implement list_modified from Perl, which will be used by most of the *_cmd functions, including status in the following commit. It lists a numstat comparing changed files between a) the worktree and the index; b) the index and the HEAD. To do so, we use run_diff_index() and run_diff_files() to get changed files, use the diffstat API on them to get the numstat and use a combination of a hashmap and qsort() to print the result in O(n) + O(n lg n) complexity. Add new file: add-interactive.c which will be used for implementing "application logic" of git add -i (alongside with add-interactive.h, added in later commit), whereas add--helper.c will be used mostly for parsing the command line. Mentored-by: Johannes Schindelin Original-patch-by: Daniel Ferreira Signed-off-by: Slavica Djukic --- Makefile | 1 + add-interactive.c | 193 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 add-interactive.c diff --git a/Makefile b/Makefile index e64e202debd809..1b3c06b63391fa 100644 --- a/Makefile +++ b/Makefile @@ -847,6 +847,7 @@ LIB_H = $(shell $(FIND) . \ -name '*.h' -print) LIB_OBJS += abspath.o +LIB_OBJS += add-interactive.o LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.o diff --git a/add-interactive.c b/add-interactive.c new file mode 100644 index 00000000000000..42215059994f44 --- /dev/null +++ b/add-interactive.c @@ -0,0 +1,193 @@ +#include "cache.h" +#include "commit.h" +#include "color.h" +#include "config.h" +#include "diffcore.h" +#include "refs.h" +#include "revision.h" + +#define HEADER_INDENT " " + +#define HEADER_MAXLEN 30 + +struct adddel { uintmax_t add, del; }; + +struct file_stat { + struct hashmap_entry ent; + struct adddel index, worktree; + char name[FLEX_ARRAY]; +}; + +struct collection_status { + int collecting_from_index; + + const char *reference; + struct pathspec pathspec; + + struct hashmap file_map; +}; + +static int pathname_equal(const void *unused_cmp_data, + const void *entry, const void *entry_or_key, + const void *keydata) +{ + const struct file_stat *e1 = entry, *e2 = entry_or_key; + const char *name = keydata ? keydata : e2->name; + + return strcmp(e1->name, name); +} + +static int pathname_cmp(const void *a, const void *b) +{ + struct file_stat *f1 = *((struct file_stat **)a); + struct file_stat *f2 = *((struct file_stat **)b); + + return strcmp(f1->name, f2->name); +} + +static void populate_adddel(struct adddel *ad, uintmax_t add, uintmax_t del) +{ + ad->add = add; + ad->del = del; +} + +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++) { + struct file_stat *entry; + const char *name = stat.files[i]->name; + unsigned int hash = strhash(name); + + entry = hashmap_get_from_hash(&s->file_map, hash, name); + if (!entry) { + FLEX_ALLOC_STR(entry, name, name); + hashmap_entry_init(entry, hash); + hashmap_add(&s->file_map, entry); + } + + if (s->collecting_from_index) + populate_adddel(&entry->index, stat.files[i]->added,stat.files[i]->deleted); + else + populate_adddel(&entry->worktree, stat.files[i]->added, stat.files[i]->deleted); + } +} + +static void collect_changes_worktree(struct collection_status *s) +{ + struct rev_info rev; + + s->collecting_from_index = 0; + + init_revisions(&rev, NULL); + setup_revisions(0, NULL, &rev, NULL); + + /* Use the max_count field to specify the unmerged stage, against + * which the working tree file is compared for an unmerged path. + * git diff-files itself when running cmd_diff_files() leaves + * rev.max_count untouched to get a normal output (as opposed to + * the case when it is told to do --base/--ours/--theirs), so it + * ends up passing -1 in this field in such a case. + */ + rev.max_count = 0; + + rev.diffopt.flags.ignore_dirty_submodules = 1; + rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = collect_changes_cb; + rev.diffopt.format_callback_data = s; + + run_diff_files(&rev, 0); +} + +static void collect_changes_index(struct collection_status *s) +{ + struct rev_info rev; + struct setup_revision_opt opt = { 0 }; + + s->collecting_from_index = 1; + + init_revisions(&rev, NULL); + opt.def = s->reference; + 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; + + run_diff_index(&rev, 1); +} + +static int on_unborn_branch(void) +{ + int flags = 0; + struct object_id oid; + const char *ref; + + ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &oid, &flags); + return !ref && (flags & REF_ISSYMREF); +} + +static const char *get_diff_reference(void) +{ + return on_unborn_branch() ? empty_tree_oid_hex() : "HEAD"; +} + +static struct file_stat **list_modified(struct repository *r, + const char *filter) +{ + int i = 0; + int hashmap_size = 0; + struct collection_status *s = xcalloc(1, sizeof(*s)); + struct hashmap_iter iter; + struct file_stat **files; + struct file_stat *entry; + + if (repo_read_index(r) < 0) { + free(s); + return NULL; + } + + s->reference = get_diff_reference(); + hashmap_init(&s->file_map, pathname_equal, NULL, 0); + + if (!filter) { + collect_changes_index(s); + collect_changes_worktree(s); + } + else if (!strcmp(filter, "index-only")) + collect_changes_index(s); + else if (!strcmp(filter, "file-only")) + collect_changes_worktree(s); + else + BUG("unknown filter parameter\n"); + + hashmap_size = hashmap_get_size(&s->file_map); + + if (hashmap_size < 1) { + free(s); + return NULL; + } + + hashmap_iter_init(&s->file_map, &iter); + + files = xcalloc(hashmap_size + 1, sizeof(struct file_stat)); + while ((entry = hashmap_iter_next(&iter))) { + files[i++] = entry; + } + QSORT(files, hashmap_size, pathname_cmp); + files[hashmap_size] = NULL; + + hashmap_free(&s->file_map, 0); + free(s); + return files; +} From 2b55a8cfc0c02cacfbcc7ab7903c3debebcb718f Mon Sep 17 00:00:00 2001 From: Slavica Djukic Date: Thu, 14 Feb 2019 10:55:51 +0100 Subject: [PATCH 04/11] add-interactive.c: implement list_and_choose This is one of the two functions translated from Perl script, besides list_modified that will be used by *_cmd functions (including status) to collect index/worktree changes, list those changes and let user make a choice. At this point, it only prints worktree and index changes, but later on in this patch series there will be upgraded to highlighting unique prefixes of list items and let user pick those items. Mentored-by: Johannes Schindelin Signed-off-by: Slavica Djukic --- add-interactive.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/add-interactive.c b/add-interactive.c index 42215059994f44..1331ff3e1129bb 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -27,6 +27,64 @@ struct collection_status { struct hashmap file_map; }; +struct list_and_choose_options { + int column_n; + unsigned singleton:1; + unsigned list_only:1; + unsigned list_only_file_names:1; + unsigned immediate:1; + struct strbuf header; + const char *prompt; + const char *header_indent; + void (*on_eof_fn)(void); +}; + +struct choice { + struct hashmap_entry e; + union { + void (*command_fn)(void); + struct { + struct adddel index, worktree; + } file; + } u; + size_t prefix_length; + const char *name; +}; + +enum choice_type { + FILE_STAT, + COMMAND +}; + +struct choices { + struct choice **choices; + size_t alloc, nr; + enum choice_type type; +}; +#define CHOICES_INIT { NULL, 0, 0, 0 } + +static int use_color = -1; +enum color_add_i { + COLOR_PROMPT, + COLOR_HEADER, + COLOR_HELP, + COLOR_ERROR +}; + +static char list_and_choose_colors[][COLOR_MAXLEN] = { + GIT_COLOR_BOLD_BLUE, /* Prompt */ + GIT_COLOR_BOLD, /* Header */ + GIT_COLOR_BOLD_RED, /* Help */ + GIT_COLOR_BOLD_RED /* Error */ +}; + +static const char *get_color(enum color_add_i ix) +{ + if (want_color(use_color)) + return list_and_choose_colors[ix]; + return ""; +} + static int pathname_equal(const void *unused_cmp_data, const void *entry, const void *entry_or_key, const void *keydata) @@ -191,3 +249,83 @@ static struct file_stat **list_modified(struct repository *r, free(s); return files; } + +static void populate_wi_changes(struct strbuf *buf, struct adddel *ad, + char *no_changes) +{ + if (ad->add || ad->del) + strbuf_addf(buf, "+%"PRIuMAX"/-%"PRIuMAX, + ad->add, ad->del); + else + strbuf_addf(buf, "%s", _(no_changes)); +} + +static struct choices *list_and_choose(struct choices *data, + struct list_and_choose_options *opts) +{ + int i; + struct strbuf print_buf = STRBUF_INIT; + struct strbuf print = STRBUF_INIT; + struct strbuf index_changes = STRBUF_INIT; + struct strbuf worktree_changes = STRBUF_INIT; + + if (!data) + return NULL; + + while (1) { + int last_lf = 0; + + if (opts->header.len) { + const char *header_color = get_color(COLOR_HEADER); + if (opts->header_indent) + fputs(opts->header_indent, stdout); + color_fprintf_ln(stdout, header_color, "%s", opts->header.buf); + } + + for (i = 0; i < data->nr; i++) { + struct choice *c = data->choices[i]; + const char *modified_fmt = _("%12s %12s %s"); + + strbuf_reset(&print_buf); + + strbuf_add(&print_buf, c->name, strlen(c->name)); + + if ((data->type == FILE_STAT) && (!opts->list_only_file_names)) { + strbuf_reset(&print); + strbuf_reset(&index_changes); + strbuf_reset(&worktree_changes); + + populate_wi_changes(&worktree_changes, &c->u.file.worktree, + "nothing"); + populate_wi_changes(&index_changes, &c->u.file.index, + "unchanged"); + + strbuf_addbuf(&print, &print_buf); + strbuf_reset(&print_buf); + strbuf_addf(&print_buf, modified_fmt, index_changes.buf, + worktree_changes.buf, print.buf); + } + + printf(" %2d: %s", i + 1, print_buf.buf); + + if ((opts->column_n) && ((i + 1) % (opts->column_n))) { + putchar('\t'); + last_lf = 0; + } + else { + putchar('\n'); + last_lf = 1; + } + } + + if (!last_lf) + putchar('\n'); + + return NULL; + } + + strbuf_release(&print_buf); + strbuf_release(&print); + strbuf_release(&index_changes); + strbuf_release(&worktree_changes); +} From 37c200d8189249a643a39d8e58f3db0912e3a6ba Mon Sep 17 00:00:00 2001 From: Slavica Djukic Date: Thu, 14 Feb 2019 11:24:57 +0100 Subject: [PATCH 05/11] add-interactive.c: implement status command Implement add --interactive's status command in add-interactive.c and use it in builtin add--helper.c. This is the first interactive add command implemented in C of those anticipated by the previous commit, which introduced the add--helper built-in. Implement additional helper functions dealing with struct choice and colors. Use list_modified to get the data, and then pass it to list_and_choose to show it in appropriate format. We're a bit lax with the command-line parsing, as the command is intended to be called only by one internal user: the add--interactive script. Mentored-by: Johannes Schindelin Original-patch-by: Daniel Ferreira Signed-off-by: Slavica Djukic --- add-interactive.c | 121 ++++++++++++++++++++++++++++++++++++++++++ add-interactive.h | 8 +++ builtin/add--helper.c | 32 +++++++++++ 3 files changed, 161 insertions(+) create mode 100644 add-interactive.h diff --git a/add-interactive.c b/add-interactive.c index 1331ff3e1129bb..e5e34247e7e7a2 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -1,3 +1,4 @@ +#include "add-interactive.h" #include "cache.h" #include "commit.h" #include "color.h" @@ -27,6 +28,11 @@ struct collection_status { struct hashmap file_map; }; +struct command { + char *name; + void (*command_fn)(void); +}; + struct list_and_choose_options { int column_n; unsigned singleton:1; @@ -85,6 +91,42 @@ static const char *get_color(enum color_add_i ix) return ""; } +static int parse_color_slot(const char *slot) +{ + if (!strcasecmp(slot, "prompt")) + return COLOR_PROMPT; + if (!strcasecmp(slot, "header")) + return COLOR_HEADER; + if (!strcasecmp(slot, "help")) + return COLOR_HELP; + if (!strcasecmp(slot, "error")) + return COLOR_ERROR; + + return -1; +} + +int add_i_config(const char *var, + const char *value, void *cbdata) +{ + 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_and_choose_colors[slot]); + } + + return git_default_config(var, value, cbdata); +} + static int pathname_equal(const void *unused_cmp_data, const void *entry, const void *entry_or_key, const void *keydata) @@ -329,3 +371,82 @@ static struct choices *list_and_choose(struct choices *data, strbuf_release(&index_changes); strbuf_release(&worktree_changes); } + +static struct choice *make_choice(const char *name ) +{ + struct choice *choice; + + FLEXPTR_ALLOC_STR(choice, name, name); + return choice; +} + +static struct choice *add_choice(struct choices *choices, + struct file_stat *file, struct command *command) +{ + struct choice *choice; + + if (file && command) + BUG("either file_stat or command should be NULL\n"); + + switch (choices->type) { + case FILE_STAT: + choice = make_choice(file->name); + choice->u.file.index = file->index; + choice->u.file.worktree = file->worktree; + break; + case COMMAND: + choice = make_choice(command->name); + choice->u.command_fn = command->command_fn; + break; + } + + ALLOC_GROW(choices->choices, choices->nr + 1, choices->alloc); + choices->choices[choices->nr++] = choice; + + return choice; +} + +static void free_choices(struct choices *choices) +{ + int i; + + for (i = 0; i < choices->nr; i++) + free(choices->choices[i]); + free(choices->choices); + choices->choices = NULL; + choices->nr = choices->alloc = 0; +} + +void add_i_status(void) +{ + int i; + struct file_stat **files; + struct list_and_choose_options opts = { 0 }; + struct choices choices = CHOICES_INIT; + const char *modified_fmt = _("%12s %12s %s"); + + choices.type = FILE_STAT; + + opts.list_only = 1; + opts.header_indent = HEADER_INDENT; + strbuf_init(&opts.header, 0); + strbuf_addf(&opts.header, modified_fmt, _("staged"), + _("unstaged"), _("path")); + + files = list_modified(the_repository, NULL); + if (files == NULL) { + strbuf_release(&opts.header); + putchar('\n'); + return; + } + + for (i = 0; files[i]; i++) + add_choice(&choices, files[i], NULL); + + list_and_choose(&choices, &opts); + putchar('\n'); + + strbuf_release(&opts.header); + free(files); + free_choices(&choices); +} diff --git a/add-interactive.h b/add-interactive.h new file mode 100644 index 00000000000000..8ef3d2e82b63d8 --- /dev/null +++ b/add-interactive.h @@ -0,0 +1,8 @@ +#ifndef ADD_INTERACTIVE_H +#define ADD_INTERACTIVE_H + +int add_i_config(const char *var, const char *value, void *cbdata); + +void add_i_status(void); + +#endif diff --git a/builtin/add--helper.c b/builtin/add--helper.c index 6a97f0e191acb0..464d2245f3327e 100644 --- a/builtin/add--helper.c +++ b/builtin/add--helper.c @@ -1,6 +1,38 @@ +#include "add-interactive.h" #include "builtin.h" +#include "config.h" +#include "revision.h" + +static const char * const builtin_add_helper_usage[] = { + N_("git add-interactive--helper "), + NULL +}; + +enum cmd_mode { + DEFAULT = 0, + STATUS +}; int cmd_add__helper(int argc, const char **argv, const char *prefix) { + enum cmd_mode mode = DEFAULT; + + struct option options[] = { + OPT_CMDMODE(0, "status", &mode, + N_("print status information with diffstat"), STATUS), + OPT_END() + }; + + git_config(add_i_config, NULL); + argc = parse_options(argc, argv, NULL, options, + builtin_add_helper_usage, + PARSE_OPT_KEEP_ARGV0); + + if (mode == STATUS) + add_i_status(); + else + usage_with_options(builtin_add_helper_usage, + options); + return 0; } From f10adbc73a0ae6c9cc004647acac5311cdc38cfd Mon Sep 17 00:00:00 2001 From: Slavica Djukic Date: Wed, 27 Feb 2019 12:30:53 +0100 Subject: [PATCH 06/11] add--interactive.perl: use add--helper --status for status_cmd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Call the newly introduced add--helper builtin in status_cmd() instead of relying on add--interactive's Perl functions to print the numstat. If an error occurs, it will be reported, but the Perl script will not exit, since the add--helper is called within an eval block. As the Perl script will go away soon, so will this scenario, where the built-in helper is called from the Perl script. Combined with the fact that it would be hard to test, we'll pass on adding a regression test for this. Mentored-by: Johannes Schindelin Original-patch-by: Daniel Ferreira Signed-off-by: Slavica Djukic Signed-off-by: Ævar Arnfjörð Bjarmason --- git-add--interactive.perl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 20eb81cc92f947..c2c6b4d5e38b10 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -597,9 +597,8 @@ sub prompt_help_cmd { } sub status_cmd { - list_and_choose({ LIST_ONLY => 1, HEADER => $status_head }, - list_modified()); - print "\n"; + my @status_cmd = ("git", "add--helper", "--status"); + !system(@status_cmd) or die "@status_cmd exited with code $?"; } sub say_n_paths { From 641d13d8707e5c6a2729a5fd7dc8d355d51ed7bf Mon Sep 17 00:00:00 2001 From: Slavica Djukic Date: Wed, 27 Feb 2019 12:31:53 +0100 Subject: [PATCH 07/11] add-interactive.c: add support for list_only option If list_only option is not set, (i.e. we want to pick elements from the list, not just display them), highlight unique prefixes of list elements and let user make a choice as shown in prompt_help_cmd and singleton_prompt_help_cmd. Input that is expected from user is full line. Although that's also the case with Perl script in this particular situation, there is also sub prompt_single_character, which deals with single keystroke. Ever since f7a4cea, we did not use _getch() in our code base's C code, and this would be the first user. There are portability issues with _getch() (or getch()) that we would like to avoid. That said, from now on, every other input will also be expected to be full line, rather than single keystroke. Mentored-by: Johannes Schindelin Signed-off-by: Slavica Djukic --- add-interactive.c | 355 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 348 insertions(+), 7 deletions(-) diff --git a/add-interactive.c b/add-interactive.c index e5e34247e7e7a2..da5698adf2ca78 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -69,19 +69,28 @@ struct choices { }; #define CHOICES_INIT { NULL, 0, 0, 0 } +struct prefix_entry { + struct hashmap_entry e; + const char *name; + size_t prefix_length; + struct choice *item; +}; + static int use_color = -1; enum color_add_i { COLOR_PROMPT, COLOR_HEADER, COLOR_HELP, - COLOR_ERROR + COLOR_ERROR, + COLOR_RESET }; static char list_and_choose_colors[][COLOR_MAXLEN] = { GIT_COLOR_BOLD_BLUE, /* Prompt */ GIT_COLOR_BOLD, /* Header */ GIT_COLOR_BOLD_RED, /* Help */ - GIT_COLOR_BOLD_RED /* Error */ + GIT_COLOR_BOLD_RED, /* Error */ + GIT_COLOR_RESET /* Reset */ }; static const char *get_color(enum color_add_i ix) @@ -101,6 +110,8 @@ static int parse_color_slot(const char *slot) return COLOR_HELP; if (!strcasecmp(slot, "error")) return COLOR_ERROR; + if (!strcasecmp(slot, "reset")) + return COLOR_RESET; return -1; } @@ -302,6 +313,187 @@ static void populate_wi_changes(struct strbuf *buf, struct adddel *ad, strbuf_addf(buf, "%s", _(no_changes)); } +static int map_cmp(const void *unused_cmp_data, + const void *entry, + const void *entry_or_key, + const void *unused_keydata) +{ + const struct choice *a = entry; + const struct choice *b = entry_or_key; + if((a->prefix_length == b->prefix_length) && + (strncmp(a->name, b->name, a->prefix_length) == 0)) + return 0; + return 1; +} + +static struct prefix_entry *new_prefix_entry(const char *name, + size_t prefix_length, + struct choice *item) +{ + struct prefix_entry *result = xcalloc(1, sizeof(*result)); + result->name = name; + result->prefix_length = prefix_length; + result->item = item; + hashmap_entry_init(result, memhash(name, prefix_length)); + return result; +} + +static void find_unique_prefixes(struct choices *data) +{ + int i; + int j; + int soft_limit = 0; + int hard_limit = 4; + struct hashmap map; + + hashmap_init(&map, map_cmp, NULL, 0); + + for (i = 0; i < data->nr; i++) { + struct prefix_entry *e = xcalloc(1, sizeof(*e)); + struct prefix_entry *e2; + e->name = data->choices[i]->name; + e->item = data->choices[i]; + + for (j = soft_limit + 1; j <= hard_limit; j++) { + if (!isascii(e->name[j])) + break; + + e->prefix_length = j; + hashmap_entry_init(e, memhash(e->name, j)); + e2 = hashmap_get(&map, e, NULL); + if (!e2) { + e->item->prefix_length = j; + hashmap_add(&map, e); + e = NULL; + break; + } + + if (!e2->item) { + continue; /* non-unique prefix */ + } + + if (j != e2->item->prefix_length) + BUG("Hashmap entry has unexpected prefix length (%"PRIuMAX"/ != %"PRIuMAX"/)", + (uintmax_t)j, (uintmax_t)e2->item->prefix_length); + + /* skip common prefix */ + for (j++; j <= hard_limit && e->name[j - 1]; j++) { + if (e->item->name[j - 1] != e2->item->name[j - 1]) + break; + hashmap_add(&map, new_prefix_entry(e->name, j, NULL)); + } + if (j <= hard_limit && e2->name[j - 1]) { + e2->item->prefix_length = j; + hashmap_add(&map, new_prefix_entry(e2->name, j, e2->item)); + } + else { + e2->item->prefix_length = 0; + } + e2->item = NULL; + + if (j <= hard_limit && e->name[j - 1]) { + e->item->prefix_length = j; + hashmap_add(&map, new_prefix_entry(e->name, + e->item->prefix_length, e->item)); + e = NULL; + } + else + e->item->prefix_length = 0; + break; + } + + free(e); + } +} + +static int find_unique(char *string, struct choices *data) +{ + int found = 0; + int i = 0; + int hit = 0; + + for (i = 0; i < data->nr; i++) { + struct choice *item = data->choices[i]; + hit = 0; + if (!strcmp(item->name, string)) + hit = 1; + if (hit && found) + return 0; + if (hit) + found = i + 1; + } + + return found; +} + +/* filters out prefixes which have special meaning to list_and_choose() */ +static int is_valid_prefix(const char *prefix) +{ + regex_t *regex; + const char *pattern = "(\\s,)|(^-)|(^[0-9]+)"; + int is_valid = 0; + + regex = xmalloc(sizeof(*regex)); + if (regcomp(regex, pattern, REG_EXTENDED)) + return 0; + + is_valid = prefix && + regexec(regex, prefix, 0, NULL, 0) && + strcmp(prefix, "*") && + strcmp(prefix, "?"); + free(regex); + return is_valid; +} + +/* return a string with the prefix highlighted */ +/* for now use square brackets; later might use ANSI colors (underline, bold) */ +static void highlight_prefix(struct strbuf *buf, struct choice *item) +{ + if (item->prefix_length <= 0 || !is_valid_prefix(item->name)) { + strbuf_addstr(buf, item->name); + return; + } + + strbuf_addf(buf, "%s%.*s%s%s", + use_color ? get_color(COLOR_PROMPT) : "[", + (int)item->prefix_length, item->name, + use_color ? get_color(COLOR_RESET) : "]", + item->name + item->prefix_length); +} + +static void singleton_prompt_help_cmd(void) +{ + const char *help_color = get_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")); +} + +static void prompt_help_cmd(void) +{ + const char *help_color = get_color(COLOR_HELP); + color_fprintf_ln(stdout, help_color, "%s", + _("Prompt help:")); + color_fprintf_ln(stdout, help_color, "1 - %s", + _("select a single item")); + color_fprintf_ln(stdout, help_color, "3-5 - %s", + _("select a range of items")); + color_fprintf_ln(stdout, help_color, "2-3,6-9 - %s", + _("select multiple ranges")); + color_fprintf_ln(stdout, help_color, "foo - %s", + _("select item based on unique prefix")); + color_fprintf_ln(stdout, help_color, "-... - %s", + _("unselect specified items")); + color_fprintf_ln(stdout, help_color, "* - %s", + _("choose all items")); + color_fprintf_ln(stdout, help_color, " - %s", + _("(empty) finish selecting")); +} + static struct choices *list_and_choose(struct choices *data, struct list_and_choose_options *opts) { @@ -310,12 +502,38 @@ static struct choices *list_and_choose(struct choices *data, struct strbuf print = STRBUF_INIT; struct strbuf index_changes = STRBUF_INIT; struct strbuf worktree_changes = STRBUF_INIT; - - if (!data) + char *chosen_choices = xcalloc(data->nr, sizeof(char *)); + struct choices *results = xcalloc(1, sizeof(*results)); + int chosen_size = 0; + struct strbuf choice = STRBUF_INIT; + struct strbuf token = STRBUF_INIT; + struct strbuf input = STRBUF_INIT; + + if (!data) { + free(chosen_choices); + free(results); return NULL; + } + + if (!opts->list_only) + find_unique_prefixes(data); +top: while (1) { + int j; int last_lf = 0; + const char *prompt_color = get_color(COLOR_PROMPT); + const char *error_color = get_color(COLOR_ERROR); + char *token_tmp; + regex_t *regex_dash_range; + regex_t *regex_number; + const char *pattern_dash_range; + const char *pattern_number; + const char delim[] = " ,"; + + strbuf_reset(&choice); + strbuf_reset(&token); + strbuf_reset(&input); if (opts->header.len) { const char *header_color = get_color(COLOR_HEADER); @@ -326,11 +544,15 @@ static struct choices *list_and_choose(struct choices *data, for (i = 0; i < data->nr; i++) { struct choice *c = data->choices[i]; + char chosen = chosen_choices[i]? '*' : ' '; const char *modified_fmt = _("%12s %12s %s"); strbuf_reset(&print_buf); - strbuf_add(&print_buf, c->name, strlen(c->name)); + if (!opts->list_only) + highlight_prefix(&print_buf, data->choices[i]); + else + strbuf_add(&print_buf, c->name, strlen(c->name)); if ((data->type == FILE_STAT) && (!opts->list_only_file_names)) { strbuf_reset(&print); @@ -348,7 +570,7 @@ static struct choices *list_and_choose(struct choices *data, worktree_changes.buf, print.buf); } - printf(" %2d: %s", i + 1, print_buf.buf); + printf("%c%2d: %s", chosen, i + 1, print_buf.buf); if ((opts->column_n) && ((i + 1) % (opts->column_n))) { putchar('\t'); @@ -363,13 +585,132 @@ static struct choices *list_and_choose(struct choices *data, if (!last_lf) putchar('\n'); - return NULL; + if (opts->list_only) + return NULL; + + color_fprintf(stdout, prompt_color, "%s", opts->prompt); + if(opts->singleton) + fputs("> ", stdout); + else + fputs(">> ", stdout); + + fflush(stdout); + strbuf_getline(&input, stdin); + strbuf_trim(&input); + + if (!input.buf) + break; + + if (!input.buf[0]) { + putchar('\n'); + if (opts->on_eof_fn) + opts->on_eof_fn(); + break; + } + + if (!strcmp(input.buf, "?")) { + opts->singleton? singleton_prompt_help_cmd() : prompt_help_cmd(); + goto top; + } + + token_tmp = strtok(input.buf, delim); + strbuf_add(&token, token_tmp, strlen(token_tmp)); + + while (1) { + int choose = 1; + int bottom = 0, top = 0; + strbuf_addbuf(&choice, &token); + + /* Input that begins with '-'; unchoose */ + pattern_dash_range = "^-"; + regex_dash_range = xmalloc(sizeof(*regex_dash_range)); + + if (regcomp(regex_dash_range, pattern_dash_range, REG_EXTENDED)) + BUG("regex compilation for pattern %s failed", + pattern_dash_range); + if (!regexec(regex_dash_range, choice.buf, 0, NULL, 0)) { + choose = 0; + /* remove dash from input */ + strbuf_remove(&choice, 0, 1); + } + + /* A range can be specified like 5-7 or 5-. */ + pattern_dash_range = "^([0-9]+)-([0-9]*)$"; + pattern_number = "^[0-9]+$"; + regex_number = xmalloc(sizeof(*regex_number)); + + if (regcomp(regex_dash_range, pattern_dash_range, REG_EXTENDED)) + BUG("regex compilation for pattern %s failed", + pattern_dash_range); + if (regcomp(regex_number, pattern_number, REG_EXTENDED)) + BUG("regex compilation for pattern %s failed", pattern_number); + + if (!regexec(regex_dash_range, choice.buf, 0, NULL, 0)) { + const char delim_dash[] = "-"; + char *num = NULL; + num = strtok(choice.buf, delim_dash); + bottom = atoi(num); + num = strtok(NULL, delim_dash); + top = num? atoi(num) : (1 + data->nr); + } + else if (!regexec(regex_number, choice.buf, 0, NULL, 0)) + bottom = top = atoi(choice.buf); + else if (!strcmp(choice.buf, "*")) { + bottom = 1; + top = 1 + data->nr; + } + else { + bottom = top = find_unique(choice.buf, data); + if (!bottom) { + color_fprintf_ln(stdout, error_color, _("Huh (%s)?"), choice.buf); + goto top; + } + } + + if (opts->singleton && bottom != top) { + color_fprintf_ln(stdout, error_color, _("Huh (%s)?"), choice.buf); + goto top; + } + + for (j = bottom - 1; j <= top - 1; j++) { + if (data->nr <= j || j < 0) + continue; + chosen_choices[j] = choose; + if (choose == 1) + chosen_size++; + } + + strbuf_reset(&token); + strbuf_reset(&choice); + + token_tmp = strtok(NULL, delim); + if (!token_tmp) + break; + strbuf_add(&token, token_tmp, strlen(token_tmp)); + } + + if ((opts->immediate) || !(strcmp(choice.buf, "*"))) + break; } strbuf_release(&print_buf); strbuf_release(&print); strbuf_release(&index_changes); strbuf_release(&worktree_changes); + + strbuf_release(&choice); + strbuf_release(&token); + strbuf_release(&input); + + for (i = 0; i < data->nr; i++) { + if (chosen_choices[i]) { + ALLOC_GROW(results->choices, results->nr + 1, results->alloc); + results->choices[results->nr++] = data->choices[i]; + } + } + + free(chosen_choices); + return results; } static struct choice *make_choice(const char *name ) From 85bac3b957aeb67aaa42af14ed1097f3d97c44c2 Mon Sep 17 00:00:00 2001 From: Slavica Djukic Date: Thu, 13 Dec 2018 15:40:55 +0100 Subject: [PATCH 08/11] add-interactive.c: implement show-help command Implement show-help command in add-interactive.c and use it in builtin add--helper.c. Use command name "show-help" instead of "help": add--helper is builtin, hence add--helper --help would be intercepted by handle_builtin and re-routed to the help command, without ever calling cmd_add__helper(). Mentored-by: Johannes Schindelin Signed-off-by: Slavica Djukic --- add-interactive.c | 17 +++++++++++++++++ add-interactive.h | 2 ++ builtin/add--helper.c | 7 ++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/add-interactive.c b/add-interactive.c index da5698adf2ca78..a4df97cd80ec42 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -791,3 +791,20 @@ void add_i_status(void) free(files); free_choices(&choices); } + +void add_i_show_help(void) +{ + const char *help_color = get_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")); +} diff --git a/add-interactive.h b/add-interactive.h index 8ef3d2e82b63d8..ddeedd3a332ba9 100644 --- a/add-interactive.h +++ b/add-interactive.h @@ -5,4 +5,6 @@ int add_i_config(const char *var, const char *value, void *cbdata); void add_i_status(void); +void add_i_show_help(void); + #endif diff --git a/builtin/add--helper.c b/builtin/add--helper.c index 464d2245f3327e..1fe64bc7fb7694 100644 --- a/builtin/add--helper.c +++ b/builtin/add--helper.c @@ -10,7 +10,8 @@ static const char * const builtin_add_helper_usage[] = { enum cmd_mode { DEFAULT = 0, - STATUS + STATUS, + HELP }; int cmd_add__helper(int argc, const char **argv, const char *prefix) @@ -20,6 +21,8 @@ int cmd_add__helper(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_CMDMODE(0, "status", &mode, N_("print status information with diffstat"), STATUS), + OPT_CMDMODE(0, "show-help", &mode, + N_("show help"), HELP), OPT_END() }; @@ -30,6 +33,8 @@ int cmd_add__helper(int argc, const char **argv, const char *prefix) if (mode == STATUS) add_i_status(); + else if (mode == HELP) + add_i_show_help(); else usage_with_options(builtin_add_helper_usage, options); From a53e591077f93ac2ce2915fb73cd59022e764052 Mon Sep 17 00:00:00 2001 From: Slavica Djukic Date: Tue, 15 Jan 2019 18:10:37 +0100 Subject: [PATCH 09/11] t3701-add-interactive: test add_i_show_help() Add test to t3701-add-interactive to verify that add_i_show_help() outputs expected content. Also, add it before changing git-add--interactive.perl's help_cmd to demonstrate that there are no changes introduced by the conversion to C. Prefix git add -i call with GIT_PAGER_IN_USE=true TERM=vt100 to force colored output on Windows. Mentored-by: Johannes Schindelin Signed-off-by: Slavica Djukic --- t/t3701-add-interactive.sh | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 65dfbc033a027d..91aaef29323276 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -639,4 +639,28 @@ test_expect_success 'add -p patch editing works with pathological context lines' test_cmp expected-2 actual ' +test_expect_success 'show help from add--helper' ' + git reset --hard && + cat >expect <<-EOF && + + *** Commands *** + 1: status 2: update 3: revert 4: add untracked + 5: patch 6: diff 7: quit 8: help + What now> status - show paths with changes + update - add working tree state to the staged set of changes + revert - revert staged set of changes back to the HEAD version + patch - pick hunks and update selectively + diff - view diff between HEAD and index + add untracked - add contents of untracked files to the staged set of changes + *** Commands *** + 1: status 2: update 3: revert 4: add untracked + 5: patch 6: diff 7: quit 8: help + What now>$SP + Bye. + EOF + test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored && + test_decode_color actual && + test_i18ncmp expect actual +' + test_done From 0019e48cf714621d9bcbbcef932fdf19f26262b8 Mon Sep 17 00:00:00 2001 From: Slavica Djukic Date: Thu, 14 Feb 2019 11:41:46 +0100 Subject: [PATCH 10/11] add--interactive.perl: use add--helper --show-help for help_cmd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change help_cmd sub in git-add--interactive.perl to use show-help command from builtin add--helper. If an error occurs, it will be reported, but the Perl script will not exit, since the add--helper is called within an eval block. Just like the change where the Perl script calls the add--helper to print the numstat, also here we forgo adding a regression test: the Perl script is on its way out (and this patch is part of that journey). Mentored-by: Johannes Schindelin Signed-off-by: Slavica Djukic Signed-off-by: Ævar Arnfjörð Bjarmason --- git-add--interactive.perl | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index c2c6b4d5e38b10..88b7be6602493d 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -1718,16 +1718,8 @@ sub quit_cmd { } sub help_cmd { -# TRANSLATORS: please do not translate the command names -# 'status', 'update', 'revert', etc. - print colored $help_color, __ <<'EOF' ; -status - show paths with changes -update - add working tree state to the staged set of changes -revert - revert staged set of changes back to the HEAD version -patch - pick hunks and update selectively -diff - view diff between HEAD and index -add untracked - add contents of untracked files to the staged set of changes -EOF + my @help_cmd = ("git", "add--helper", "--show-help"); + !system(@help_cmd) or die "@help_cmd exited with code $?"; } sub process_args { From 2d7ba49c3509a22d5a78698ad1afaeb2bb68c704 Mon Sep 17 00:00:00 2001 From: Slavica Djukic Date: Sun, 3 Mar 2019 13:19:27 +0100 Subject: [PATCH 11/11] add-interactive.c: make list_and_choose more type-independent This is "draft-commit" of changes that will be introduced so that list_and_choose can deal with variety of data types. After review, changes will be reflected in the corresponding commits resulting with deleting this commit. Mentored-by: Johannes Schindelin Signed-off-by: Slavica Djukic --- add-interactive.c | 61 ++++++++++++++--------------------------------- 1 file changed, 18 insertions(+), 43 deletions(-) diff --git a/add-interactive.c b/add-interactive.c index a4df97cd80ec42..d4f02a5ab5c4ce 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -47,27 +47,20 @@ struct list_and_choose_options { struct choice { struct hashmap_entry e; - union { - void (*command_fn)(void); - struct { - struct adddel index, worktree; - } file; - } u; size_t prefix_length; const char *name; }; -enum choice_type { - FILE_STAT, - COMMAND +struct file_choice { + struct choice choice; + struct adddel index, worktree; }; struct choices { struct choice **choices; size_t alloc, nr; - enum choice_type type; }; -#define CHOICES_INIT { NULL, 0, 0, 0 } +#define CHOICES_INIT { NULL, 0, 0 } struct prefix_entry { struct hashmap_entry e; @@ -554,14 +547,16 @@ static struct choices *list_and_choose(struct choices *data, else strbuf_add(&print_buf, c->name, strlen(c->name)); - if ((data->type == FILE_STAT) && (!opts->list_only_file_names)) { + if ((opts->list_only) && (!opts->list_only_file_names)) { + struct file_choice *choice = (struct file_choice*)c; + strbuf_reset(&print); strbuf_reset(&index_changes); strbuf_reset(&worktree_changes); - populate_wi_changes(&worktree_changes, &c->u.file.worktree, + populate_wi_changes(&worktree_changes, &choice->worktree, "nothing"); - populate_wi_changes(&index_changes, &c->u.file.index, + populate_wi_changes(&index_changes, &choice->index, "unchanged"); strbuf_addbuf(&print, &print_buf); @@ -713,36 +708,18 @@ static struct choices *list_and_choose(struct choices *data, return results; } -static struct choice *make_choice(const char *name ) +static struct file_choice *add_file_choice(struct choices *choices, + struct file_stat *file) { - struct choice *choice; - - FLEXPTR_ALLOC_STR(choice, name, name); - return choice; -} + struct file_choice *choice; -static struct choice *add_choice(struct choices *choices, - struct file_stat *file, struct command *command) -{ - struct choice *choice; - - if (file && command) - BUG("either file_stat or command should be NULL\n"); - - switch (choices->type) { - case FILE_STAT: - choice = make_choice(file->name); - choice->u.file.index = file->index; - choice->u.file.worktree = file->worktree; - break; - case COMMAND: - choice = make_choice(command->name); - choice->u.command_fn = command->command_fn; - break; - } + FLEXPTR_ALLOC_STR(choice, choice.name, file->name); + choice->choice.prefix_length = 0; + choice->index = file->index; + choice->worktree = file->worktree; ALLOC_GROW(choices->choices, choices->nr + 1, choices->alloc); - choices->choices[choices->nr++] = choice; + choices->choices[choices->nr++] = (struct choice*)choice; return choice; } @@ -766,8 +743,6 @@ void add_i_status(void) struct choices choices = CHOICES_INIT; const char *modified_fmt = _("%12s %12s %s"); - choices.type = FILE_STAT; - opts.list_only = 1; opts.header_indent = HEADER_INDENT; strbuf_init(&opts.header, 0); @@ -782,7 +757,7 @@ void add_i_status(void) } for (i = 0; files[i]; i++) - add_choice(&choices, files[i], NULL); + add_file_choice(&choices, files[i]); list_and_choose(&choices, &opts); putchar('\n');