2 #include "add-interactive.h"
8 #include "string-list.h"
13 char header_color
[COLOR_MAXLEN
];
14 char help_color
[COLOR_MAXLEN
];
15 char prompt_color
[COLOR_MAXLEN
];
16 char error_color
[COLOR_MAXLEN
];
17 char reset_color
[COLOR_MAXLEN
];
20 static void init_color(struct repository
*r
, struct add_i_state
*s
,
21 const char *slot_name
, char *dst
,
22 const char *default_color
)
24 char *key
= xstrfmt("color.interactive.%s", slot_name
);
29 else if (repo_config_get_value(r
, key
, &value
) ||
30 color_parse(value
, dst
))
31 strlcpy(dst
, default_color
, COLOR_MAXLEN
);
36 static void init_add_i_state(struct add_i_state
*s
, struct repository
*r
)
42 if (repo_config_get_value(r
, "color.interactive", &value
))
46 git_config_colorbool("color.interactive", value
);
47 s
->use_color
= want_color(s
->use_color
);
49 init_color(r
, s
, "header", s
->header_color
, GIT_COLOR_BOLD
);
50 init_color(r
, s
, "help", s
->help_color
, GIT_COLOR_BOLD_RED
);
51 init_color(r
, s
, "prompt", s
->prompt_color
, GIT_COLOR_BOLD_BLUE
);
52 init_color(r
, s
, "error", s
->error_color
, GIT_COLOR_BOLD_RED
);
53 init_color(r
, s
, "reset", s
->reset_color
, GIT_COLOR_RESET
);
57 * A "prefix item list" is a list of items that are identified by a string, and
58 * a unique prefix (if any) is determined for each item.
60 * It is implemented in the form of a pair of `string_list`s, the first one
61 * duplicating the strings, with the `util` field pointing at a structure whose
62 * first field must be `size_t prefix_length`.
64 * That `prefix_length` field will be computed by `find_unique_prefixes()`; It
65 * will be set to zero if no valid, unique prefix could be found.
67 * The second `string_list` is called `sorted` and does _not_ duplicate the
68 * strings but simply reuses the first one's, with the `util` field pointing at
69 * the `string_item_list` of the first `string_list`. It will be populated and
70 * sorted by `find_unique_prefixes()`.
72 struct prefix_item_list
{
73 struct string_list items
;
74 struct string_list sorted
;
75 size_t min_length
, max_length
;
77 #define PREFIX_ITEM_LIST_INIT \
78 { STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, 1, 4 }
80 static void prefix_item_list_clear(struct prefix_item_list
*list
)
82 string_list_clear(&list
->items
, 1);
83 string_list_clear(&list
->sorted
, 0);
86 static void extend_prefix_length(struct string_list_item
*p
,
87 const char *other_string
, size_t max_length
)
89 size_t *len
= p
->util
;
91 if (!*len
|| memcmp(p
->string
, other_string
, *len
))
95 char c
= p
->string
[*len
];
98 * Is `p` a strict prefix of `other`? Or have we exhausted the
99 * maximal length of the prefix? Or is the current character a
100 * multi-byte UTF-8 one? If so, there is no valid, unique
103 if (!c
|| ++*len
> max_length
|| !isascii(c
)) {
108 if (c
!= other_string
[*len
- 1])
113 static void find_unique_prefixes(struct prefix_item_list
*list
)
117 if (list
->sorted
.nr
== list
->items
.nr
)
120 string_list_clear(&list
->sorted
, 0);
121 /* Avoid reallocating incrementally */
122 list
->sorted
.items
= xmalloc(st_mult(sizeof(*list
->sorted
.items
),
124 list
->sorted
.nr
= list
->sorted
.alloc
= list
->items
.nr
;
126 for (i
= 0; i
< list
->items
.nr
; i
++) {
127 list
->sorted
.items
[i
].string
= list
->items
.items
[i
].string
;
128 list
->sorted
.items
[i
].util
= list
->items
.items
+ i
;
131 string_list_sort(&list
->sorted
);
133 for (i
= 0; i
< list
->sorted
.nr
; i
++) {
134 struct string_list_item
*sorted_item
= list
->sorted
.items
+ i
;
135 struct string_list_item
*item
= sorted_item
->util
;
136 size_t *len
= item
->util
;
139 while (*len
< list
->min_length
) {
140 char c
= item
->string
[(*len
)++];
142 if (!c
|| !isascii(c
)) {
149 extend_prefix_length(item
, sorted_item
[-1].string
,
151 if (i
+ 1 < list
->sorted
.nr
)
152 extend_prefix_length(item
, sorted_item
[1].string
,
157 static ssize_t
find_unique(const char *string
, struct prefix_item_list
*list
)
159 int index
= string_list_find_insert_index(&list
->sorted
, string
, 1);
160 struct string_list_item
*item
;
162 if (list
->items
.nr
!= list
->sorted
.nr
)
163 BUG("prefix_item_list in inconsistent state (%"PRIuMAX
165 (uintmax_t)list
->items
.nr
, (uintmax_t)list
->sorted
.nr
);
168 item
= list
->sorted
.items
[-1 - index
].util
;
169 else if (index
> 0 &&
170 starts_with(list
->sorted
.items
[index
- 1].string
, string
))
172 else if (index
+ 1 < list
->sorted
.nr
&&
173 starts_with(list
->sorted
.items
[index
+ 1].string
, string
))
175 else if (index
< list
->sorted
.nr
)
176 item
= list
->sorted
.items
[index
].util
;
179 return item
- list
->items
.items
;
182 struct list_options
{
185 void (*print_item
)(int i
, struct string_list_item
*item
, void *print_item_data
);
186 void *print_item_data
;
189 static void list(struct add_i_state
*s
, struct string_list
*list
,
190 struct list_options
*opts
)
198 color_fprintf_ln(stdout
, s
->header_color
,
201 for (i
= 0; i
< list
->nr
; i
++) {
202 opts
->print_item(i
, list
->items
+ i
, opts
->print_item_data
);
204 if ((opts
->columns
) && ((i
+ 1) % (opts
->columns
))) {
217 struct list_and_choose_options
{
218 struct list_options list_opts
;
221 void (*print_help
)(struct add_i_state
*s
);
224 #define LIST_AND_CHOOSE_ERROR (-1)
225 #define LIST_AND_CHOOSE_QUIT (-2)
228 * Returns the selected index.
230 * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
231 * `LIST_AND_CHOOSE_QUIT` is returned.
233 static ssize_t
list_and_choose(struct add_i_state
*s
,
234 struct prefix_item_list
*items
,
235 struct list_and_choose_options
*opts
)
237 struct strbuf input
= STRBUF_INIT
;
238 ssize_t res
= LIST_AND_CHOOSE_ERROR
;
240 find_unique_prefixes(items
);
245 strbuf_reset(&input
);
247 list(s
, &items
->items
, &opts
->list_opts
);
249 color_fprintf(stdout
, s
->prompt_color
, "%s", opts
->prompt
);
253 if (strbuf_getline(&input
, stdin
) == EOF
) {
255 res
= LIST_AND_CHOOSE_QUIT
;
263 if (!strcmp(input
.buf
, "?")) {
270 size_t sep
= strcspn(p
, " \t\r\n,");
282 index
= strtoul(p
, &endp
, 10) - 1;
290 index
= find_unique(p
, items
);
292 if (index
< 0 || index
>= items
->items
.nr
)
293 color_fprintf_ln(stdout
, s
->error_color
,
303 if (res
!= LIST_AND_CHOOSE_ERROR
)
307 strbuf_release(&input
);
313 unsigned seen
:1, binary
:1;
317 struct adddel index
, worktree
;
320 static void add_file_item(struct string_list
*files
, const char *name
)
322 struct file_item
*item
= xcalloc(sizeof(*item
), 1);
324 string_list_append(files
, name
)->util
= item
;
327 struct pathname_entry
{
328 struct hashmap_entry ent
;
330 struct file_item
*item
;
333 static int pathname_entry_cmp(const void *unused_cmp_data
,
334 const struct hashmap_entry
*he1
,
335 const struct hashmap_entry
*he2
,
338 const struct pathname_entry
*e1
=
339 container_of(he1
, const struct pathname_entry
, ent
);
340 const struct pathname_entry
*e2
=
341 container_of(he2
, const struct pathname_entry
, ent
);
343 return strcmp(e1
->name
, name
? (const char *)name
: e2
->name
);
346 struct collection_status
{
347 enum { FROM_WORKTREE
= 0, FROM_INDEX
= 1 } phase
;
349 const char *reference
;
351 struct string_list
*files
;
352 struct hashmap file_map
;
355 static void collect_changes_cb(struct diff_queue_struct
*q
,
356 struct diff_options
*options
,
359 struct collection_status
*s
= data
;
360 struct diffstat_t stat
= { 0 };
366 compute_diffstat(options
, &stat
, q
);
368 for (i
= 0; i
< stat
.nr
; i
++) {
369 const char *name
= stat
.files
[i
]->name
;
370 int hash
= strhash(name
);
371 struct pathname_entry
*entry
;
372 struct file_item
*file_item
;
373 struct adddel
*adddel
;
375 entry
= hashmap_get_entry_from_hash(&s
->file_map
, hash
, name
,
376 struct pathname_entry
, ent
);
378 add_file_item(s
->files
, name
);
380 entry
= xcalloc(sizeof(*entry
), 1);
381 hashmap_entry_init(&entry
->ent
, hash
);
382 entry
->name
= s
->files
->items
[s
->files
->nr
- 1].string
;
383 entry
->item
= s
->files
->items
[s
->files
->nr
- 1].util
;
384 hashmap_add(&s
->file_map
, &entry
->ent
);
387 file_item
= entry
->item
;
388 adddel
= s
->phase
== FROM_INDEX
?
389 &file_item
->index
: &file_item
->worktree
;
391 adddel
->add
= stat
.files
[i
]->added
;
392 adddel
->del
= stat
.files
[i
]->deleted
;
393 if (stat
.files
[i
]->is_binary
)
396 free_diffstat_info(&stat
);
399 static int get_modified_files(struct repository
*r
, struct string_list
*files
,
400 const struct pathspec
*ps
)
402 struct object_id head_oid
;
403 int is_initial
= !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING
,
405 struct collection_status s
= { FROM_WORKTREE
};
407 if (discard_index(r
->index
) < 0 ||
408 repo_read_index_preload(r
, ps
, 0) < 0)
409 return error(_("could not read index"));
411 string_list_clear(files
, 1);
413 hashmap_init(&s
.file_map
, pathname_entry_cmp
, NULL
, 0);
415 for (s
.phase
= FROM_WORKTREE
; s
.phase
<= FROM_INDEX
; s
.phase
++) {
417 struct setup_revision_opt opt
= { 0 };
419 opt
.def
= is_initial
?
420 empty_tree_oid_hex() : oid_to_hex(&head_oid
);
422 init_revisions(&rev
, NULL
);
423 setup_revisions(0, NULL
, &rev
, &opt
);
425 rev
.diffopt
.output_format
= DIFF_FORMAT_CALLBACK
;
426 rev
.diffopt
.format_callback
= collect_changes_cb
;
427 rev
.diffopt
.format_callback_data
= &s
;
430 copy_pathspec(&rev
.prune_data
, ps
);
432 if (s
.phase
== FROM_INDEX
)
433 run_diff_index(&rev
, 1);
435 rev
.diffopt
.flags
.ignore_dirty_submodules
= 1;
436 run_diff_files(&rev
, 0);
439 hashmap_free_entries(&s
.file_map
, struct pathname_entry
, ent
);
441 /* While the diffs are ordered already, we ran *two* diffs... */
442 string_list_sort(files
);
447 static void render_adddel(struct strbuf
*buf
,
448 struct adddel
*ad
, const char *no_changes
)
451 strbuf_addstr(buf
, _("binary"));
453 strbuf_addf(buf
, "+%"PRIuMAX
"/-%"PRIuMAX
,
454 (uintmax_t)ad
->add
, (uintmax_t)ad
->del
);
456 strbuf_addstr(buf
, no_changes
);
459 /* filters out prefixes which have special meaning to list_and_choose() */
460 static int is_valid_prefix(const char *prefix
, size_t prefix_len
)
462 return prefix_len
&& prefix
&&
464 * We expect `prefix` to be NUL terminated, therefore this
465 * `strcspn()` call is okay, even if it might do much more
466 * work than strictly necessary.
468 strcspn(prefix
, " \t\r\n,") >= prefix_len
&& /* separators */
469 *prefix
!= '-' && /* deselection */
470 !isdigit(*prefix
) && /* selection */
472 (*prefix
!= '*' && /* "all" wildcard */
473 *prefix
!= '?')); /* prompt help */
476 struct print_file_item_data
{
477 const char *modified_fmt
;
478 struct strbuf buf
, index
, worktree
;
481 static void print_file_item(int i
, struct string_list_item
*item
,
482 void *print_file_item_data
)
484 struct file_item
*c
= item
->util
;
485 struct print_file_item_data
*d
= print_file_item_data
;
487 strbuf_reset(&d
->index
);
488 strbuf_reset(&d
->worktree
);
489 strbuf_reset(&d
->buf
);
491 render_adddel(&d
->worktree
, &c
->worktree
, _("nothing"));
492 render_adddel(&d
->index
, &c
->index
, _("unchanged"));
493 strbuf_addf(&d
->buf
, d
->modified_fmt
,
494 d
->index
.buf
, d
->worktree
.buf
, item
->string
);
496 printf(" %2d: %s", i
+ 1, d
->buf
.buf
);
499 static int run_status(struct add_i_state
*s
, const struct pathspec
*ps
,
500 struct string_list
*files
, struct list_options
*opts
)
502 if (get_modified_files(s
->r
, files
, ps
) < 0)
505 list(s
, files
, opts
);
511 static int run_help(struct add_i_state
*s
, const struct pathspec
*unused_ps
,
512 struct string_list
*unused_files
,
513 struct list_options
*unused_opts
)
515 color_fprintf_ln(stdout
, s
->help_color
, "status - %s",
516 _("show paths with changes"));
517 color_fprintf_ln(stdout
, s
->help_color
, "update - %s",
518 _("add working tree state to the staged set of changes"));
519 color_fprintf_ln(stdout
, s
->help_color
, "revert - %s",
520 _("revert staged set of changes back to the HEAD version"));
521 color_fprintf_ln(stdout
, s
->help_color
, "patch - %s",
522 _("pick hunks and update selectively"));
523 color_fprintf_ln(stdout
, s
->help_color
, "diff - %s",
524 _("view diff between HEAD and index"));
525 color_fprintf_ln(stdout
, s
->help_color
, "add untracked - %s",
526 _("add contents of untracked files to the staged set of changes"));
531 typedef int (*command_t
)(struct add_i_state
*s
, const struct pathspec
*ps
,
532 struct string_list
*files
,
533 struct list_options
*opts
);
535 struct command_item
{
536 size_t prefix_length
;
540 struct print_command_item_data
{
541 const char *color
, *reset
;
544 static void print_command_item(int i
, struct string_list_item
*item
,
545 void *print_command_item_data
)
547 struct print_command_item_data
*d
= print_command_item_data
;
548 struct command_item
*util
= item
->util
;
550 if (!util
->prefix_length
||
551 !is_valid_prefix(item
->string
, util
->prefix_length
))
552 printf(" %2d: %s", i
+ 1, item
->string
);
554 printf(" %2d: %s%.*s%s%s", i
+ 1,
555 d
->color
, (int)util
->prefix_length
, item
->string
,
556 d
->reset
, item
->string
+ util
->prefix_length
);
559 static void command_prompt_help(struct add_i_state
*s
)
561 const char *help_color
= s
->help_color
;
562 color_fprintf_ln(stdout
, help_color
, "%s", _("Prompt help:"));
563 color_fprintf_ln(stdout
, help_color
, "1 - %s",
564 _("select a numbered item"));
565 color_fprintf_ln(stdout
, help_color
, "foo - %s",
566 _("select item based on unique prefix"));
567 color_fprintf_ln(stdout
, help_color
, " - %s",
568 _("(empty) select nothing"));
571 int run_add_i(struct repository
*r
, const struct pathspec
*ps
)
573 struct add_i_state s
= { NULL
};
574 struct print_command_item_data data
= { "[", "]" };
575 struct list_and_choose_options main_loop_opts
= {
576 { 4, N_("*** Commands ***"), print_command_item
, &data
},
577 N_("What now"), command_prompt_help
583 { "status", run_status
},
584 { "help", run_help
},
586 struct prefix_item_list commands
= PREFIX_ITEM_LIST_INIT
;
588 struct print_file_item_data print_file_item_data
= {
589 "%12s %12s %s", STRBUF_INIT
, STRBUF_INIT
, STRBUF_INIT
591 struct list_options opts
= {
592 0, NULL
, print_file_item
, &print_file_item_data
594 struct strbuf header
= STRBUF_INIT
;
595 struct string_list files
= STRING_LIST_INIT_DUP
;
599 for (i
= 0; i
< ARRAY_SIZE(command_list
); i
++) {
600 struct command_item
*util
= xcalloc(sizeof(*util
), 1);
601 util
->command
= command_list
[i
].command
;
602 string_list_append(&commands
.items
, command_list
[i
].string
)
606 init_add_i_state(&s
, r
);
609 * When color was asked for, use the prompt color for
610 * highlighting, otherwise use square brackets.
613 data
.color
= s
.prompt_color
;
614 data
.reset
= s
.reset_color
;
617 strbuf_addstr(&header
, " ");
618 strbuf_addf(&header
, print_file_item_data
.modified_fmt
,
619 _("staged"), _("unstaged"), _("path"));
620 opts
.header
= header
.buf
;
622 if (discard_index(r
->index
) < 0 ||
623 repo_read_index(r
) < 0 ||
624 repo_refresh_and_write_index(r
, REFRESH_QUIET
, 0, 1,
625 NULL
, NULL
, NULL
) < 0)
626 warning(_("could not refresh index"));
628 res
= run_status(&s
, ps
, &files
, &opts
);
631 i
= list_and_choose(&s
, &commands
, &main_loop_opts
);
632 if (i
== LIST_AND_CHOOSE_QUIT
) {
637 if (i
!= LIST_AND_CHOOSE_ERROR
) {
638 struct command_item
*util
=
639 commands
.items
.items
[i
].util
;
640 res
= util
->command(&s
, ps
, &files
, &opts
);
644 string_list_clear(&files
, 1);
645 strbuf_release(&print_file_item_data
.buf
);
646 strbuf_release(&print_file_item_data
.index
);
647 strbuf_release(&print_file_item_data
.worktree
);
648 strbuf_release(&header
);
649 prefix_item_list_clear(&commands
);