From: Johannes Schindelin <johannes.schindelin@xxxxxx> In the `update` command of `git add -i`, we are primarily interested in the list of modified files that have worktree (i.e. unstaged) changes. At the same time, we need to determine _also_ the staged changes, to be able to produce the full added/deleted information. The Perl script version of `git add -i` has a parameter of the `list_modified()` function for that matter. In C, we can be a lot more precise, using an `enum`. The C implementation of the filter also has an easier time to avoid unnecessary work, simply by using an adaptive order of the `diff-index` and `diff-files` phases, and then skipping files in the second phase when they have not been seen in the first phase. Seeing as we change the meaning of the `phase` field, we rename it to `mode` to reflect that the order depends on the exact invocation of the `git add -i` command. Signed-off-by: Johannes Schindelin <johannes.schindelin@xxxxxx> --- add-interactive.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/add-interactive.c b/add-interactive.c index de2fccb0ef..c62d63e35b 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -344,10 +344,11 @@ static int pathname_entry_cmp(const void *unused_cmp_data, } struct collection_status { - enum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase; + enum { FROM_WORKTREE = 0, FROM_INDEX = 1 } mode; const char *reference; + unsigned skip_unseen:1; struct string_list *files; struct hashmap file_map; }; @@ -375,6 +376,9 @@ static void collect_changes_cb(struct diff_queue_struct *q, entry = hashmap_get_entry_from_hash(&s->file_map, hash, name, struct pathname_entry, ent); if (!entry) { + if (s->skip_unseen) + continue; + add_file_item(s->files, name); entry = xcalloc(sizeof(*entry), 1); @@ -385,7 +389,7 @@ static void collect_changes_cb(struct diff_queue_struct *q, } file_item = entry->item; - adddel = s->phase == FROM_INDEX ? + adddel = s->mode == FROM_INDEX ? &file_item->index : &file_item->worktree; adddel->seen = 1; adddel->add = stat.files[i]->added; @@ -396,13 +400,22 @@ static void collect_changes_cb(struct diff_queue_struct *q, free_diffstat_info(&stat); } -static int get_modified_files(struct repository *r, struct string_list *files, +enum modified_files_filter { + NO_FILTER = 0, + WORKTREE_ONLY = 1, + INDEX_ONLY = 2, +}; + +static int get_modified_files(struct repository *r, + enum modified_files_filter filter, + struct string_list *files, const struct pathspec *ps) { struct object_id head_oid; int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL); - struct collection_status s = { FROM_WORKTREE }; + struct collection_status s = { 0 }; + int i; if (discard_index(r->index) < 0 || repo_read_index_preload(r, ps, 0) < 0) @@ -412,10 +425,16 @@ static int get_modified_files(struct repository *r, struct string_list *files, s.files = files; hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0); - for (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) { + for (i = 0; i < 2; i++) { struct rev_info rev; struct setup_revision_opt opt = { 0 }; + if (filter == INDEX_ONLY) + s.mode = (i == 0) ? FROM_INDEX : FROM_WORKTREE; + else + s.mode = (i == 0) ? FROM_WORKTREE : FROM_INDEX; + s.skip_unseen = filter && i; + opt.def = is_initial ? empty_tree_oid_hex() : oid_to_hex(&head_oid); @@ -429,7 +448,7 @@ static int get_modified_files(struct repository *r, struct string_list *files, if (ps) copy_pathspec(&rev.prune_data, ps); - if (s.phase == FROM_INDEX) + if (s.mode == FROM_INDEX) run_diff_index(&rev, 1); else { rev.diffopt.flags.ignore_dirty_submodules = 1; @@ -502,7 +521,7 @@ static void print_file_item(int i, struct string_list_item *item, static int run_status(struct add_i_state *s, const struct pathspec *ps, struct string_list *files, struct list_options *opts) { - if (get_modified_files(s->r, files, ps) < 0) + if (get_modified_files(s->r, NO_FILTER, files, ps) < 0) return -1; list(s, files, opts); -- gitgitgadget