|
1 | 1 | #include "cache.h"
|
2 | 2 | #include "add-interactive.h"
|
| 3 | +#include "diffcore.h" |
| 4 | +#include "revision.h" |
| 5 | +#include "refs.h" |
| 6 | +#include "string-list.h" |
| 7 | + |
| 8 | +struct add_i_state { |
| 9 | + struct repository *r; |
| 10 | +}; |
| 11 | + |
| 12 | +static void init_add_i_state(struct add_i_state *s, struct repository *r) |
| 13 | +{ |
| 14 | + s->r = r; |
| 15 | +} |
| 16 | + |
| 17 | +struct list_options { |
| 18 | + const char *header; |
| 19 | + void (*print_item)(int i, struct string_list_item *item, void *print_item_data); |
| 20 | + void *print_item_data; |
| 21 | +}; |
| 22 | + |
| 23 | +static void list(struct string_list *list, struct list_options *opts) |
| 24 | +{ |
| 25 | + int i; |
| 26 | + |
| 27 | + if (!list->nr) |
| 28 | + return; |
| 29 | + |
| 30 | + if (opts->header) |
| 31 | + printf("%s\n", opts->header); |
| 32 | + |
| 33 | + for (i = 0; i < list->nr; i++) { |
| 34 | + opts->print_item(i, list->items + i, opts->print_item_data); |
| 35 | + putchar('\n'); |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +struct adddel { |
| 40 | + uintmax_t add, del; |
| 41 | + unsigned seen:1, binary:1; |
| 42 | +}; |
| 43 | + |
| 44 | +struct file_item { |
| 45 | + struct adddel index, worktree; |
| 46 | +}; |
| 47 | + |
| 48 | +static void add_file_item(struct string_list *files, const char *name) |
| 49 | +{ |
| 50 | + struct file_item *item = xcalloc(sizeof(*item), 1); |
| 51 | + |
| 52 | + string_list_append(files, name)->util = item; |
| 53 | +} |
| 54 | + |
| 55 | +struct pathname_entry { |
| 56 | + struct hashmap_entry ent; |
| 57 | + const char *name; |
| 58 | + struct file_item *item; |
| 59 | +}; |
| 60 | + |
| 61 | +static int pathname_entry_cmp(const void *unused_cmp_data, |
| 62 | + const struct hashmap_entry *he1, |
| 63 | + const struct hashmap_entry *he2, |
| 64 | + const void *name) |
| 65 | +{ |
| 66 | + const struct pathname_entry *e1 = |
| 67 | + container_of(he1, const struct pathname_entry, ent); |
| 68 | + const struct pathname_entry *e2 = |
| 69 | + container_of(he2, const struct pathname_entry, ent); |
| 70 | + |
| 71 | + return strcmp(e1->name, name ? (const char *)name : e2->name); |
| 72 | +} |
| 73 | + |
| 74 | +struct collection_status { |
| 75 | + enum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase; |
| 76 | + |
| 77 | + const char *reference; |
| 78 | + |
| 79 | + struct string_list *files; |
| 80 | + struct hashmap file_map; |
| 81 | +}; |
| 82 | + |
| 83 | +static void collect_changes_cb(struct diff_queue_struct *q, |
| 84 | + struct diff_options *options, |
| 85 | + void *data) |
| 86 | +{ |
| 87 | + struct collection_status *s = data; |
| 88 | + struct diffstat_t stat = { 0 }; |
| 89 | + int i; |
| 90 | + |
| 91 | + if (!q->nr) |
| 92 | + return; |
| 93 | + |
| 94 | + compute_diffstat(options, &stat, q); |
| 95 | + |
| 96 | + for (i = 0; i < stat.nr; i++) { |
| 97 | + const char *name = stat.files[i]->name; |
| 98 | + int hash = strhash(name); |
| 99 | + struct pathname_entry *entry; |
| 100 | + struct file_item *file_item; |
| 101 | + struct adddel *adddel; |
| 102 | + |
| 103 | + entry = hashmap_get_entry_from_hash(&s->file_map, hash, name, |
| 104 | + struct pathname_entry, ent); |
| 105 | + if (!entry) { |
| 106 | + add_file_item(s->files, name); |
| 107 | + |
| 108 | + entry = xcalloc(sizeof(*entry), 1); |
| 109 | + hashmap_entry_init(&entry->ent, hash); |
| 110 | + entry->name = s->files->items[s->files->nr - 1].string; |
| 111 | + entry->item = s->files->items[s->files->nr - 1].util; |
| 112 | + hashmap_add(&s->file_map, &entry->ent); |
| 113 | + } |
| 114 | + |
| 115 | + file_item = entry->item; |
| 116 | + adddel = s->phase == FROM_INDEX ? |
| 117 | + &file_item->index : &file_item->worktree; |
| 118 | + adddel->seen = 1; |
| 119 | + adddel->add = stat.files[i]->added; |
| 120 | + adddel->del = stat.files[i]->deleted; |
| 121 | + if (stat.files[i]->is_binary) |
| 122 | + adddel->binary = 1; |
| 123 | + } |
| 124 | + free_diffstat_info(&stat); |
| 125 | +} |
| 126 | + |
| 127 | +static int get_modified_files(struct repository *r, struct string_list *files, |
| 128 | + const struct pathspec *ps) |
| 129 | +{ |
| 130 | + struct object_id head_oid; |
| 131 | + int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, |
| 132 | + &head_oid, NULL); |
| 133 | + struct collection_status s = { FROM_WORKTREE }; |
| 134 | + |
| 135 | + if (discard_index(r->index) < 0 || |
| 136 | + repo_read_index_preload(r, ps, 0) < 0) |
| 137 | + return error(_("could not read index")); |
| 138 | + |
| 139 | + string_list_clear(files, 1); |
| 140 | + s.files = files; |
| 141 | + hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0); |
| 142 | + |
| 143 | + for (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) { |
| 144 | + struct rev_info rev; |
| 145 | + struct setup_revision_opt opt = { 0 }; |
| 146 | + |
| 147 | + opt.def = is_initial ? |
| 148 | + empty_tree_oid_hex() : oid_to_hex(&head_oid); |
| 149 | + |
| 150 | + init_revisions(&rev, NULL); |
| 151 | + setup_revisions(0, NULL, &rev, &opt); |
| 152 | + |
| 153 | + rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; |
| 154 | + rev.diffopt.format_callback = collect_changes_cb; |
| 155 | + rev.diffopt.format_callback_data = &s; |
| 156 | + |
| 157 | + if (ps) |
| 158 | + copy_pathspec(&rev.prune_data, ps); |
| 159 | + |
| 160 | + if (s.phase == FROM_INDEX) |
| 161 | + run_diff_index(&rev, 1); |
| 162 | + else { |
| 163 | + rev.diffopt.flags.ignore_dirty_submodules = 1; |
| 164 | + run_diff_files(&rev, 0); |
| 165 | + } |
| 166 | + } |
| 167 | + hashmap_free_entries(&s.file_map, struct pathname_entry, ent); |
| 168 | + |
| 169 | + /* While the diffs are ordered already, we ran *two* diffs... */ |
| 170 | + string_list_sort(files); |
| 171 | + |
| 172 | + return 0; |
| 173 | +} |
| 174 | + |
| 175 | +static void render_adddel(struct strbuf *buf, |
| 176 | + struct adddel *ad, const char *no_changes) |
| 177 | +{ |
| 178 | + if (ad->binary) |
| 179 | + strbuf_addstr(buf, _("binary")); |
| 180 | + else if (ad->seen) |
| 181 | + strbuf_addf(buf, "+%"PRIuMAX"/-%"PRIuMAX, |
| 182 | + (uintmax_t)ad->add, (uintmax_t)ad->del); |
| 183 | + else |
| 184 | + strbuf_addstr(buf, no_changes); |
| 185 | +} |
| 186 | + |
| 187 | +struct print_file_item_data { |
| 188 | + const char *modified_fmt; |
| 189 | + struct strbuf buf, index, worktree; |
| 190 | +}; |
| 191 | + |
| 192 | +static void print_file_item(int i, struct string_list_item *item, |
| 193 | + void *print_file_item_data) |
| 194 | +{ |
| 195 | + struct file_item *c = item->util; |
| 196 | + struct print_file_item_data *d = print_file_item_data; |
| 197 | + |
| 198 | + strbuf_reset(&d->index); |
| 199 | + strbuf_reset(&d->worktree); |
| 200 | + strbuf_reset(&d->buf); |
| 201 | + |
| 202 | + render_adddel(&d->worktree, &c->worktree, _("nothing")); |
| 203 | + render_adddel(&d->index, &c->index, _("unchanged")); |
| 204 | + strbuf_addf(&d->buf, d->modified_fmt, |
| 205 | + d->index.buf, d->worktree.buf, item->string); |
| 206 | + |
| 207 | + printf(" %2d: %s", i + 1, d->buf.buf); |
| 208 | +} |
| 209 | + |
| 210 | +static int run_status(struct add_i_state *s, const struct pathspec *ps, |
| 211 | + struct string_list *files, struct list_options *opts) |
| 212 | +{ |
| 213 | + if (get_modified_files(s->r, files, ps) < 0) |
| 214 | + return -1; |
| 215 | + |
| 216 | + list(files, opts); |
| 217 | + putchar('\n'); |
| 218 | + |
| 219 | + return 0; |
| 220 | +} |
3 | 221 |
|
4 | 222 | int run_add_i(struct repository *r, const struct pathspec *ps)
|
5 | 223 | {
|
6 |
| - die(_("No commands are available in the built-in `git add -i` yet!")); |
| 224 | + struct add_i_state s = { NULL }; |
| 225 | + struct print_file_item_data print_file_item_data = { |
| 226 | + "%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT |
| 227 | + }; |
| 228 | + struct list_options opts = { |
| 229 | + NULL, print_file_item, &print_file_item_data |
| 230 | + }; |
| 231 | + struct strbuf header = STRBUF_INIT; |
| 232 | + struct string_list files = STRING_LIST_INIT_DUP; |
| 233 | + int res = 0; |
| 234 | + |
| 235 | + init_add_i_state(&s, r); |
| 236 | + strbuf_addstr(&header, " "); |
| 237 | + strbuf_addf(&header, print_file_item_data.modified_fmt, |
| 238 | + _("staged"), _("unstaged"), _("path")); |
| 239 | + opts.header = header.buf; |
| 240 | + |
| 241 | + if (discard_index(r->index) < 0 || |
| 242 | + repo_read_index(r) < 0 || |
| 243 | + repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1, |
| 244 | + NULL, NULL, NULL) < 0) |
| 245 | + warning(_("could not refresh index")); |
| 246 | + |
| 247 | + res = run_status(&s, ps, &files, &opts); |
| 248 | + |
| 249 | + string_list_clear(&files, 1); |
| 250 | + strbuf_release(&print_file_item_data.buf); |
| 251 | + strbuf_release(&print_file_item_data.index); |
| 252 | + strbuf_release(&print_file_item_data.worktree); |
| 253 | + strbuf_release(&header); |
| 254 | + |
| 255 | + return res; |
7 | 256 | }
|
0 commit comments