From 7c0ab4458994aa895855abc4a504cf693ecc0cf1 Mon Sep 17 00:00:00 2001 From: Wincent Colaiuta Date: Thu, 22 Nov 2007 01:02:52 +0100 Subject: [PATCH 01/10] Teach builtin-add to pass multiple paths to git-add--interactive Instead of just accepting a single file parameter, git-add now accepts any number of path parameters, fowarding them to git-add--interactive. Signed-off-by: Wincent Colaiuta Signed-off-by: Junio C Hamano --- builtin-add.c | 21 ++++++++++++--------- commit.h | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/builtin-add.c b/builtin-add.c index cf815a0b8e..9e3beafe5b 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -135,11 +135,17 @@ static void refresh(int verbose, const char **pathspec) free(seen); } -int interactive_add(void) +int interactive_add(int argc, const char **argv) { - const char *argv[2] = { "add--interactive", NULL }; + int status; + const char **args = xmalloc(sizeof(const char *) * (argc + 1)); + args[0] = "add--interactive"; + memcpy((void *)args + sizeof(const char *), argv, sizeof(const char *) * argc); + args[argc + 1] = NULL; - return run_command_v_opt(argv, RUN_GIT_CMD); + status = run_command_v_opt(args, RUN_GIT_CMD); + free(args); + return status; } static struct lock_file lock_file; @@ -163,17 +169,14 @@ static struct option builtin_add_options[] = { int cmd_add(int argc, const char **argv, const char *prefix) { - int i, newfd, orig_argc = argc; + int i, newfd; const char **pathspec; struct dir_struct dir; argc = parse_options(argc, argv, builtin_add_options, builtin_add_usage, 0); - if (add_interactive) { - if (add_interactive != 1 || orig_argc != 2) - die("add --interactive does not take any parameters"); - exit(interactive_add()); - } + if (add_interactive) + exit(interactive_add(argc, argv)); git_config(git_default_config); diff --git a/commit.h b/commit.h index aa679867a9..d82b8bc300 100644 --- a/commit.h +++ b/commit.h @@ -113,7 +113,7 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads, int in_merge_bases(struct commit *, struct commit **, int); -extern int interactive_add(void); +extern int interactive_add(int argc, const char **argv); extern void add_files_to_cache(int verbose, const char *prefix, const char **files); extern int rerere(void); From 4c8416847aa48e2bd60fa26585e32940a1a9c61c Mon Sep 17 00:00:00 2001 From: Wincent Colaiuta Date: Thu, 22 Nov 2007 02:36:24 +0100 Subject: [PATCH 02/10] Add path-limiting to git-add--interactive Implement Junio's suggestion that git-add--interactive should reproduce the path-limiting semantics of non-interactive git-add. In otherwords, if "git add -i" (unrestricted) shows paths from a set A, "git add -i paths..." should show paths from a subset of the set A and that subset should be defined with the existing ls-files pathspec semantics. Signed-off-by: Wincent Colaiuta Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index fb1e92a766..a0e480e3ef 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -37,7 +37,7 @@ sub list_untracked { chomp $_; $_; } - run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @_); + run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV); } my $status_fmt = '%12s %12s %s'; @@ -56,9 +56,17 @@ sub list_modified { my ($only) = @_; my (%data, @return); my ($add, $del, $adddel, $file); + my @tracked = (); + + if (@ARGV) { + @tracked = map { + chomp $_; $_; + } run_cmd_pipe(qw(git ls-files --exclude-standard --), @ARGV); + return if (!@tracked); + } for (run_cmd_pipe(qw(git diff-index --cached - --numstat --summary HEAD))) { + --numstat --summary HEAD --), @tracked)) { if (($add, $del, $file) = /^([-\d]+) ([-\d]+) (.*)/) { my ($change, $bin); @@ -81,7 +89,7 @@ sub list_modified { } } - for (run_cmd_pipe(qw(git diff-files --numstat --summary))) { + for (run_cmd_pipe(qw(git diff-files --numstat --summary --), @tracked)) { if (($add, $del, $file) = /^([-\d]+) ([-\d]+) (.*)/) { if (!exists $data{$file}) { From 12db334e75ae291aa69987cbe0feda2b6a64af38 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 22 Nov 2007 01:47:13 -0800 Subject: [PATCH 03/10] git-add -i: allow multiple selection in patch subcommand This allows more than one files from the list to be chosen from the patch subcommand instead of going through the file one by one. This also updates the "list-and-choose" UI for usability. When the prompt ends with ">>", if you type '*' to choose all choices, the prompt immediately returns the choice without requiring an extra empty line to confirm the selection. Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index a0e480e3ef..e347216550 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -260,7 +260,7 @@ sub list_and_choose { $chosen[$i] = $choose; } } - last if ($opts->{IMMEDIATE}); + last if ($opts->{IMMEDIATE} || $line eq '*'); } for ($i = 0; $i < @stuff; $i++) { if ($chosen[$i]) { @@ -567,12 +567,12 @@ sub patch_update_cmd { @mods = grep { !($_->{BINARY}) } @mods; return if (!@mods); - my ($it) = list_and_choose({ PROMPT => 'Patch update', - SINGLETON => 1, - IMMEDIATE => 1, - HEADER => $status_head, }, - @mods); - patch_update_file($it->{VALUE}) if ($it); + my (@them) = list_and_choose({ PROMPT => 'Patch update', + HEADER => $status_head, }, + @mods); + for (@them) { + patch_update_file($_->{VALUE}); + } } sub patch_update_file { From 324ccbd6a09816af830b22b02bbeb06349141849 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Nov 2007 10:07:55 -0800 Subject: [PATCH 04/10] builtin-add: fix command line building to call interactive The earlier 7c0ab4458994aa895855abc4a504cf693ecc0cf1 (Teach builtin-add to pass multiple paths to git-add--interactive) did not allocate enough, and had unneeded (void*) pointer arithmetic. Signed-off-by: Junio C Hamano --- builtin-add.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/builtin-add.c b/builtin-add.c index dd895dfb1d..7c6a296af1 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -138,9 +138,10 @@ static void refresh(int verbose, const char **pathspec) int interactive_add(int argc, const char **argv) { int status; - const char **args = xmalloc(sizeof(const char *) * (argc + 1)); + const char **args = xcalloc(sizeof(const char *), (argc + 2)); + args[0] = "add--interactive"; - memcpy((void *)args + sizeof(const char *), argv, sizeof(const char *) * argc); + memcpy(&(args[1]), argv, sizeof(const char *) * argc); args[argc + 1] = NULL; status = run_command_v_opt(args, RUN_GIT_CMD); From 3f061887c562b20d3ed3d1f764462cf986a1ad12 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Nov 2007 10:10:10 -0800 Subject: [PATCH 05/10] add -i: Fix running from a subdirectory This fixes the pathspec interactive_add() passes to the underlying git-add--interactive helper. When the command was run from a subdirectory, cmd_add() already has gone up to the toplevel of the work tree, and the helper will be spawned from there. The pathspec given on the command line from the user needs to be adjusted for this. This adds "validate_pathspec()" function in the callchain, but it does not validate yet. The function can be changed to barf if there are unmatching pathspec given by the user, but that is not strictly necessary. Signed-off-by: Junio C Hamano --- builtin-add.c | 24 ++++++++++++++++++++---- builtin-commit.c | 2 +- commit.h | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/builtin-add.c b/builtin-add.c index 7c6a296af1..865c475ec9 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -135,13 +135,29 @@ static void refresh(int verbose, const char **pathspec) free(seen); } -int interactive_add(int argc, const char **argv) +static const char **validate_pathspec(int argc, const char **argv, const char *prefix) +{ + const char **pathspec = get_pathspec(prefix, argv); + + return pathspec; +} + +int interactive_add(int argc, const char **argv, const char *prefix) { int status; - const char **args = xcalloc(sizeof(const char *), (argc + 2)); + const char **args; + const char **pathspec = NULL; + if (argc) { + pathspec = validate_pathspec(argc, argv, prefix); + if (!pathspec) + return -1; + } + + args = xcalloc(sizeof(const char *), (argc + 2)); args[0] = "add--interactive"; - memcpy(&(args[1]), argv, sizeof(const char *) * argc); + if (argc) + memcpy(&(args[1]), pathspec, sizeof(const char *) * argc); args[argc + 1] = NULL; status = run_command_v_opt(args, RUN_GIT_CMD); @@ -177,7 +193,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, builtin_add_options, builtin_add_usage, 0); if (add_interactive) - exit(interactive_add(argc, argv)); + exit(interactive_add(argc, argv, prefix)); git_config(git_default_config); diff --git a/builtin-commit.c b/builtin-commit.c index 5d27102a62..45e51b1d5f 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -165,7 +165,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix) const char **pathspec = NULL; if (interactive) { - interactive_add(argc, argv); + interactive_add(argc, argv, prefix); commit_style = COMMIT_AS_IS; return get_index_file(); } diff --git a/commit.h b/commit.h index 9f0765bd9c..10e2b5d4cf 100644 --- a/commit.h +++ b/commit.h @@ -113,7 +113,7 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads, int in_merge_bases(struct commit *, struct commit **, int); -extern int interactive_add(int argc, const char **argv); +extern int interactive_add(int argc, const char **argv, const char *prefix); extern int rerere(void); static inline int single_parent(struct commit *commit) From b63e99500137c913bd801a2f22b6cf88c63b95c5 Mon Sep 17 00:00:00 2001 From: Wincent Colaiuta Date: Sun, 25 Nov 2007 14:15:42 +0100 Subject: [PATCH 06/10] Add "--patch" option to git-add--interactive When the "--patch" option is supplied, the patch_update_cmd() function is called bypassing the main_loop() and exits. Seeing as builtin-add is the only caller of git-add--interactive we can impose a strict requirement on the format of the arguments to avoid possible ambiguity: an "--" argument must be used whenever any pathspecs are passed, both with the "--patch" option and without it. Signed-off-by: Wincent Colaiuta --- Documentation/git-add.txt | 9 ++++++- builtin-add.c | 24 ++++++++++++------ git-add--interactive.perl | 51 +++++++++++++++++++++++++++++++-------- 3 files changed, 65 insertions(+), 19 deletions(-) diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 63829d93cc..ce22de8d2e 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -61,7 +61,14 @@ OPTIONS -i, \--interactive:: Add modified contents in the working tree interactively to - the index. + the index. Optional path arguments may be supplied to limit + operation to a subset of the working tree. See ``Interactive + mode'' for details. + +-p, \--patch: + Similar to Interactive mode but the initial command loop is + bypassed and the 'patch' subcommand is invoked using each of + the specified filepatterns before exiting. -u:: Update only files that git already knows about. This is similar diff --git a/builtin-add.c b/builtin-add.c index 865c475ec9..5c29cc2f3f 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -19,7 +19,7 @@ static const char * const builtin_add_usage[] = { "git-add [options] [--] ...", NULL }; - +static int patch_interactive = 0, add_interactive = 0; static int take_worktree_changes; static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) @@ -144,7 +144,7 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p int interactive_add(int argc, const char **argv, const char *prefix) { - int status; + int status, ac; const char **args; const char **pathspec = NULL; @@ -154,11 +154,17 @@ int interactive_add(int argc, const char **argv, const char *prefix) return -1; } - args = xcalloc(sizeof(const char *), (argc + 2)); - args[0] = "add--interactive"; - if (argc) - memcpy(&(args[1]), pathspec, sizeof(const char *) * argc); - args[argc + 1] = NULL; + args = xcalloc(sizeof(const char *), (argc + 4)); + ac = 0; + args[ac++] = "add--interactive"; + if (patch_interactive) + args[ac++] = "--patch"; + args[ac++] = "--"; + if (argc) { + memcpy(&(args[ac]), pathspec, sizeof(const char *) * argc); + ac += argc; + } + args[ac] = NULL; status = run_command_v_opt(args, RUN_GIT_CMD); free(args); @@ -171,13 +177,13 @@ static const char ignore_error[] = "The following paths are ignored by one of your .gitignore files:\n"; static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0; -static int add_interactive = 0; static struct option builtin_add_options[] = { OPT__DRY_RUN(&show_only), OPT__VERBOSE(&verbose), OPT_GROUP(""), OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"), + OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"), OPT_BOOLEAN('f', NULL, &ignored_too, "allow adding otherwise ignored files"), OPT_BOOLEAN('u', NULL, &take_worktree_changes, "update tracked files"), OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"), @@ -192,6 +198,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, builtin_add_options, builtin_add_usage, 0); + if (patch_interactive) + add_interactive = 1; if (add_interactive) exit(interactive_add(argc, argv, prefix)); diff --git a/git-add--interactive.perl b/git-add--interactive.perl index e347216550..df5df3ec90 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -2,6 +2,9 @@ use strict; +# command line options +my $patch_mode; + sub run_cmd_pipe { if ($^O eq 'MSWin32') { my @invalid = grep {m/[":*]/} @_; @@ -552,8 +555,8 @@ sub help_patch_cmd { print <<\EOF ; y - stage this hunk n - do not stage this hunk -a - stage this and all the remaining hunks -d - do not stage this hunk nor any of the remaining hunks +a - stage this and all the remaining hunks in the file +d - do not stage this hunk nor any of the remaining hunks in the file j - leave this hunk undecided, see next undecided hunk J - leave this hunk undecided, see next hunk k - leave this hunk undecided, see previous undecided hunk @@ -563,13 +566,21 @@ EOF } sub patch_update_cmd { - my @mods = list_modified('file-only'); - @mods = grep { !($_->{BINARY}) } @mods; - return if (!@mods); + my @mods = grep { !($_->{BINARY}) } list_modified('file-only'); + my @them; - my (@them) = list_and_choose({ PROMPT => 'Patch update', - HEADER => $status_head, }, - @mods); + if (!@mods) { + print STDERR "No changes.\n"; + return 0; + } + if ($patch_mode) { + @them = @mods; + } + else { + @them = list_and_choose({ PROMPT => 'Patch update', + HEADER => $status_head, }, + @mods); + } for (@them) { patch_update_file($_->{VALUE}); } @@ -783,6 +794,20 @@ add untracked - add contents of untracked files to the staged set of changes EOF } +sub process_args { + return unless @ARGV; + my $arg = shift @ARGV; + if ($arg eq "--patch") { + $patch_mode = 1; + $arg = shift @ARGV or die "missing --"; + die "invalid argument $arg, expecting --" + unless $arg eq "--"; + } + elsif ($arg ne "--") { + die "invalid argument $arg, expecting --"; + } +} + sub main_loop { my @cmd = ([ 'status', \&status_cmd, ], [ 'update', \&update_cmd, ], @@ -811,6 +836,12 @@ sub main_loop { } } +process_args(); refresh(); -status_cmd(); -main_loop(); +if ($patch_mode) { + patch_update_cmd(); +} +else { + status_cmd(); + main_loop(); +} From 280e50c7e5c2c52cbcc452fa5fb254ee580ae39c Mon Sep 17 00:00:00 2001 From: Ralf Wildenhues Date: Wed, 28 Nov 2007 19:21:42 +0100 Subject: [PATCH 07/10] Document all help keys in "git add -i" patch mode. Signed-off-by: Junio C Hamano --- Documentation/git-add.txt | 2 ++ git-add--interactive.perl | 1 + 2 files changed, 3 insertions(+) diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index ce22de8d2e..e74f83b47a 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -217,6 +217,8 @@ patch:: k - do not decide on this hunk now, and view the previous undecided hunk K - do not decide on this hunk now, and view the previous hunk + s - split the current hunk into smaller hunks + ? - print help + After deciding the fate for all hunks, if there is any hunk that was chosen, the index is updated with the selected hunks. diff --git a/git-add--interactive.perl b/git-add--interactive.perl index df5df3ec90..879cc5eadf 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -562,6 +562,7 @@ J - leave this hunk undecided, see next hunk k - leave this hunk undecided, see previous undecided hunk K - leave this hunk undecided, see previous hunk s - split the current hunk into smaller hunks +? - print help EOF } From 14cb50382c9027ca3b8cf8dbc0a26503a40f50c5 Mon Sep 17 00:00:00 2001 From: Wincent Colaiuta Date: Thu, 29 Nov 2007 13:00:38 +0100 Subject: [PATCH 08/10] Highlight keyboard shortcuts in git-add--interactive The user interface provided by the command loop in git-add--interactive gives the impression that subcommands can only be launched by entering an integer identifier from 1 through 8. A "hidden" feature is that any string can be entered, and a regex search anchored at the beginning of the string is used to find the uniquely matching option. This patch makes this feature a little more obvious by highlighting the first character of each subcommand (for example "patch" is displayed as "[p]atch"). A new function is added to detect the shortest unique prefix and this is used to decide what to highlight. Highlighting is also applied when choosing files. In the case where the common prefix may be unreasonably large highlighting is omitted; in this patch the soft limit (above which the highlighting will be omitted for a particular item) is 0 (in other words, there is no soft limit) and the hard limit (above which highlighting will be omitted for all items) is 3, but this can be tweaked. The actual highlighting is done by the highlight_prefix function, which will enable us to implement ANSI color code-based highlighting (most likely using underline or boldface) in the future. Signed-off-by: Wincent Colaiuta Acked-by: Jeff King Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 87 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 5 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 879cc5eadf..23fd2f741b 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -47,7 +47,6 @@ my $status_fmt = '%12s %12s %s'; my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path'); # Returns list of hashes, contents of each of which are: -# PRINT: print message # VALUE: pathname # BINARY: is a binary path # INDEX: is index different from HEAD? @@ -133,8 +132,6 @@ sub list_modified { } push @return, +{ VALUE => $_, - PRINT => (sprintf $status_fmt, - $it->{INDEX}, $it->{FILE}, $_), %$it, }; } @@ -170,10 +167,82 @@ sub find_unique { return $found; } +# inserts string into trie and updates count for each character +sub update_trie { + my ($trie, $string) = @_; + foreach (split //, $string) { + $trie = $trie->{$_} ||= {COUNT => 0}; + $trie->{COUNT}++; + } +} + +# returns an array of tuples (prefix, remainder) +sub find_unique_prefixes { + my @stuff = @_; + my @return = (); + + # any single prefix exceeding the soft limit is omitted + # if any prefix exceeds the hard limit all are omitted + # 0 indicates no limit + my $soft_limit = 0; + my $hard_limit = 3; + + # build a trie modelling all possible options + my %trie; + foreach my $print (@stuff) { + if ((ref $print) eq 'ARRAY') { + $print = $print->[0]; + } + else { + $print = $print->{VALUE}; + } + update_trie(\%trie, $print); + push @return, $print; + } + + # use the trie to find the unique prefixes + for (my $i = 0; $i < @return; $i++) { + my $ret = $return[$i]; + my @letters = split //, $ret; + my %search = %trie; + my ($prefix, $remainder); + my $j; + for ($j = 0; $j < @letters; $j++) { + my $letter = $letters[$j]; + if ($search{$letter}{COUNT} == 1) { + $prefix = substr $ret, 0, $j + 1; + $remainder = substr $ret, $j + 1; + last; + } + else { + my $prefix = substr $ret, 0, $j; + return () + if ($hard_limit && $j + 1 > $hard_limit); + } + %search = %{$search{$letter}}; + } + if ($soft_limit && $j + 1 > $soft_limit) { + $prefix = undef; + $remainder = $ret; + } + $return[$i] = [$prefix, $remainder]; + } + return @return; +} + +# given a prefix/remainder tuple return a string with the prefix highlighted +# for now use square brackets; later might use ANSI colors (underline, bold) +sub highlight_prefix { + my $prefix = shift; + my $remainder = shift; + return (defined $prefix) ? "[$prefix]$remainder" : $remainder; +} + sub list_and_choose { my ($opts, @stuff) = @_; my (@chosen, @return); my $i; + my @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY}; TOPLOOP: while (1) { @@ -190,10 +259,18 @@ sub list_and_choose { my $print = $stuff[$i]; if (ref $print) { if ((ref $print) eq 'ARRAY') { - $print = $print->[0]; + $print = @prefixes ? + highlight_prefix(@{$prefixes[$i]}) : + $print->[0]; } else { - $print = $print->{PRINT}; + my $value = @prefixes ? + highlight_prefix(@{$prefixes[$i]}) : + $print->{VALUE}; + $print = sprintf($status_fmt, + $print->{INDEX}, + $print->{FILE}, + $value); } } printf("%s%2d: %s", $chosen, $i+1, $print); From 633209898b71df4cf4d9296f224294f9fe252f66 Mon Sep 17 00:00:00 2001 From: Wincent Colaiuta Date: Sun, 2 Dec 2007 14:44:11 +0100 Subject: [PATCH 09/10] add -i: allow prefix highlighting for "Add untracked" as well. These changes make the automatic prefix highlighting work with the "Add untracked" subcommand in git-add--interactive by explicitly handling arrays, hashes and strings internally (previously only arrays and hashes were handled). In addition, prefixes which have special meaning for list_and_choose (things like "*" for "all" and "-" for "deselect) are explicitly excluded (highlighting these prefixes would be misleading). Signed-off-by: Wincent Colaiuta Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 47 +++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 23fd2f741b..32fb9ea2bb 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -193,7 +193,7 @@ sub find_unique_prefixes { if ((ref $print) eq 'ARRAY') { $print = $print->[0]; } - else { + elsif ((ref $print) eq 'HASH') { $print = $print->{VALUE}; } update_trie(\%trie, $print); @@ -230,12 +230,25 @@ sub find_unique_prefixes { return @return; } +# filters out prefixes which have special meaning to list_and_choose() +sub is_valid_prefix { + my $prefix = shift; + return (defined $prefix) && + !($prefix =~ /[\s,]/) && # separators + !($prefix =~ /^-/) && # deselection + !($prefix =~ /^\d+/) && # selection + ($prefix ne '*'); # "all" wildcard +} + # given a prefix/remainder tuple return a string with the prefix highlighted # for now use square brackets; later might use ANSI colors (underline, bold) sub highlight_prefix { my $prefix = shift; my $remainder = shift; - return (defined $prefix) ? "[$prefix]$remainder" : $remainder; + return $remainder unless defined $prefix; + return is_valid_prefix($prefix) ? + "[$prefix]$remainder" : + "$prefix$remainder"; } sub list_and_choose { @@ -257,21 +270,21 @@ sub list_and_choose { for ($i = 0; $i < @stuff; $i++) { my $chosen = $chosen[$i] ? '*' : ' '; my $print = $stuff[$i]; - if (ref $print) { - if ((ref $print) eq 'ARRAY') { - $print = @prefixes ? - highlight_prefix(@{$prefixes[$i]}) : - $print->[0]; - } - else { - my $value = @prefixes ? - highlight_prefix(@{$prefixes[$i]}) : - $print->{VALUE}; - $print = sprintf($status_fmt, - $print->{INDEX}, - $print->{FILE}, - $value); - } + my $ref = ref $print; + my $highlighted = highlight_prefix(@{$prefixes[$i]}) + if @prefixes; + if ($ref eq 'ARRAY') { + $print = $highlighted || $print->[0]; + } + elsif ($ref eq 'HASH') { + my $value = $highlighted || $print->{VALUE}; + $print = sprintf($status_fmt, + $print->{INDEX}, + $print->{FILE}, + $value); + } + else { + $print = $highlighted || $print; } printf("%s%2d: %s", $chosen, $i+1, $print); if (($opts->{LIST_FLAT}) && From 7e018be2ada073c92da59eb41442cde22c0c2796 Mon Sep 17 00:00:00 2001 From: Wincent Colaiuta Date: Mon, 3 Dec 2007 09:09:43 +0100 Subject: [PATCH 10/10] git-add -i: add help text for list-and-choose UI Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 32fb9ea2bb..335c2c6b56 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -237,7 +237,8 @@ sub is_valid_prefix { !($prefix =~ /[\s,]/) && # separators !($prefix =~ /^-/) && # deselection !($prefix =~ /^\d+/) && # selection - ($prefix ne '*'); # "all" wildcard + ($prefix ne '*') && # "all" wildcard + ($prefix ne '?'); # prompt help } # given a prefix/remainder tuple return a string with the prefix highlighted @@ -318,6 +319,12 @@ sub list_and_choose { } chomp $line; last if $line eq ''; + if ($line eq '?') { + $opts->{SINGLETON} ? + singleton_prompt_help_cmd() : + prompt_help_cmd(); + next TOPLOOP; + } for my $choice (split(/[\s,]+/, $line)) { my $choose = 1; my ($bottom, $top); @@ -363,6 +370,28 @@ sub list_and_choose { return @return; } +sub singleton_prompt_help_cmd { + print <<\EOF ; +Prompt help: +1 - select a numbered item +foo - select item based on unique prefix + - (empty) select nothing +EOF +} + +sub prompt_help_cmd { + print <<\EOF ; +Prompt help: +1 - select a single item +3-5 - select a range of items +2-3,6-9 - select multiple ranges +foo - select item based on unique prefix +-... - unselect specified items +* - choose all items + - (empty) finish selecting +EOF +} + sub status_cmd { list_and_choose({ LIST_ONLY => 1, HEADER => $status_head }, list_modified());