All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Daniel Ferreira via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: "Ævar Arnfjörð Bjarmason" <avarab@gmail.com>,
	"Junio C Hamano" <gitster@pobox.com>,
	"Daniel Ferreira" <bnmvco@gmail.com>
Subject: [PATCH v4 3/7] add-interactive.c: implement status command
Date: Fri, 25 Jan 2019 04:23:33 -0800 (PST)	[thread overview]
Message-ID: <d247ef69fe2c9d017b8d363de554a52269c0d281.1548419009.git.gitgitgadget@gmail.com> (raw)
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


  parent reply	other threads:[~2019-01-25 12:23 UTC|newest]

Thread overview: 76+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-12-20 12:34 [PATCH 0/7] Turn git add-i into built-in Johannes Schindelin
2018-12-20 12:09 ` [PATCH 1/7] diff: export diffstat interface Daniel Ferreira via GitGitGadget
2018-12-20 12:09 ` [PATCH 2/7] add--helper: create builtin helper for interactive add Daniel Ferreira via GitGitGadget
2018-12-20 12:09 ` [PATCH 3/7] add-interactive.c: implement status command Daniel Ferreira via GitGitGadget
2018-12-20 12:09 ` [PATCH 4/7] add--interactive.perl: use add--helper --status for status_cmd Daniel Ferreira via GitGitGadget
2018-12-20 12:09 ` [PATCH 5/7] add-interactive.c: implement show-help command Slavica Djukic via GitGitGadget
2019-01-14 11:12   ` Phillip Wood
2018-12-20 12:09 ` [PATCH 6/7] Git.pm: introduce environment variable GIT_TEST_PRETEND_TTY Slavica Djukic via GitGitGadget
2019-01-14 11:13   ` Phillip Wood
2019-01-15 12:45     ` Slavica Djukic
2019-01-15 13:50     ` Johannes Schindelin
2019-01-15 16:09       ` Phillip Wood
2018-12-20 12:09 ` [PATCH 7/7] add--interactive.perl: use add--helper --show-help for help_cmd Slavica Djukic via GitGitGadget
2019-01-14 11:17   ` Phillip Wood
2018-12-20 12:37 ` [PATCH 0/7] Turn git add-i into built-in Johannes Schindelin
2019-01-11 14:09 ` Slavica Djukic
2019-01-18  7:47 ` [PATCH v2 " Slavica Đukić via GitGitGadget
2019-01-18  7:47   ` [PATCH v2 1/7] diff: export diffstat interface Daniel Ferreira via GitGitGadget
2019-01-18  7:47   ` [PATCH v2 2/7] add--helper: create builtin helper for interactive add Daniel Ferreira via GitGitGadget
2019-01-18  7:47   ` [PATCH v2 3/7] add-interactive.c: implement status command Daniel Ferreira via GitGitGadget
2019-01-18  7:47   ` [PATCH v2 4/7] add--interactive.perl: use add--helper --status for status_cmd Daniel Ferreira via GitGitGadget
2019-01-18  7:47   ` [PATCH v2 5/7] add-interactive.c: implement show-help command Slavica Djukic via GitGitGadget
2019-01-18 11:20     ` Phillip Wood
2019-01-18 12:19       ` Slavica Djukic
     [not found]       ` <VI1PR05MB577331CCE110D2EAE325927CA69C0@VI1PR05MB5773.eurprd05.prod.outlook.com>
2019-01-18 14:25         ` Phillip Wood
2019-01-18 20:40           ` Johannes Schindelin
2019-01-18  7:47   ` [PATCH v2 6/7] t3701-add-interactive: test add_i_show_help() Slavica Djukic via GitGitGadget
2019-01-18 11:23     ` Phillip Wood
2019-01-18  7:47   ` [PATCH v2 7/7] add--interactive.perl: use add--helper --show-help for help_cmd Slavica Djukic via GitGitGadget
2019-01-21  9:13   ` [PATCH v3 0/7] Turn git add-i into built-in Slavica Đukić via GitGitGadget
2019-01-21  9:13     ` [PATCH v3 1/7] diff: export diffstat interface Daniel Ferreira via GitGitGadget
2019-01-21  9:13     ` [PATCH v3 3/7] add-interactive.c: implement status command Daniel Ferreira via GitGitGadget
2019-01-21  9:13     ` [PATCH v3 2/7] add--helper: create builtin helper for interactive add Daniel Ferreira via GitGitGadget
2019-01-21  9:13     ` [PATCH v3 4/7] add--interactive.perl: use add--helper --status for status_cmd Daniel Ferreira via GitGitGadget
2019-01-21  9:13     ` [PATCH v3 5/7] add-interactive.c: implement show-help command Slavica Djukic via GitGitGadget
2019-01-21  9:13     ` [PATCH v3 6/7] t3701-add-interactive: test add_i_show_help() Slavica Djukic via GitGitGadget
2019-01-25 11:01       ` Phillip Wood
2019-01-25 11:36         ` Slavica Djukic
2019-01-21  9:13     ` [PATCH v3 7/7] add--interactive.perl: use add--helper --show-help for help_cmd Slavica Djukic via GitGitGadget
2019-01-21  9:59       ` Ævar Arnfjörð Bjarmason
2019-01-21 11:59         ` Slavica Djukic
2019-01-25 12:23     ` [PATCH v4 0/7] Turn git add-i into built-in Slavica Đukić via GitGitGadget
2019-01-25 12:23       ` [PATCH v4 1/7] diff: export diffstat interface Daniel Ferreira via GitGitGadget
2019-01-25 12:23       ` [PATCH v4 2/7] add--helper: create builtin helper for interactive add Daniel Ferreira via GitGitGadget
2019-01-25 12:23       ` Daniel Ferreira via GitGitGadget [this message]
2019-01-25 12:23       ` [PATCH v4 4/7] add--interactive.perl: use add--helper --status for status_cmd Daniel Ferreira via GitGitGadget
2019-01-25 12:23       ` [PATCH v4 5/7] add-interactive.c: implement show-help command Slavica Djukic via GitGitGadget
2019-01-25 12:23       ` [PATCH v4 6/7] t3701-add-interactive: test add_i_show_help() Slavica Djukic via GitGitGadget
2019-01-25 12:23       ` [PATCH v4 7/7] add--interactive.perl: use add--helper --show-help for help_cmd Slavica Djukic via GitGitGadget
2019-01-25 12:37       ` [PATCH v4 0/7] Turn git add-i into built-in Slavica Djukic
2019-02-01 14:37         ` Phillip Wood
2019-02-20 11:41       ` [PATCH v5 00/10] " Slavica Đukić via GitGitGadget
2019-02-20 11:41         ` [PATCH v5 01/10] diff: export diffstat interface Daniel Ferreira via GitGitGadget
2019-02-21 17:53           ` Junio C Hamano
2019-02-22  9:03             ` Slavica Djukic
2019-02-20 11:41         ` [PATCH v5 02/10] add--helper: create builtin helper for interactive add Daniel Ferreira via GitGitGadget
2019-02-21 17:56           ` Junio C Hamano
2019-03-08 20:48             ` Johannes Schindelin
2019-02-20 11:41         ` [PATCH v5 03/10] add-interactive.c: implement list_modified Slavica Djukic via GitGitGadget
2019-02-21 19:06           ` Junio C Hamano
2019-02-21 20:27             ` Junio C Hamano
2019-02-22 12:18               ` Slavica Djukic
2019-02-22 11:35             ` Slavica Djukic
2019-02-20 11:41         ` [PATCH v5 04/10] add-interactive.c: implement list_and_choose Slavica Djukic via GitGitGadget
2019-02-22 21:46           ` Junio C Hamano
2019-03-01 11:20             ` Slavica Djukic
2019-02-20 11:41         ` [PATCH v5 06/10] add--interactive.perl: use add--helper --status for status_cmd Daniel Ferreira via GitGitGadget
2019-02-20 11:41         ` [PATCH v5 05/10] add-interactive.c: implement status command Slavica Djukic via GitGitGadget
2019-02-22 22:11           ` Junio C Hamano
2019-03-01 11:08             ` Slavica Djukic
2019-02-20 11:41         ` [PATCH v5 07/10] add-interactive.c: add support for list_only option Slavica Djukic via GitGitGadget
2019-02-20 11:41         ` [PATCH v5 08/10] add-interactive.c: implement show-help command Slavica Djukic via GitGitGadget
2019-02-20 11:41         ` [PATCH v5 09/10] t3701-add-interactive: test add_i_show_help() Slavica Djukic via GitGitGadget
2019-02-20 11:41         ` [PATCH v5 10/10] add--interactive.perl: use add--helper --show-help for help_cmd Slavica Djukic via GitGitGadget
2019-02-22  4:53         ` [PATCH v5 00/10] Turn git add-i into built-in Junio C Hamano
2019-03-04 10:49         ` End of Outreachy internship Slavica Djukic

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=d247ef69fe2c9d017b8d363de554a52269c0d281.1548419009.git.gitgitgadget@gmail.com \
    --to=gitgitgadget@gmail.com \
    --cc=avarab@gmail.com \
    --cc=bnmvco@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.