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)

Reply via email to