This is the first version of a patch series to start porting git-add--interactive from Perl to C. Daniel Ferreira's patch series used as a head start: https://public-inbox.org/git/1494907234-28903-1-git-send-email-bnmvco@xxxxxxxxx/t/#u Changes since v4: * rename print_modified to list_modifed * the big change was implementing list_and_choose, which resulted in code refactoring, i.e. separating list_modified and status_cmd and making status_cmd use both list_modified and list_and_choose * implement struct choice instead of struct stuff_item as main data structure for list_and_choose * introduce list_only option and implement support for !list_only users * introduce highlighting of unique prefixes Note that authorship handling is slightly changed. In some of the commits, I used Original-patch-by instead of listing Daniel Ferreira as author. Also, I would like to point out that my Outreachy internship is going to finish on March 4 and I would really appreciate reviews before it does. Daniel Ferreira (3): diff: export diffstat interface add--helper: create builtin helper for interactive add add--interactive.perl: use add--helper --status for status_cmd Slavica Djukic (7): add-interactive.c: implement list_modified add-interactive.c: implement list_and_choose add-interactive.c: implement status command add-interactive.c: add support for list_only option add-interactive.c: implement show-help command t3701-add-interactive: test add_i_show_help() add--interactive.perl: use add--helper --show-help for help_cmd .gitignore | 1 + Makefile | 2 + add-interactive.c | 819 +++++++++++++++++++++++++++++++++++++ add-interactive.h | 10 + builtin.h | 1 + builtin/add--helper.c | 43 ++ diff.c | 36 +- diff.h | 18 + git-add--interactive.perl | 17 +- git.c | 1 + t/t3701-add-interactive.sh | 24 ++ 11 files changed, 937 insertions(+), 35 deletions(-) create mode 100644 add-interactive.c create mode 100644 add-interactive.h create mode 100644 builtin/add--helper.c base-commit: ca1b4116483b397e78483376296bcd23916ab553 Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-103%2FslavicaDj%2Fadd-i-v5 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-103/slavicaDj/add-i-v5 Pull-Request: https://github.com/gitgitgadget/git/pull/103 Range-diff vs v4: 1: 737767b6f4 ! 1: d839f0c082 diff: export diffstat interface @@ -11,6 +11,7 @@ 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 <Johannes.Schindelin@xxxxxx> Signed-off-by: Daniel Ferreira <bnmvco@xxxxxxxxx> Signed-off-by: Slavica Djukic <slawica92@xxxxxxxxxxx> 2: 91b1963125 ! 2: 304c3863b1 add--helper: create builtin helper for interactive add @@ -2,8 +2,8 @@ add--helper: create builtin helper for interactive add - Create a builtin helper for git-add--interactive, which right now is not - able to do anything. + 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 @@ -13,6 +13,7 @@ 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 <Johannes.Schindelin@xxxxxx> Signed-off-by: Daniel Ferreira <bnmvco@xxxxxxxxx> Signed-off-by: Slavica Djukic <slawica92@xxxxxxxxxxx> 3: d247ef69fe ! 3: 8790ffaa39 add-interactive.c: implement status command @@ -1,17 +1,12 @@ -Author: Daniel Ferreira <bnmvco@xxxxxxxxx> +Author: Slavica Djukic <slawica92@xxxxxxxxxxx> - add-interactive.c: implement status command + add-interactive.c: implement list_modified - Add new files: add-interactive.c and add-interactive.h, which - will be used for implementing "application logic" of git add -i, - whereas add--helper.c will be used mostly for parsing the command line. - 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. + Implement list_modified from Perl, which will be used + by most of the *_cmd functions, including status in the + following commit. - Implement add --interactive's status command in add-interactive.c and - use it in builtin add--helper.c. - - It prints a numstat comparing changed files between a) the worktree and + 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 @@ -19,11 +14,13 @@ combination of a hashmap and qsort() to print the result in O(n) + O(n lg n) complexity. - This is the first interactive add command implemented in C of those - anticipated by the previous commit, which introduced - the add--helper built-in. + 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. - Signed-off-by: Daniel Ferreira <bnmvco@xxxxxxxxx> + Mentored-by: Johannes Schindelin <Johannes.Schindelin@xxxxxx> + Original-patch-by: Daniel Ferreira <bnmvco@xxxxxxxxx> Signed-off-by: Slavica Djukic <slawica92@xxxxxxxxxxx> diff --git a/Makefile b/Makefile @@ -43,7 +40,6 @@ --- /dev/null +++ b/add-interactive.c @@ -+#include "add-interactive.h" +#include "cache.h" +#include "commit.h" +#include "color.h" @@ -51,8 +47,6 @@ +#include "diffcore.h" +#include "revision.h" + -+#define HEADER_INDENT " " -+ +enum collection_phase { + WORKTREE, + INDEX @@ -75,66 +69,8 @@ + struct hashmap file_map; +}; + -+static int use_color = -1; -+enum color_add_i { -+ COLOR_PROMPT, -+ COLOR_HEADER, -+ COLOR_HELP, -+ COLOR_ERROR -+}; -+ -+static char 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 colors[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, colors[slot]); -+ } -+ -+ return git_default_config(var, value, cbdata); -+} -+ +static int hash_cmp(const void *unused_cmp_data, const void *entry, -+ const void *entry_or_key, const void *keydata) ++ 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; @@ -151,8 +87,8 @@ +} + +static void collect_changes_cb(struct diff_queue_struct *q, -+ struct diff_options *options, -+ void *data) ++ struct diff_options *options, ++ void *data) +{ + struct collection_status *s = data; + struct diffstat_t stat = { 0 }; @@ -222,128 +158,73 @@ + run_diff_index(&rev, 1); +} + -+void add_i_print_modified(void) ++static int is_inital_commit(void) +{ -+ int i = 0; -+ struct collection_status s; -+ /* TRANSLATORS: you can adjust this to align "git add -i" status menu */ -+ const char *modified_fmt = _("%12s %12s %s"); -+ const char *header_color = get_color(COLOR_HEADER); + struct object_id sha1; ++ if (get_oid("HEAD", &sha1)) ++ return 1; ++ return 0; ++} ++ ++static const char *get_diff_reference(void) ++{ ++ if(is_inital_commit()) ++ return empty_tree_oid_hex(); ++ return "HEAD"; ++} ++ ++static void filter_files(const char *filter, struct hashmap *file_map, ++ struct file_stat **files) ++{ ++ ++ for (int i = 0; i < hashmap_get_size(file_map); i++) { ++ struct file_stat *f = files[i]; ++ ++ if ((!(f->worktree.added || f->worktree.deleted)) && ++ (!strcmp(filter, "file-only"))) ++ hashmap_remove(file_map, f, NULL); + ++ if ((!(f->index.added || f->index.deleted)) && ++ (!strcmp(filter, "index-only"))) ++ hashmap_remove(file_map, f, NULL); ++ } ++} ++ ++static struct collection_status *list_modified(struct repository *r, const char *filter) ++{ ++ int i = 0; ++ struct collection_status *s = xcalloc(1, sizeof(*s)); + struct hashmap_iter iter; + struct file_stat **files; + struct file_stat *entry; + -+ if (read_cache() < 0) -+ return; ++ if (repo_read_index(r) < 0) { ++ printf("\n"); ++ return NULL; ++ } + -+ s.reference = !get_oid("HEAD", &sha1) ? "HEAD": empty_tree_oid_hex(); -+ hashmap_init(&s.file_map, hash_cmp, NULL, 0); ++ s->reference = get_diff_reference(); ++ hashmap_init(&s->file_map, hash_cmp, NULL, 0); + -+ collect_changes_worktree(&s); -+ collect_changes_index(&s); ++ collect_changes_worktree(s); ++ collect_changes_index(s); + -+ if (hashmap_get_size(&s.file_map) < 1) { ++ if (hashmap_get_size(&s->file_map) < 1) { + printf("\n"); -+ return; ++ return NULL; + } + -+ printf(HEADER_INDENT); -+ color_fprintf(stdout, header_color, modified_fmt, _("staged"), -+ _("unstaged"), _("path")); -+ printf("\n"); -+ -+ hashmap_iter_init(&s.file_map, &iter); ++ hashmap_iter_init(&s->file_map, &iter); + -+ files = xcalloc(hashmap_get_size(&s.file_map), sizeof(struct file_stat *)); ++ files = xcalloc(hashmap_get_size(&s->file_map), sizeof(struct file_stat *)); + while ((entry = hashmap_iter_next(&iter))) { + files[i++] = entry; + } -+ QSORT(files, hashmap_get_size(&s.file_map), alphabetical_cmp); ++ QSORT(files, hashmap_get_size(&s->file_map), alphabetical_cmp); + -+ for (i = 0; i < hashmap_get_size(&s.file_map); i++) { -+ struct file_stat *f = files[i]; -+ -+ char worktree_changes[50]; -+ char index_changes[50]; -+ -+ if (f->worktree.added || f->worktree.deleted) -+ snprintf(worktree_changes, 50, "+%"PRIuMAX"/-%"PRIuMAX, f->worktree.added, -+ f->worktree.deleted); -+ else -+ snprintf(worktree_changes, 50, "%s", _("nothing")); -+ -+ if (f->index.added || f->index.deleted) -+ snprintf(index_changes, 50, "+%"PRIuMAX"/-%"PRIuMAX, f->index.added, -+ f->index.deleted); -+ else -+ snprintf(index_changes, 50, "%s", _("unchanged")); -+ -+ printf(" %2d: ", i + 1); -+ printf(modified_fmt, index_changes, worktree_changes, f->name); -+ printf("\n"); -+ } -+ printf("\n"); ++ if (filter) ++ filter_files(filter, &s->file_map, files); + + free(files); -+ hashmap_free(&s.file_map, 1); ++ return s; +} - - diff --git a/add-interactive.h b/add-interactive.h - new file mode 100644 - --- /dev/null - +++ b/add-interactive.h -@@ -+#ifndef ADD_INTERACTIVE_H -+#define ADD_INTERACTIVE_H -+ -+int add_i_config(const char *var, const char *value, void *cbdata); -+ -+void add_i_print_modified(void); -+ -+#endif - \ No newline at end of file - - diff --git a/builtin/add--helper.c b/builtin/add--helper.c - --- a/builtin/add--helper.c - +++ b/builtin/add--helper.c -@@ -+#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 <command>"), -+ 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_print_modified(); -+ else -+ usage_with_options(builtin_add_helper_usage, -+ options); -+ - return 0; - } -: ---------- > 4: a97b29d274 add-interactive.c: implement list_and_choose -: ---------- > 5: 9a72aabe6c add-interactive.c: implement status command 4: fb3f9378ac ! 6: 883963ee6e add--interactive.perl: use add--helper --status for status_cmd @@ -14,6 +14,7 @@ the fact that it would be hard to test, we'll pass on adding a regression test for this. + Mentored-by: Johannes Schindelin <Johannes.Schindelin@xxxxxx> Signed-off-by: Daniel Ferreira <bnmvco@xxxxxxxxx> Signed-off-by: Slavica Djukic <slawica92@xxxxxxxxxxx> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@xxxxxxxxx> -: ---------- > 7: 7912f37517 add-interactive.c: add support for list_only option 5: ab16afd1d5 ! 8: 441321fc3d add-interactive.c: implement show-help command @@ -10,31 +10,32 @@ handle_builtin and re-routed to the help command, without ever calling cmd_add__helper(). + Mentored-by: Johannes Schindelin <Johannes.Schindelin@xxxxxx> Signed-off-by: Slavica Djukic <slawica92@xxxxxxxxxxx> diff --git a/add-interactive.c b/add-interactive.c --- a/add-interactive.c +++ b/add-interactive.c @@ - free(files); - hashmap_free(&s.file_map, 1); + free(s); + 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")); ++ _("show paths with changes")); + color_fprintf_ln(stdout, help_color, "update - %s", -+ _("add working tree state to the staged set of changes")); ++ _("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")); ++ _("revert staged set of changes back to the HEAD version")); + color_fprintf_ln(stdout, help_color, "patch - %s", -+ _("pick hunks and update selectively")); ++ _("pick hunks and update selectively")); + color_fprintf_ln(stdout, help_color, "diff - %s", -+ _("view diff between HEAD and index")); ++ _("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")); ++ _("add contents of untracked files to the staged set of changes")); +} diff --git a/add-interactive.h b/add-interactive.h @@ -42,13 +43,11 @@ +++ b/add-interactive.h @@ - void add_i_print_modified(void); + void add_i_status(void); --#endif - \ No newline at end of file +void add_i_show_help(void); + -+#endif + #endif diff --git a/builtin/add--helper.c b/builtin/add--helper.c --- a/builtin/add--helper.c @@ -66,16 +65,16 @@ @@ struct option options[] = { OPT_CMDMODE(0, "status", &mode, - N_("print status information with diffstat"), STATUS), + N_("print status information with diffstat"), STATUS), + OPT_CMDMODE(0, "show-help", &mode, -+ N_("show help"), HELP), ++ N_("show help"), HELP), OPT_END() }; @@ if (mode == STATUS) - add_i_print_modified(); + add_i_status(); + else if (mode == HELP) + add_i_show_help(); else 6: 0a27304a84 ! 9: 315ae8b211 t3701-add-interactive: test add_i_show_help() @@ -11,6 +11,7 @@ Prefix git add -i call with GIT_PAGER_IN_USE=true TERM=vt100 to force colored output on Windows. + Mentored-by: Johannes Schindelin <Johannes.Schindelin@xxxxxx> Signed-off-by: Slavica Djukic <slawica92@xxxxxxxxxxx> diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh 7: ca2a7c4375 ! 10: 2b4bdce730 add--interactive.perl: use add--helper --show-help for help_cmd @@ -12,6 +12,7 @@ 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 <Johannes.Schindelin@xxxxxx> Signed-off-by: Slavica Djukic <slawica92@xxxxxxxxxxx> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@xxxxxxxxx> -- gitgitgadget