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

[PATCH v4 3/7] add-interactive.c: implement status command

From
Daniel Ferreira via GitGitGadget <gitgitgadget@gmail.com>
Date
Jan 25, 2019, 12:23 UTC
Message-ID
<d247ef69fe2c9d017b8d363de554a52269c0d281.1548419009.git.gitgitgadget@gmail.com>
In-Reply-To
<pull.103.v4.git.gitgitgadget@gmail.com>
From: Daniel Ferreira <bnmvco@gmail.com>

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 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 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.

This is the first interactive add command implemented in C of those anticipated by the previous commit, which introduced the add--helper built-in.

Signed-off-by: Daniel Ferreira <bnmvco@gmail.com>
Signed-off-by: Slavica Djukic <slawica92@hotmail.com>
---
 Makefile              |   1 +
 add-interactive.c     | 246 ++++++++++++++++++++++++++++++++++++++++++
 add-interactive.h     |   8 ++
 builtin/add--helper.c |  32 ++++++
 4 files changed, 287 insertions(+)
 create mode 100644 add-interactive.c
 create mode 100644 add-interactive.h
diff --git a/Makefile b/Makefile
index 9c84b80739..2a4a5cc37b 100644
--- a/Makefile
+++ b/Makefile
@@ -827,6 +827,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 0000000000..c55d934186
--- /dev/null
+++ b/add-interactive.c
@@ -0,0 +1,246 @@
+#include "add-interactive.h"
+#include "cache.h"
+#include "commit.h"
+#include "color.h"
+#include "config.h"
+#include "diffcore.h"
+#include "revision.h"
+
+#define HEADER_INDENT "      "
+
+enum collection_phase {
+	WORKTREE,
+	INDEX
+};
+
+struct file_stat {
+	struct hashmap_entry ent;
+	struct {
+		uintmax_t added, deleted;
+	} index, worktree;
+	char name[FLEX_ARRAY];
+};
+
+struct collection_status {
+	enum collection_phase phase;
+
+	const char *reference;
+	struct pathspec pathspec;
+
+	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 struct file_stat *e1 = entry, *e2 = entry_or_key;
+	const char *name = keydata ? keydata : e2->name;
+
+	return strcmp(e1->name, name);
+}
+
+static int alphabetical_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 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);
+
+	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->phase == WORKTREE) {
+			entry->worktree.added = stat.files[i]->added;
+			entry->worktree.deleted = stat.files[i]->deleted;
+		} else if (s->phase == INDEX) {
+			entry->index.added = stat.files[i]->added;
+			entry->index.deleted = stat.files[i]->deleted;
+		}
+	}
+}
+
+static void collect_changes_worktree(struct collection_status *s)
+{
+	struct rev_info rev;
+
+	s->phase = WORKTREE;
+
+	init_revisions(&rev, NULL);
+	setup_revisions(0, NULL, &rev, NULL);
+
+	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->phase = INDEX;
+
+	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);
+}
+
+void add_i_print_modified(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;
+
+	struct hashmap_iter iter;
+	struct file_stat **files;
+	struct file_stat *entry;
+
+	if (read_cache() < 0)
+		return;
+
+	s.reference = !get_oid("HEAD", &sha1) ? "HEAD": empty_tree_oid_hex();
+	hashmap_init(&s.file_map, hash_cmp, NULL, 0);
+
+	collect_changes_worktree(&s);
+	collect_changes_index(&s);
+
+	if (hashmap_get_size(&s.file_map) < 1) {
+		printf("\n");
+		return;
+	}
+
+	printf(HEADER_INDENT);
+	color_fprintf(stdout, header_color, modified_fmt, _("staged"),
+			_("unstaged"), _("path"));
+	printf("\n");
+
+	hashmap_iter_init(&s.file_map, &iter);
+
+	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);
+
+	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");
+
+	free(files);
+	hashmap_free(&s.file_map, 1);
+}
diff --git a/add-interactive.h b/add-interactive.h
new file mode 100644
index 0000000000..1f4747553c
--- /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_print_modified(void);
+
+#endif
\ No newline at end of file
diff --git a/builtin/add--helper.c b/builtin/add--helper.c
index 6a97f0e191..43545d9af5 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 <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;
 }
