Create a builtin helper for git-add--interactive, which right now is only able to reproduce git-add--interactive.perl's status_cmd() function, providing a summarized diff numstat to the user.
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. Signed-off-by: Daniel Ferreira <bnm...@gmail.com> --- .gitignore | 1 + Makefile | 1 + builtin.h | 1 + builtin/add-interactive--helper.c | 258 ++++++++++++++++++++++++++++++++++++++ git.c | 1 + 5 files changed, 262 insertions(+) create mode 100644 builtin/add-interactive--helper.c diff --git a/.gitignore b/.gitignore index 833ef3b..0d6cfe4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ /git /git-add /git-add--interactive +/git-add-interactive--helper /git-am /git-annotate /git-apply diff --git a/Makefile b/Makefile index e35542e..842fce2 100644 --- a/Makefile +++ b/Makefile @@ -873,6 +873,7 @@ LIB_OBJS += xdiff-interface.o LIB_OBJS += zlib.o BUILTIN_OBJS += builtin/add.o +BUILTIN_OBJS += builtin/add-interactive--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 9e4a898..3d6a0ab 100644 --- a/builtin.h +++ b/builtin.h @@ -30,6 +30,7 @@ extern int textconv_object(const char *path, unsigned mode, const struct object_ extern int is_builtin(const char *s); extern int cmd_add(int argc, const char **argv, const char *prefix); +extern int cmd_add_interactive__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-interactive--helper.c b/builtin/add-interactive--helper.c new file mode 100644 index 0000000..97ca1b3 --- /dev/null +++ b/builtin/add-interactive--helper.c @@ -0,0 +1,258 @@ +#include "builtin.h" +#include "color.h" +#include "diff.h" +#include "diffcore.h" +#include "revision.h" + +#define ADD_INTERACTIVE_HEADER_INDENT " " + +enum add_interactive_collection_mode { + COLLECTION_MODE_NONE, + COLLECTION_MODE_WORKTREE, + COLLECTION_MODE_INDEX +}; + +struct add_interactive_file_status { + int selected; + + char path[PATH_MAX]; + + int lines_added_index; + int lines_deleted_index; + int lines_added_worktree; + int lines_deleted_worktree; +}; + +struct add_interactive_status { + enum add_interactive_collection_mode current_mode; + + const char *reference; + struct pathspec pathspec; + + int file_count; + struct add_interactive_file_status *files; +}; + +static int add_interactive_use_color = -1; +enum color_add_interactive { + ADD_INTERACTIVE_PROMPT, + ADD_INTERACTIVE_HEADER, + ADD_INTERACTIVE_HELP, + ADD_INTERACTIVE_ERROR +}; + +static char add_interactive_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 *add_interactive_get_color(enum color_add_interactive ix) +{ + if (want_color(add_interactive_use_color)) + return add_interactive_colors[ix]; + return ""; +} + +static int parse_add_interactive_color_slot(const char *slot) +{ + if (!strcasecmp(slot, "prompt")) + return ADD_INTERACTIVE_PROMPT; + if (!strcasecmp(slot, "header")) + return ADD_INTERACTIVE_HEADER; + if (!strcasecmp(slot, "help")) + return ADD_INTERACTIVE_HELP; + if (!strcasecmp(slot, "error")) + return ADD_INTERACTIVE_ERROR; + + return -1; +} + +static int git_add_interactive_config(const char *var, + const char *value, void *cbdata) +{ + const char *name; + + if (!strcmp(var, "color.interactive")) { + add_interactive_use_color = git_config_colorbool(var, value); + return 0; + } + + if (skip_prefix(var, "color.interactive", &name)) { + int slot = parse_add_interactive_color_slot(name); + if (slot < 0) + return 0; + if (!value) + return config_error_nonbool(var); + return color_parse(value, add_interactive_colors[slot]); + } + + return git_default_config(var, value, cbdata); +} + +static void add_interactive_status_collect_changed_cb(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + struct add_interactive_status *s = data; + struct diffstat_t stat; + int i, j; + + if (!q->nr) + return; + + memset(&stat, 0, sizeof(struct diffstat_t)); + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p; + p = q->queue[i]; + diff_flush_stat(p, options, &stat); + } + + for (i = 0; i < stat.nr; i++) { + int file_index = s->file_count; + for (j = 0; j < s->file_count; j++) { + if (!strcmp(s->files[j].path, stat.files[i]->name)) { + file_index = j; + break; + } + } + + if (file_index == s->file_count) { + s->file_count++; + s->files = realloc(s->files, + (q->nr + s->file_count) * sizeof(*s->files)); + memset(&s->files[file_index], 0, + sizeof(struct add_interactive_file_status)); + } + + memcpy(s->files[file_index].path, stat.files[i]->name, + strlen(stat.files[i]->name) + 1); + if (s->current_mode == COLLECTION_MODE_WORKTREE) { + s->files[file_index].lines_added_worktree = stat.files[i]->added; + s->files[file_index].lines_deleted_worktree = stat.files[i]->deleted; + } else if (s->current_mode == COLLECTION_MODE_INDEX) { + s->files[file_index].lines_added_index = stat.files[i]->added; + s->files[file_index].lines_deleted_index = stat.files[i]->deleted; + } + } +} + +static void add_interactive_status_collect_changes_worktree(struct add_interactive_status *s) +{ + struct rev_info rev; + + s->current_mode = COLLECTION_MODE_WORKTREE; + + init_revisions(&rev, NULL); + setup_revisions(0, NULL, &rev, NULL); + + rev.max_count = 0; + + rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = add_interactive_status_collect_changed_cb; + rev.diffopt.format_callback_data = s; + + run_diff_files(&rev, 0); +} + +static void add_interactive_status_collect_changes_index(struct add_interactive_status *s) +{ + struct rev_info rev; + struct setup_revision_opt opt; + + s->current_mode = COLLECTION_MODE_INDEX; + + init_revisions(&rev, NULL); + memset(&opt, 0, sizeof(opt)); + opt.def = s->reference; + setup_revisions(0, NULL, &rev, &opt); + + rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = add_interactive_status_collect_changed_cb; + rev.diffopt.format_callback_data = s; + + run_diff_index(&rev, 1); +} + +static void list_modified_into_status(struct add_interactive_status *s) +{ + add_interactive_status_collect_changes_worktree(s); + add_interactive_status_collect_changes_index(s); +} + +static void print_modified(void) +{ + int i; + struct add_interactive_status s; + const char *modified_fmt = _("%12s %12s %s"); + const char *header_color = add_interactive_get_color(ADD_INTERACTIVE_HEADER); + unsigned char sha1[20]; + + if (read_cache() < 0) + return; + + s.current_mode = COLLECTION_MODE_NONE; + s.reference = !get_sha1("HEAD", sha1) ? "HEAD": EMPTY_TREE_SHA1_HEX; + s.file_count = 0; + s.files = NULL; + list_modified_into_status(&s); + + printf(ADD_INTERACTIVE_HEADER_INDENT); + color_fprintf(stdout, header_color, modified_fmt, _("staged"), + _("unstaged"), _("path")); + printf("\n"); + + for (i = 0; i < s.file_count; i++) { + struct add_interactive_file_status f = s.files[i]; + char selection = f.selected ? '*' : ' '; + + char worktree_changes[50]; + char index_changes[50]; + + if (f.lines_added_worktree != 0 || f.lines_deleted_worktree != 0) + snprintf(worktree_changes, 50, "+%d/-%d", f.lines_added_worktree, + f.lines_deleted_worktree); + else + snprintf(worktree_changes, 50, "%s", _("nothing")); + + if (f.lines_added_index != 0 || f.lines_deleted_index != 0) + snprintf(index_changes, 50, "+%d/-%d", f.lines_added_index, + f.lines_deleted_index); + else + snprintf(index_changes, 50, "%s", _("unchanged")); + + printf("%c%2d: ", selection, i + 1); + printf(modified_fmt, index_changes, worktree_changes, f.path); + printf("\n"); + } +} + +static void status_cmd(void) +{ + print_modified(); +} + +static const char add_interactive_helper_usage[] = +"git add-interactive--helper <command>"; + +int cmd_add_interactive__helper(int argc, const char **argv, const char *prefix) +{ + int i, found_opt = 0; + + git_config(git_add_interactive_config, NULL); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (!strcmp(arg, "--status")) { + status_cmd(); + found_opt = 1; + } + } + + if (!found_opt) + usage(add_interactive_helper_usage); + + return 0; +} diff --git a/git.c b/git.c index 8ff44f0..796971e 100644 --- a/git.c +++ b/git.c @@ -391,6 +391,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-interactive--helper", cmd_add_interactive__helper, RUN_SETUP | NEED_WORK_TREE }, { "am", cmd_am, RUN_SETUP | NEED_WORK_TREE }, { "annotate", cmd_annotate, RUN_SETUP }, { "apply", cmd_apply, RUN_SETUP_GENTLY }, -- 2.7.4 (Apple Git-66)