git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 2/3] add--interactive: add builtin helper for interactive add

From
Daniel Ferreira <bnmvco@gmail.com>
Date
May 5, 2017, 18:43 UTC
Message-ID
<1494009820-2090-3-git-send-email-bnmvco@gmail.com>
In-Reply-To
<1494009820-2090-1-git-send-email-bnmvco@gmail.com>

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 <bnmvco@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)
Previous: Johannes SchindelinNext: Ævar Arnfjörð Bjarmason
Message 4 of 21 in “Port git-add--interactive.perl:status_cmd to C”
  1. 0/3 Port git-add--interactive.perl:status_cmd to CDaniel Ferreira, May 5, 2017
  2. 1/3 diff: export diffstat interfaceDaniel Ferreira, May 5, 2017
  3. Johannes SchindelinMay 5, 2017
  4. 2/3 add--interactive: add builtin helper for interactive addDaniel Ferreira, May 5, 2017
  5. Ævar Arnfjörð BjarmasonMay 5, 2017
  6. Johannes SchindelinMay 5, 2017
  7. Ævar Arnfjörð BjarmasonMay 5, 2017
  8. Johannes SchindelinMay 5, 2017
  9. Ævar Arnfjörð BjarmasonMay 5, 2017
  10. Johannes SchindelinMay 8, 2017
  11. Daniel Ferreira (theiostream)May 5, 2017
  12. Ævar Arnfjörð BjarmasonMay 5, 2017
  13. Johannes SchindelinMay 8, 2017
  14. 3/3 add--interactive: use add-interactive--helper for status_cmdDaniel Ferreira, May 5, 2017
  15. Johannes SchindelinMay 5, 2017
  16. Ævar Arnfjörð BjarmasonMay 5, 2017
  17. Johannes SchindelinMay 5, 2017
  18. Jonathan NiederMay 5, 2017
  19. Johannes SchindelinMay 5, 2017
  20. Daniel Ferreira (theiostream)May 5, 2017
  21. Johannes SchindelinMay 8, 2017

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.