-- 
gitgitgadget
Previous: Daniel Ferreira via GitGitGadgetNext: Slavica Djukic via GitGitGadget
Message 43 of 76 in “Turn git add-i into built-in”
  1. 0/7 Turn git add-i into built-inJohannes Schindelin, Dec 20, 2018
  2. 1/7 diff: export diffstat interfaceDaniel Ferreira via GitGitGadget, Dec 20, 2018
  3. 2/7 add--helper: create builtin helper for interactive addDaniel Ferreira via GitGitGadget, Dec 20, 2018
  4. 4/7 add--interactive.perl: use add--helper --status for status_cmdDaniel Ferreira via GitGitGadget, Dec 20, 2018
  5. 3/7 add-interactive.c: implement status commandDaniel Ferreira via GitGitGadget, Dec 20, 2018
  6. 5/7 add-interactive.c: implement show-help commandSlavica Djukic via GitGitGadget, Dec 20, 2018
  7. Phillip WoodJan 14, 2019
  8. 6/7 Git.pm: introduce environment variable GIT_TEST_PRETEND_TTYSlavica Djukic via GitGitGadget, Dec 20, 2018
  9. Phillip WoodJan 14, 2019
  10. Slavica DjukicJan 15, 2019
  11. Johannes SchindelinJan 15, 2019
  12. Phillip WoodJan 15, 2019
  13. 7/7 add--interactive.perl: use add--helper --show-help for help_cmdSlavica Djukic via GitGitGadget, Dec 20, 2018
  14. Phillip WoodJan 14, 2019
  15. Johannes SchindelinDec 20, 2018
  16. Slavica DjukicJan 11, 2019
  17. 0/7 Turn git add-i into built-inSlavica Đukić via GitGitGadget, Jan 18, 2019
  18. 1/7 diff: export diffstat interfaceDaniel Ferreira via GitGitGadget, Jan 18, 2019
  19. 2/7 add--helper: create builtin helper for interactive addDaniel Ferreira via GitGitGadget, Jan 18, 2019
  20. 4/7 add--interactive.perl: use add--helper --status for status_cmdDaniel Ferreira via GitGitGadget, Jan 18, 2019
  21. 5/7 add-interactive.c: implement show-help commandSlavica Djukic via GitGitGadget, Jan 18, 2019
  22. Phillip WoodJan 18, 2019
  23. Slavica DjukicJan 18, 2019
  24. 3/7 add-interactive.c: implement status commandDaniel Ferreira via GitGitGadget, Jan 18, 2019
  25. 7/7 add--interactive.perl: use add--helper --show-help for help_cmdSlavica Djukic via GitGitGadget, Jan 18, 2019
  26. 6/7 t3701-add-interactive: test add_i_show_help()Slavica Djukic via GitGitGadget, Jan 18, 2019
  27. Phillip WoodJan 18, 2019
  28. 0/7 Turn git add-i into built-inSlavica Đukić via GitGitGadget, Jan 21, 2019
  29. 1/7 diff: export diffstat interfaceDaniel Ferreira via GitGitGadget, Jan 21, 2019
  30. 2/7 add--helper: create builtin helper for interactive addDaniel Ferreira via GitGitGadget, Jan 21, 2019
  31. 4/7 add--interactive.perl: use add--helper --status for status_cmdDaniel Ferreira via GitGitGadget, Jan 21, 2019
  32. 5/7 add-interactive.c: implement show-help commandSlavica Djukic via GitGitGadget, Jan 21, 2019
  33. 6/7 t3701-add-interactive: test add_i_show_help()Slavica Djukic via GitGitGadget, Jan 21, 2019
  34. Phillip WoodJan 25, 2019
  35. Slavica DjukicJan 25, 2019
  36. 7/7 add--interactive.perl: use add--helper --show-help for help_cmdSlavica Djukic via GitGitGadget, Jan 21, 2019
  37. Ævar Arnfjörð BjarmasonJan 21, 2019
  38. Slavica DjukicJan 21, 2019
  39. 3/7 add-interactive.c: implement status commandDaniel Ferreira via GitGitGadget, Jan 21, 2019
  40. 0/7 Turn git add-i into built-inSlavica Đukić via GitGitGadget, Jan 25, 2019
  41. 1/7 diff: export diffstat interfaceDaniel Ferreira via GitGitGadget, Jan 25, 2019
  42. 2/7 add--helper: create builtin helper for interactive addDaniel Ferreira via GitGitGadget, Jan 25, 2019
  43. 3/7 add-interactive.c: implement status commandDaniel Ferreira via GitGitGadget, Jan 25, 2019
  44. 6/7 t3701-add-interactive: test add_i_show_help()Slavica Djukic via GitGitGadget, Jan 25, 2019
  45. 5/7 add-interactive.c: implement show-help commandSlavica Djukic via GitGitGadget, Jan 25, 2019
  46. 4/7 add--interactive.perl: use add--helper --status for status_cmdDaniel Ferreira via GitGitGadget, Jan 25, 2019
  47. 7/7 add--interactive.perl: use add--helper --show-help for help_cmdSlavica Djukic via GitGitGadget, Jan 25, 2019
  48. Slavica DjukicJan 25, 2019
  49. Phillip WoodFeb 1, 2019
  50. 00/10 Turn git add-i into built-inSlavica Đukić via GitGitGadget, Feb 20, 2019
  51. 01/10 diff: export diffstat interfaceDaniel Ferreira via GitGitGadget, Feb 20, 2019
  52. Junio C HamanoFeb 21, 2019
  53. Slavica DjukicFeb 22, 2019
  54. 02/10 add--helper: create builtin helper for interactive addDaniel Ferreira via GitGitGadget, Feb 20, 2019
  55. Junio C HamanoFeb 21, 2019
  56. Johannes SchindelinMar 8, 2019
  57. 08/10 add-interactive.c: implement show-help commandSlavica Djukic via GitGitGadget, Feb 20, 2019
  58. 10/10 add--interactive.perl: use add--helper --show-help for help_cmdSlavica Djukic via GitGitGadget, Feb 20, 2019
  59. 09/10 t3701-add-interactive: test add_i_show_help()Slavica Djukic via GitGitGadget, Feb 20, 2019
  60. 06/10 add--interactive.perl: use add--helper --status for status_cmdDaniel Ferreira via GitGitGadget, Feb 20, 2019
  61. 04/10 add-interactive.c: implement list_and_chooseSlavica Djukic via GitGitGadget, Feb 20, 2019
  62. Junio C HamanoFeb 22, 2019
  63. Slavica DjukicMar 1, 2019
  64. 07/10 add-interactive.c: add support for list_only optionSlavica Djukic via GitGitGadget, Feb 20, 2019
  65. 05/10 add-interactive.c: implement status commandSlavica Djukic via GitGitGadget, Feb 20, 2019
  66. Junio C HamanoFeb 22, 2019
  67. Slavica DjukicMar 1, 2019
  68. 03/10 add-interactive.c: implement list_modifiedSlavica Djukic via GitGitGadget, Feb 20, 2019
  69. Junio C HamanoFeb 21, 2019
  70. Junio C HamanoFeb 21, 2019
  71. Slavica DjukicFeb 22, 2019
  72. Slavica DjukicFeb 22, 2019
  73. Junio C HamanoFeb 22, 2019
  74. End of Outreachy internshipSlavica Djukic, Mar 4, 2019
  75. Phillip WoodJan 18, 2019
  76. Johannes SchindelinJan 18, 2019

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.