*** Interactive git-clean ***
v11 cover *** Interactive git-clean ***
Update since v10:
* Patch 2/15: add test cases for relative_test() in t0060 and test-path-utils.
* Rewrite if...else... to switch...case...
* Fixed typo in commit logs.
Jiang Xin (15):
path.c: refactor relative_path(), not only strip prefix
test: test relative_path through test-path-utils quote.c: remove path_relative, use relative_path instead Refactor quote_path_relative, remove unused params Refactor write_name_quoted_relative, remove unused params
git-clean: refactor git-clean into two phases
git-clean: add support for -i/--interactive
git-clean: show items of del_list in columns
git-clean: add colors to interactive git-clean
git-clean: use a git-add-interactive compatible UI
git-clean: add filter by pattern interactive action
git-clean: add select by numbers interactive action
git-clean: add ask each interactive action
git-clean: add documentation for interactive git-clean
test: add t7301 for git-clean--interactive
Documentation/config.txt | 21 +- Documentation/git-clean.txt | 71 +++- builtin/clean.c | 778 +++++++++++++++++++++++++++++++++++++++++-- builtin/grep.c | 5 +- builtin/ls-files.c | 16 +- cache.h | 2 +- path.c | 112 +++++-- quote.c | 65 +--- quote.h | 7 +- setup.c | 5 +- t/t0060-path-utils.sh | 23 ++ t/t7301-clean-interactive.sh | 439 ++++++++++++++++++++++++ test-path-utils.c | 24 ++ wt-status.c | 17 +- 14 files changed, 1434 insertions(+), 151 deletions(-) create mode 100755 t/t7301-clean-interactive.sh
Signature · 1 line
1.8.3.rc2.380.g956c2b2
PATCH v11 1/15 path.c: refactor relative_path(), not only strip prefix
Original design of relative_path() is simple, just strip the prefix (*base) from the absolute path (*abs). In most cases, we need a real relative path, such as: ../foo, ../../bar. That's why there is another reimplementation (path_relative()) in quote.c.
Refactor relative_path() in path.c to return real relative path, so that user can reuse this function without reimplement his/her own. I will use this method for interactive git-clean later. Some of the implementations are borrowed from path_relative() in quote.c.
Different results for relative_path() before and after this refactor:
abs path base path relative (original) relative (refactor)
======== ========= =================== ===================
/a/b/c/ /a/b c/ c/
/a/b//c/ //a///b/ c/ c/
/a/b /a/b . ./
/a/b/ /a/b . ./
/a /a/b/ /a ../
/ /a/b/ / ../../
/a/c /a/b/ /a/c ../c
/a/b (empty) /a/b /a/b
/a/b (null) /a/b /a/b
(empty) /a/b (empty) ./
(null) /a/b (empty) ./
(null) (empty) (null) ./--- cache.h | 2 +- path.c | 112 +++++++++++++++++++++++++++++++++++++++++++++++++--------------- setup.c | 5 ++- 3 files changed, 92 insertions(+), 27 deletions(-)
cache.h +1 -1
@@ -737,7 +737,7 @@ int is_directory(const char *);const char *real_path (const char *path);const char *real_path_if_valid (const char *path);const char *absolute_path (const char *path); -const char *relative_path (const char *abs,const char *base); +const char *relative_path (const char *abs,const char *base,struct strbuf *sb);int normalize_path_copy (char *dst,const char *src);int longest_ancestor_length (const char *path,struct string_list *prefixes);char *strip_path_suffix (const char *path,const char *suffix);
path.c +87 -25
129 lines not on this page · raw message
setup.c +4 -1
@@ -360,6 +360,7 @@ int is_inside_work_tree(void)void setup_work_tree (void ) { +struct strbuf sb =STRBUF_INIT ;const char *work_tree, *git_dir;static int initialized =0 ;
@@ -379,8 +380,10 @@ void setup_work_tree(void)if (getenv (GIT_WORK_TREE_ENVIRONMENT ))setenv (GIT_WORK_TREE_ENVIRONMENT ,"." ,1 ); -set_git_dir (relative_path (git_dir, work_tree)); +set_git_dir (relative_path (git_dir, work_tree, &sb)); initialized =1 ; + +strbuf_release (&sb); }static int check_repository_format_gently (const char *gitdir,int *nongit_ok)
Signature · 1 line
1.8.3.rc2.380.g956c2b2
PATCH v11 2/15 test: test relative_path through test-path-utils
In reply to Jiang XinAdd subcommand "relative_path" in test-path-utils, and add test cases in t0060.
--- t/t0060-path-utils.sh | 23 +++++++++++++++++++++++ test-path-utils.c | 24 ++++++++++++++++++++++++ 2 files changed, 47 insertions(+)
t/t0060-path-utils.sh +23 -0
@@ -12,6 +12,11 @@ norm_path() { "test \"\$(test-path-utils normalize_path_copy '$1')\" = '$2'" } +relative_path() { + test_expect_success $4 "relative path: $1 $2 => $3" \ + "test \"\$(test-path-utils relative_path '$1' '$2')\" = '$3'" +} + # On Windows, we are using MSYS's bash, which mangles the paths. # Absolute paths are anchored at the MSYS installation directory, # which means that the path / accounts for this many characters:
@@ -183,4 +188,22 @@ test_expect_success SYMLINKS 'real path works on symlinks' ' test "$sym" = "$(test-path-utils real_path "$dir2/syml")" ' +relative_path /a/b/c/ /a/b/ c/ +relative_path /a/b/c/ /a/b c/ +relative_path /a//b//c/ //a/b// c/ +relative_path /a/b /a/b ./ +relative_path /a/b/ /a/b ./ +relative_path /a /a/b ../ +relative_path / /a/b/ ../../ +relative_path /a/c /a/b/ ../c +relative_path /a/c /a/b ../c +relative_path /a/b "(empty)" /a/b +relative_path /a/b "(null)" /a/b +relative_path "(empty)" /a/b ./ +relative_path "(empty)" "(empty)" ./ +relative_path "(empty)" "(null)" ./ +relative_path "(null)" /a/b ./ +relative_path "(null)" "(empty)" ./ +relative_path "(null)" "(null)" ./ + test_done
test-path-utils.c +24 -0
@@ -1,6 +1,19 @@#include "cache.h" #include "string-list.h" +#define PARSE_ARGV_STRING (var, input) do { \ + if (!strcmp(input, "(null)")) { \ + var = NULL; \ + } else if (!strcmp(input, "(empty)")) { \ + var = ""; \ + } else if (*input == '(') { \ + fprintf(stderr, "Bad value: %s\n", input); \ + return 1; \ + } else { \ + var = input; \ + } \ +} while (0) +/* * A "string_list_each_func_t" function that normalizes an entry from * GIT_CEILING_DIRECTORIES. If the path is unusable for some reason,
@@ -103,6 +116,17 @@ int main(int argc, char **argv)return 0 ; } +if (argc ==4 && !strcmp (argv[1 ],"relative_path" )) { +struct strbuf sb =STRBUF_INIT ; +const char *abs, *base, *rel; +PARSE_ARGV_STRING (abs, argv[2 ]); +PARSE_ARGV_STRING (base, argv[3 ]); + rel =relative_path (abs, base, &sb); +puts (rel ? rel :"(null)" ); +strbuf_release (&sb); +return 0 ; + } +fprintf (stderr,"%s: unknown function name: %s\n" , argv[0 ], argv[1 ] ? argv[1 ] :"(there was none)" );return 1 ;
Signature · 1 line
1.8.3.rc2.380.g956c2b2
PATCH v11 3/15 quote.c: remove path_relative, use relative_path instead
In reply to Jiang XinSince there is an enhanced version of relative_path() in path.c, remove duplicate counterpart path_relative() in quote.c.
--- quote.c | 55 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 53 deletions(-)
quote.c +2 -53
@@ -312,75 +312,24 @@ void write_name_quotedpfx(const char *pfx, size_t pfxlen,fputc (terminator, fp); } -static const char *path_relative (const char *in,int len, -struct strbuf *sb,const char *prefix, -int prefix_len); -void write_name_quoted_relative (const char *name,size_t len,const char *prefix,size_t prefix_len,FILE *fp,int terminator) {struct strbuf sb =STRBUF_INIT ; - name =path_relative (name, len, &sb, prefix, prefix_len); + name =relative_path (name, prefix, &sb);write_name_quoted (name, fp, terminator);strbuf_release (&sb); } -/* -* Give path as relative to prefix. -* -* The strbuf may or may not be used, so do not assume it contains the -* returned path. -*/ -static const char *path_relative (const char *in,int len, -struct strbuf *sb,const char *prefix, -int prefix_len) -{ -int off, i; - -if (len <0 ) - len =strlen (in); -if (prefix_len <0 ) { -if (prefix) - prefix_len =strlen (prefix); -else - prefix_len =0 ; - } - - off =0 ; - i =0 ; -while (i < prefix_len && i < len && prefix[i] == in[i]) { -if (prefix[i] =='/' ) - off = i +1 ; - i++; - } - in += off; - len -= off; - -if (i >= prefix_len) -return in; - -strbuf_reset (sb); -strbuf_grow (sb, len); - -while (i < prefix_len) { -if (prefix[i] =='/' ) -strbuf_addstr (sb,"../" ); - i++; - } -strbuf_add (sb, in, len); - -return sb->buf; -} -/* quote path as relative to the given prefix */ char *quote_path_relative (const char *in,int len,struct strbuf *out,const char *prefix) {struct strbuf sb =STRBUF_INIT ; -const char *rel =path_relative (in, len, &sb, prefix,-1 ); +const char *rel =relative_path (in, prefix, &sb);strbuf_reset (out);quote_c_style_counted (rel,strlen (rel), out,NULL ,0 );strbuf_release (&sb);
Signature · 1 line
1.8.3.rc2.380.g956c2b2
PATCH v11 4/15 Refactor quote_path_relative, remove unused params
In reply to Jiang XinAfter substitute path_relative() in quote.c with relative_path() from path.c, parameters (such as len and prefix_len) are obsolete in function quote_path_relative(). Remove unused parameters and change the order of parameters for quote_path_relative() function.
--- builtin/clean.c | 18 +++++++++--------- builtin/grep.c | 5 ++--- builtin/ls-files.c | 2 +- quote.c | 7 ++----- quote.h | 4 ++-- wt-status.c | 17 ++++++++--------- 6 files changed, 24 insertions(+), 29 deletions(-)
builtin/clean.c +9 -9
@@ -56,7 +56,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,if ((force_flag &REMOVE_DIR_KEEP_NESTED_GIT ) && !resolve_gitlink_ref (path->buf,"HEAD" , submodule_head)) {if (!quiet) { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);printf (dry_run ?_ (msg_would_skip_git_dir) :_ (msg_skip_git_dir), quoted.buf); }
@@ -70,7 +70,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,/* an empty dir could be removed even if it is unreadble */ res = dry_run ?0 :rmdir (path->buf);if (res) { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);warning (_ (msg_warn_remove_failed), quoted.buf); *dir_gone =0 ; }
@@ -94,7 +94,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,if (remove_dirs (path, prefix, force_flag, dry_run, quiet, &gone)) ret =1 ;if (gone) { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);string_list_append (&dels, quoted.buf); }else *dir_gone =0 ;
@@ -102,10 +102,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, }else { res = dry_run ?0 :unlink (path->buf);if (!res) { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);string_list_append (&dels, quoted.buf); }else { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);warning (_ (msg_warn_remove_failed), quoted.buf); *dir_gone =0 ; ret =1 ;
@@ -127,7 +127,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,if (!res) *dir_gone =1 ;else { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);warning (_ (msg_warn_remove_failed), quoted.buf); *dir_gone =0 ; ret =1 ;
@@ -262,7 +262,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)if (remove_dirs (&directory, prefix, rm_flags, dry_run, quiet, &gone)) errors++;if (gone && !quiet) { - qname =quote_path_relative (directory.buf, directory.len, &buf, prefix); + qname =quote_path_relative (directory.buf, prefix, &buf);printf (dry_run ?_ (msg_would_remove) :_ (msg_remove), qname); } }
@@ -272,11 +272,11 @@ int cmd_clean(int argc, const char **argv, const char *prefix)continue ; res = dry_run ?0 :unlink (ent->name);if (res) { - qname =quote_path_relative (ent->name,-1 , &buf, prefix); + qname =quote_path_relative (ent->name, prefix, &buf);warning (_ (msg_warn_remove_failed), qname); errors++; }else if (!quiet) { - qname =quote_path_relative (ent->name,-1 , &buf, prefix); + qname =quote_path_relative (ent->name, prefix, &buf);printf (dry_run ?_ (msg_would_remove) :_ (msg_remove), qname); } }
builtin/grep.c +2 -3
@@ -286,8 +286,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,struct strbuf pathbuf =STRBUF_INIT ;if (opt->relative && opt->prefix_length) { -quote_path_relative (filename + tree_name_len,-1 , &pathbuf, - opt->prefix); +quote_path_relative (filename + tree_name_len, opt->prefix, &pathbuf);strbuf_insert (&pathbuf,0 , filename, tree_name_len); }else {strbuf_addstr (&pathbuf, filename);
@@ -318,7 +317,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)struct strbuf buf =STRBUF_INIT ;if (opt->relative && opt->prefix_length) -quote_path_relative (filename,-1 , &buf, opt->prefix); +quote_path_relative (filename, opt->prefix, &buf);else strbuf_addstr (&buf, filename);
builtin/ls-files.c +1 -1
@@ -389,7 +389,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, const charif (found_dup)continue ; - name =quote_path_relative (pathspec[num],-1 , &sb, prefix); + name =quote_path_relative (pathspec[num], prefix, &sb);error ("pathspec '%s' did not match any file(s) known to git." , name); errors++;
quote.c +2 -5
@@ -325,8 +325,8 @@ void write_name_quoted_relative(const char *name, size_t len, }/* quote path as relative to the given prefix */ -char *quote_path_relative (const char *in,int len, -struct strbuf *out,const char *prefix) +char *quote_path_relative (const char *in,const char *prefix, +struct strbuf *out) {struct strbuf sb =STRBUF_INIT ;const char *rel =relative_path (in, prefix, &sb);
@@ -334,9 +334,6 @@ char *quote_path_relative(const char *in, int len,quote_c_style_counted (rel,strlen (rel), out,NULL ,0 );strbuf_release (&sb); -if (!out->len) -strbuf_addstr (out,"./" ); -return out->buf; }
quote.h +2 -2
@@ -65,8 +65,8 @@ extern void write_name_quoted_relative(const char *name, size_t len, FILE *fp, int terminator);/* quote path as relative to the given prefix */ -extern char *quote_path_relative (const char *in,int len, -struct strbuf *out,const char *prefix); +extern char *quote_path_relative (const char *in,const char *prefix, +struct strbuf *out);/* quoting as a string literal for other languages */ extern void perl_quote_print (FILE *stream,const char *src);
wt-status.c +8 -9
@@ -243,7 +243,7 @@ static void wt_status_print_unmerged_data(struct wt_status *s,struct strbuf onebuf =STRBUF_INIT ;const char *one, *how =_ ("bug" ); - one =quote_path (it->string,-1 , &onebuf, s->prefix); + one =quote_path (it->string, s->prefix, &onebuf);status_printf (s,color (WT_STATUS_HEADER , s),"\t" );switch (d->stagemask) {case 1 : how =_ ("both deleted:" );break ;
@@ -297,8 +297,8 @@ static void wt_status_print_change_data(struct wt_status *s, change_type); } - one =quote_path (one_name,-1 , &onebuf, s->prefix); - two =quote_path (two_name,-1 , &twobuf, s->prefix); + one =quote_path (one_name, s->prefix, &onebuf); + two =quote_path (two_name, s->prefix, &twobuf);status_printf (s,color (WT_STATUS_HEADER , s),"\t" );switch (status) {
@@ -706,8 +706,7 @@ static void wt_status_print_other(struct wt_status *s,struct string_list_item *it;const char *path; it = &(l->items[i]); - path =quote_path (it->string,strlen (it->string), - &buf, s->prefix); + path =quote_path (it->string, s->prefix, &buf);if (column_active (s->colopts)) {string_list_append (&output, path);continue ;
@@ -1289,7 +1288,7 @@ static void wt_shortstatus_unmerged(struct string_list_item *it, }else {struct strbuf onebuf =STRBUF_INIT ;const char *one; - one =quote_path (it->string,-1 , &onebuf, s->prefix); + one =quote_path (it->string, s->prefix, &onebuf);printf (" %s\n" , one);strbuf_release (&onebuf); }
@@ -1317,7 +1316,7 @@ static void wt_shortstatus_status(struct string_list_item *it,struct strbuf onebuf =STRBUF_INIT ;const char *one;if (d->head_path) { - one =quote_path (d->head_path,-1 , &onebuf, s->prefix); + one =quote_path (d->head_path, s->prefix, &onebuf);if (*one !='"' &&strchr (one,' ' ) !=NULL ) {putchar ('"' );strbuf_addch (&onebuf,'"' );
@@ -1326,7 +1325,7 @@ static void wt_shortstatus_status(struct string_list_item *it,printf ("%s -> " , one);strbuf_release (&onebuf); } - one =quote_path (it->string,-1 , &onebuf, s->prefix); + one =quote_path (it->string, s->prefix, &onebuf);if (*one !='"' &&strchr (one,' ' ) !=NULL ) {putchar ('"' );strbuf_addch (&onebuf,'"' );
@@ -1345,7 +1344,7 @@ static void wt_shortstatus_other(struct string_list_item *it, }else {struct strbuf onebuf =STRBUF_INIT ;const char *one; - one =quote_path (it->string,-1 , &onebuf, s->prefix); + one =quote_path (it->string, s->prefix, &onebuf);color_fprintf (s->fp,color (WT_STATUS_UNTRACKED , s),"%s" , sign);printf (" %s\n" , one);strbuf_release (&onebuf);
Signature · 1 line
1.8.3.rc2.380.g956c2b2
PATCH v11 5/15 Refactor write_name_quoted_relative, remove unused params
In reply to Jiang XinAfter substitute path_relative() in quote.c with relative_path() from path.c, parameters (such as len and prefix_len) are obsolete in function write_name_quoted_relative(). Remove unused parameters from write_name_quoted_relative() and related functions.
--- builtin/ls-files.c | 14 ++++++++------ quote.c | 3 +-- quote.h | 3 +-- 3 files changed, 10 insertions(+), 10 deletions(-)
builtin/ls-files.c +8 -6
@@ -46,10 +46,12 @@ static const char *tag_modified = "";static const char *tag_skip_worktree ="" ;static const char *tag_resolve_undo ="" ; -static void write_name (const char * name,size_t len) +static void write_name (const char *name) { -write_name_quoted_relative (name, len, prefix, prefix_len, stdout, - line_terminator); + +/* turn off prefix, if run with "--full-name" */ +write_name_quoted_relative (name, prefix_len ? prefix :NULL , + stdout, line_terminator); }static void show_dir_entry (const char *tag,struct dir_entry *ent)
@@ -63,7 +65,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)return ;fputs (tag, stdout); -write_name (ent->name, ent->len); +write_name (ent->name); }static void show_other_files (struct dir_struct *dir)
@@ -163,7 +165,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)find_unique_abbrev (ce->sha1,abbrev), ce_stage(ce)); } -write_name (ce->name,ce_namelen (ce)); +write_name (ce->name);if (debug_mode) {printf (" ctime: %d:%d\n" , ce->ce_ctime.sec, ce->ce_ctime.nsec);printf (" mtime: %d:%d\n" , ce->ce_mtime.sec, ce->ce_mtime.nsec);
@@ -196,7 +198,7 @@ static void show_ru_info(void)printf ("%s%06o %s %d\t" , tag_resolve_undo, ui->mode[i],find_unique_abbrev (ui->sha1[i], abbrev), i +1 ); -write_name (path, len); +write_name (path); } } }
quote.c +1 -2
@@ -312,8 +312,7 @@ void write_name_quotedpfx(const char *pfx, size_t pfxlen,fputc (terminator, fp); } -void write_name_quoted_relative (const char *name,size_t len, -const char *prefix,size_t prefix_len, +void write_name_quoted_relative (const char *name,const char *prefix,FILE *fp,int terminator) {struct strbuf sb =STRBUF_INIT ;
quote.h +1 -2
@@ -60,8 +60,7 @@ extern void quote_two_c_style(struct strbuf *, const char *, const char *, int);extern void write_name_quoted (const char *name,FILE *,int terminator);extern void write_name_quotedpfx (const char *pfx,size_t pfxlen,const char *name,FILE *,int terminator); -extern void write_name_quoted_relative (const char *name,size_t len, -const char *prefix,size_t prefix_len, +extern void write_name_quoted_relative (const char *name,const char *prefix,FILE *fp,int terminator);/* quote path as relative to the given prefix */
Signature · 1 line
1.8.3.rc2.380.g956c2b2
PATCH v11 6/15 git-clean: refactor git-clean into two phases
In reply to Jiang XinBefore introducing interactive git-clean, refactor git-clean operations into two phases:
* hold cleaning items in del_list,
* and remove them in a separate loop at the end.
We will introduce interactive git-clean between the two phases. The interactive git-clean will show what would be done and must confirm before do real cleaning.
--- builtin/clean.c | 64 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 19 deletions(-)
builtin/clean.c +45 -19
@@ -15,6 +15,7 @@#include "quote.h" static int force =-1 ;/* unset */ +static struct string_list del_list =STRING_LIST_INIT_DUP ;static const char *const builtin_clean_usage[] = {N_ ("git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..." ),
@@ -148,12 +149,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix)int dry_run =0 , remove_directories =0 , quiet =0 , ignored =0 ;int ignored_only =0 , config_set =0 , errors =0 , gone =1 ;int rm_flags =REMOVE_DIR_KEEP_NESTED_GIT ; -struct strbuf directory =STRBUF_INIT ; +struct strbuf abs_path =STRBUF_INIT ;struct dir_struct dir;static const char **pathspec;struct strbuf buf =STRBUF_INIT ;struct string_list exclude_list =STRING_LIST_INIT_NODUP ;struct exclude_list *el; +struct string_list_item *item;const char *qname;char *seen =NULL ;struct option options[] = {
@@ -223,6 +225,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)int matches =0 ;struct cache_entry *ce;struct stat st; +const char *rel;/* * Remove the '/' at the end that directory
@@ -242,13 +245,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)continue ;/* Yup, this one exists unmerged */ } -/* -* we might have removed this as part of earlier -* recursive directory removal, so lstat() here could -* fail with ENOENT. -*/ if (lstat (ent->name, &st)) -continue ; +die_errno ("Cannot lstat '%s'" , ent->name);if (pathspec) {memset (seen,0 , argc >0 ? argc :1 );
@@ -257,33 +255,61 @@ int cmd_clean(int argc, const char **argv, const char *prefix) }if (S_ISDIR (st.st_mode)) { -strbuf_addstr (&directory, ent->name);if (remove_directories || (matches ==MATCHED_EXACTLY )) { -if (remove_dirs (&directory, prefix, rm_flags, dry_run, quiet, &gone)) - errors++; -if (gone && !quiet) { - qname =quote_path_relative (directory.buf, prefix, &buf); -printf (dry_run ?_ (msg_would_remove) :_ (msg_remove), qname); - } + rel =relative_path (ent->name, prefix, &buf); +string_list_append (&del_list, rel); } -strbuf_reset (&directory); }else {if (pathspec && !matches)continue ; - res = dry_run ?0 :unlink (ent->name); + rel =relative_path (ent->name, prefix, &buf); +string_list_append (&del_list, rel); + } + } + +/* TODO: do interactive git-clean here, which will modify del_list */ + +for_each_string_list_item (item, &del_list) { +struct stat st; + +if (prefix) { +strbuf_addstr (&abs_path, prefix); + } +strbuf_addstr (&abs_path, item->string); + +/* +* we might have removed this as part of earlier +* recursive directory removal, so lstat() here could +* fail with ENOENT. +*/ +if (lstat (abs_path.buf, &st)) +continue ; + +if (S_ISDIR (st.st_mode)) { +if (remove_dirs (&abs_path, prefix, rm_flags, dry_run, quiet, &gone)) + errors++; +if (gone && !quiet) { + qname =quote_path_relative (item->string,NULL , &buf); +printf (dry_run ?_ (msg_would_remove) :_ (msg_remove), qname); + } + }else { + res = dry_run ?0 :unlink (abs_path.buf);if (res) { - qname =quote_path_relative (ent->name, prefix, &buf); + qname =quote_path_relative (item->string,NULL , &buf);warning (_ (msg_warn_remove_failed), qname); errors++; }else if (!quiet) { - qname =quote_path_relative (ent->name, prefix, &buf); + qname =quote_path_relative (item->string,NULL , &buf);printf (dry_run ?_ (msg_would_remove) :_ (msg_remove), qname); } } +strbuf_reset (&abs_path); }free (seen); -strbuf_release (&directory); +strbuf_release (&abs_path); +strbuf_release (&buf); +string_list_clear (&del_list,0 );string_list_clear (&exclude_list,0 );return (errors !=0 ); }
Signature · 1 line
1.8.3.rc2.380.g956c2b2
PATCH v11 7/15 git-clean: add support for -i/--interactive
In reply to Jiang XinShow what would be done and the user must confirm before actually cleaning.
Would remove ...
Would remove ...
Would remove ...
Remove [y/n]?
Press "y" to start cleaning, and press "n" if you want to abort.
--- Documentation/git-clean.txt | 10 ++++++-- builtin/clean.c | 57 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 60 insertions(+), 7 deletions(-)
Documentation/git-clean.txt +8 -2
@@ -8,7 +8,7 @@ git-clean - Remove untracked files from the working tree SYNOPSIS -------- [verse] -'git clean' [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>... +'git clean' [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>... DESCRIPTION -----------
@@ -34,7 +34,13 @@ OPTIONS -f:: --force:: If the Git configuration variable clean.requireForce is not set - to false, 'git clean' will refuse to run unless given -f or -n. + to false, 'git clean' will refuse to run unless given -f, -n or + -i. + +-i:: +--interactive:: + Show what would be done and the user must confirm before actually + cleaning. -n:: --dry-run::
builtin/clean.c +52 -5
93 lines not on this page · raw message
Signature · 1 line
1.8.3.rc2.380.g956c2b2
PATCH v11 8/15 git-clean: show items of del_list in columns
In reply to Jiang XinWhen there are lots of items to be cleaned, it is hard to see them all in one screen. Show them in columns will solve this problem.
--- Documentation/config.txt | 4 ++++ builtin/clean.c | 49 +++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 9 deletions(-)
Documentation/config.txt +4 -0
@@ -955,6 +955,10 @@ column.branch:: Specify whether to output branch listing in `git branch` in columns. See `column.ui` for details. +column.clean:: + Specify the layout when list items in `git clean -i`, which always + shows files and directories in columns. See `column.ui` for details. + column.status:: Specify whether to output untracked files in `git status` in columns. See `column.ui` for details.
builtin/clean.c +40 -9
86 lines not on this page · raw message
Signature · 1 line
1.8.3.rc2.380.g956c2b2
PATCH v11 9/15 git-clean: add colors to interactive git-clean
In reply to Jiang XinShow header, help, error messages, and prompt in colors for interactive git-clean. Re-use config variables, such as "color.interactive" and "color.interactive.<slot>" for command git-add--interactive.
--- Documentation/config.txt | 17 +++++------ builtin/clean.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 9 deletions(-)
Documentation/config.txt +9 -8
@@ -876,16 +876,17 @@ The values of these variables may be specified as in color.branch.<slot>. color.interactive:: When set to `always`, always use colors for interactive prompts - and displays (such as those used by "git-add --interactive"). - When false (or `never`), never. When set to `true` or `auto`, use - colors only when the output is to the terminal. Defaults to false. + and displays (such as those used by "git-add --interactive" and + "git-clean --interactive"). When false (or `never`), never. + When set to `true` or `auto`, use colors only when the output is + to the terminal. Defaults to false. color.interactive.<slot>:: - Use customized color for 'git add --interactive' - output. `<slot>` may be `prompt`, `header`, `help` or `error`, for - four distinct types of normal output from interactive - commands. The values of these variables may be specified as - in color.branch.<slot>. + Use customized color for 'git add --interactive' and 'git clean + --interactive' output. `<slot>` may be `prompt`, `header`, `help` + or `error`, for four distinct types of normal output from + interactive commands. The values of these variables may be + specified as in color.branch.<slot>. color.pager:: A boolean to enable/disable colored output when the pager is in
builtin/clean.c +72 -1
108 lines not on this page · raw message
Signature · 1 line
1.8.3.rc2.380.g956c2b2
PATCH v11 10/15 git-clean: use a git-add-interactive compatible UI
In reply to Jiang XinRewrite menu using a new method list_and_choose, which is borrowed from git-add--interactive.perl. We will use this framework to add new actions for interactive git-clean later.
Please NOTE:
* Method list_and_choose return an array of integers, and
* it is up to you to free the allocated memory of the array.
* The array ends with EOF.
* If user pressed CTRL-D (i.e. EOF), no selection returned.
--- builtin/clean.c | 456 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 427 insertions(+), 29 deletions(-)
builtin/clean.c +427 -29
@@ -50,6 +50,36 @@ enum color_clean { CLEAN_COLOR_ERROR = 5, }; +#define MENU_OPTS_SINGLETON 01 +#define MENU_OPTS_IMMEDIATE 02 +#define MENU_OPTS_LIST_ONLY 04 + +struct menu_opts { +const char *header; +const char *prompt; +int flags; +}; + +#define MENU_RETURN_NO_LOOP 10 + +struct menu_item { +char hotkey; +const char *title; +int selected; +int (*fn)(); +}; + +enum menu_stuff_type { +MENU_STUFF_TYPE_STRING_LIST =1 , +MENU_STUFF_TYPE_MENU_ITEM +}; + +struct menu_stuff { +enum menu_stuff_type type; +int nr; +void *stuff; +}; +static int parse_clean_color_slot (const char *var) {if (!strcasecmp (var,"reset" ))
@@ -240,54 +270,422 @@ static void pretty_print_dels(void) copts.indent =" " ; copts.padding =2 ;print_columns (&list, colopts, &copts); -putchar ('\n' );strbuf_release (&buf);string_list_clear (&list,0 ); } -static void interactive_main_loop (void ) +static void pretty_print_menus (struct string_list *menu_list) +{ +unsigned int local_colopts =0 ; +struct column_options copts; + + local_colopts =COL_ENABLED |COL_ROW ; +memset (&copts,0 ,sizeof (copts)); + copts.indent =" " ; + copts.padding =2 ; +print_columns (menu_list, local_colopts, &copts); +} + +static void prompt_help_cmd (int singleton) +{ +clean_print_color (CLEAN_COLOR_HELP ); +printf_ln (singleton ? +_ ("Prompt help:\n" +"1 - select a numbered item\n" +"foo - select item based on unique prefix\n" +" - (empty) select nothing" ) : +_ ("Prompt help:\n" +"1 - select a single item\n" +"3-5 - select a range of items\n" +"2-3,6-9 - select multiple ranges\n" +"foo - select item based on unique prefix\n" +"-... - unselect specified items\n" +"* - choose all items\n" +" - (empty) finish selecting" )); +clean_print_color (CLEAN_COLOR_RESET ); +} + +/* +* display menu stuff with number prefix and hotkey highlight +*/ +static void print_highlight_menu_stuff (struct menu_stuff *stuff,int **chosen) +{ +struct string_list menu_list =STRING_LIST_INIT_DUP ; +struct strbuf menu =STRBUF_INIT ; +struct strbuf buf =STRBUF_INIT ; +struct menu_item *menu_item; +struct string_list_item *string_list_item; +int i; + +switch (stuff->type) { +default : +die ("Bad type of menu_staff when print menu" ); +case MENU_STUFF_TYPE_MENU_ITEM : + menu_item = (struct menu_item *)stuff->stuff; +for (i =0 ; i < stuff->nr; i++, menu_item++) { +const char *p; +int highlighted =0 ; + + p = menu_item->title; +if ((*chosen)[i] <0 ) + (*chosen)[i] = menu_item->selected ?1 :0 ; +strbuf_addf (&menu,"%s%2d: " , (*chosen)[i] ?"*" :" " , i+1 ); +for (; *p; p++) { +if (!highlighted && *p == menu_item->hotkey) { +strbuf_addstr (&menu,clean_get_color (CLEAN_COLOR_PROMPT )); +strbuf_addch (&menu, *p); +strbuf_addstr (&menu,clean_get_color (CLEAN_COLOR_RESET )); + highlighted =1 ; + }else { +strbuf_addch (&menu, *p); + } + } +string_list_append (&menu_list, menu.buf); +strbuf_reset (&menu); + } +break ; +case MENU_STUFF_TYPE_STRING_LIST : + i =0 ; +for_each_string_list_item (string_list_item, (struct string_list *)stuff->stuff) { +if ((*chosen)[i] <0 ) + (*chosen)[i] =0 ; +strbuf_addf (&menu,"%s%2d: %s" , (*chosen)[i] ?"*" :" " , i+1 , string_list_item->string); +string_list_append (&menu_list, menu.buf); +strbuf_reset (&menu); + i++; + } +break ; + } + +pretty_print_menus (&menu_list); + +strbuf_release (&menu); +strbuf_release (&buf); +string_list_clear (&menu_list,0 ); +} + +/* +* Parse user input, and return choice(s) for menu (menu_stuff). +* +* Input +* (for single choice) +* 1 - select a numbered item +* foo - select item based on menu title +* - (empty) select nothing +* +* (for multiple choice) +* 1 - select a single item +* 3-5 - select a range of items +* 2-3,6-9 - select multiple ranges +* foo - select item based on menu title +* -... - unselect specified items +* * - choose all items +* - (empty) finish selecting +* +* The parse result will be saved in array **chosen, and +* return number of total selections. +*/ +static int parse_choice (struct menu_stuff *menu_stuff, +int is_single, +struct strbuf input, +int **chosen) +{ +struct strbuf **choice_list, **ptr; +struct menu_item *menu_item; +struct string_list_item *string_list_item; +int nr =0 ; +int i; + +if (is_single) { + choice_list =strbuf_split_max (&input,'\n' ,0 ); + }else { +char *p = input.buf; +do { +if (*p ==',' ) + *p =' ' ; + }while (*p++); + choice_list =strbuf_split_max (&input,' ' ,0 ); + } + +for (ptr = choice_list; *ptr; ptr++) { +char *p; +int choose =1 ; +int bottom =0 , top =0 ; +int is_range, is_number; + +strbuf_trim (*ptr); +if (!(*ptr)->len) +continue ; + +/* Input that begins with '-'; unchoose */ +if (*(*ptr)->buf =='-' ) { + choose =0 ; +strbuf_remove ((*ptr),0 ,1 ); + } + + is_range =0 ; + is_number =1 ; +for (p = (*ptr)->buf; *p; p++) { +if ('-' == *p) { +if (!is_range) { + is_range =1 ; + is_number =0 ; + }else { + is_number =0 ; + is_range =0 ; +break ; + } + }else if (!isdigit (*p)) { + is_number =0 ; + is_range =0 ; +break ; + } + } + +if (is_number) { + bottom =atoi ((*ptr)->buf); + top = bottom; + }else if (is_range) { + bottom =atoi ((*ptr)->buf); +/* a range can be specified like 5-7 or 5- */ +if (!*(strchr ((*ptr)->buf,'-' ) +1 )) { + top = menu_stuff->nr; + }else { + top =atoi (strchr ((*ptr)->buf,'-' ) +1 ); + } + }else if (!strcmp ((*ptr)->buf,"*" )) { + bottom =1 ; + top = menu_stuff->nr; + }else { +switch (menu_stuff->type) { +default : +die ("Bad type of menu_stuff when parse choice" ); +case MENU_STUFF_TYPE_MENU_ITEM : + menu_item = (struct menu_item *)menu_stuff->stuff; +for (i =0 ; i < menu_stuff->nr; i++, menu_item++) { +if (((*ptr)->len ==1 && + *(*ptr)->buf == menu_item->hotkey) || + !strcasecmp ((*ptr)->buf, menu_item->title)) { + bottom = i +1 ; + top = bottom; +break ; + } + } +break ; +case MENU_STUFF_TYPE_STRING_LIST : + string_list_item = ((struct string_list *)menu_stuff->stuff)->items; +for (i =0 ; i < menu_stuff->nr; i++, string_list_item++) { +if (!strcasecmp ((*ptr)->buf, string_list_item->string)) { + bottom = i +1 ; + top = bottom; +break ; + } + } +break ; + } + } + +if (top <=0 || bottom <=0 || top > menu_stuff->nr || bottom > top || + (is_single && bottom != top)) { +clean_print_color (CLEAN_COLOR_ERROR ); +printf_ln (_ ("Huh (%s)?" ), (*ptr)->buf); +clean_print_color (CLEAN_COLOR_RESET ); +continue ; + } + +for (i = bottom; i <= top; i++) + (*chosen)[i-1 ] = choose; + } + +strbuf_list_free (choice_list); + +for (i =0 ; i < menu_stuff->nr; i++) + nr += (*chosen)[i]; +return nr; +} + +/* +* Implement a git-add-interactive compatible UI, which is borrowed +* from git-add--interactive.perl. +* +* Return value: +* +* - Return an array of integers +* - , and it is up to you to free the allocated memory. +* - The array ends with EOF. +* - If user pressed CTRL-D (i.e. EOF), no selection returned. +*/ +static int *list_and_choose (struct menu_opts *opts,struct menu_stuff *stuff) { -struct strbuf confirm =STRBUF_INIT ; +struct strbuf choice =STRBUF_INIT ; +int *chosen, *result; +int nr =0 ; +int eof =0 ; +int i; + + chosen =xmalloc (sizeof (int ) * stuff->nr); +/* set chosen as uninitialized */ +for (i =0 ; i < stuff->nr; i++) + chosen[i] =-1 ; + +for (;;) { +if (opts->header) { +printf_ln ("%s%s%s" , +clean_get_color (CLEAN_COLOR_HEADER ), +_ (opts->header), +clean_get_color (CLEAN_COLOR_RESET )); + } +/* chosen will be initialized by print_highlight_menu_stuff */ +print_highlight_menu_stuff (stuff, &chosen); + +if (opts->flags &MENU_OPTS_LIST_ONLY ) +break ; + +if (opts->prompt) { +printf ("%s%s%s%s" , +clean_get_color (CLEAN_COLOR_PROMPT ), +_ (opts->prompt), + opts->flags &MENU_OPTS_SINGLETON ?"> " :">> " , +clean_get_color (CLEAN_COLOR_RESET )); + } + +if (strbuf_getline (&choice, stdin,'\n' ) !=EOF ) { +strbuf_trim (&choice); + }else { + eof =1 ; +break ; + } + +/* help for prompt */ +if (!strcmp (choice.buf,"?" )) { +prompt_help_cmd (opts->flags &MENU_OPTS_SINGLETON ); +continue ; + } + +/* for a multiple-choice menu, press ENTER (empty) will return back */ +if (!(opts->flags &MENU_OPTS_SINGLETON ) && !choice.len) +break ; + + nr =parse_choice (stuff, + opts->flags &MENU_OPTS_SINGLETON , + choice, + &chosen); + +if (opts->flags &MENU_OPTS_SINGLETON ) { +if (nr) +break ; + }else if (opts->flags &MENU_OPTS_IMMEDIATE ) { +break ; + } + } + +if (eof) { + result =xmalloc (sizeof (int )); + *result =EOF ; + }else { +int j =0 ; + +/* +* recalculate nr, if return back from menu directly with +* default selections. +*/ +if (!nr) { +for (i =0 ; i < stuff->nr; i++) + nr += chosen[i]; + } + + result =xmalloc (sizeof (int ) * (nr +1 )); +memset (result,0 ,sizeof (int ) * (nr +1 )); +for (i =0 ; i < stuff->nr && j < nr; i++) { +if (chosen[i]) + result[j++] = i; + } + result[j] =EOF ; + } + +free (chosen); +strbuf_release (&choice); +return result; +} + +static int clean_cmd (void ) +{ +return MENU_RETURN_NO_LOOP ; +} + +static int quit_cmd (void ) +{ +string_list_clear (&del_list,0 ); +printf_ln (_ ("Bye." )); +return MENU_RETURN_NO_LOOP ; +} + +static int help_cmd (void ) +{ +clean_print_color (CLEAN_COLOR_HELP ); +printf_ln (_ ( +"clean - start cleaning\n" +"quit - stop cleaning\n" +"help - this screen\n" +"? - help for prompt selection" + )); +clean_print_color (CLEAN_COLOR_RESET ); +return 0 ; +} + +static void interactive_main_loop (void ) +{while (del_list.nr) { -putchar ('\n' ); +struct menu_opts menu_opts; +struct menu_stuff menu_stuff; +struct menu_item menus[] = { + {'c' ,"clean" ,0 , clean_cmd}, + {'q' ,"quit" ,0 , quit_cmd}, + {'h' ,"help" ,0 , help_cmd}, + }; +int *chosen; + + menu_opts.header =N_ ("*** Commands ***" ); + menu_opts.prompt =N_ ("What now" ); + menu_opts.flags =MENU_OPTS_SINGLETON ; + + menu_stuff.type =MENU_STUFF_TYPE_MENU_ITEM ; + menu_stuff.stuff = menus; + menu_stuff.nr =sizeof (menus) /sizeof (struct menu_item ); +clean_print_color (CLEAN_COLOR_HEADER );printf_ln (Q_ ("Would remove the following item:" ,"Would remove the following items:" , del_list.nr));clean_print_color (CLEAN_COLOR_RESET ); -putchar ('\n' );pretty_print_dels (); -clean_print_color (CLEAN_COLOR_PROMPT ); -printf (_ ("Remove [y/n]? " )); -clean_print_color (CLEAN_COLOR_RESET ); -if (strbuf_getline (&confirm, stdin,'\n' ) !=EOF ) { -strbuf_trim (&confirm); - }else { -/* Ctrl-D is the same as "quit" */ -string_list_clear (&del_list,0 ); -putchar ('\n' ); -printf_ln ("Bye." ); -break ; - } - -if (confirm.len) { -if (!strncasecmp (confirm.buf,"yes" , confirm.len)) { -break ; - }else if (!strncasecmp (confirm.buf,"no" , confirm.len) || - !strncasecmp (confirm.buf,"quit" , confirm.len)) { -string_list_clear (&del_list,0 ); -printf_ln ("Bye." ); -break ; - }else { + chosen =list_and_choose (&menu_opts, &menu_stuff); + +if (*chosen !=EOF ) { +int ret; + ret = menus[*chosen].fn (); +if (ret !=MENU_RETURN_NO_LOOP ) { +free (chosen); + chosen =NULL ; +if (!del_list.nr) { +clean_print_color (CLEAN_COLOR_ERROR ); +printf_ln (_ ("No more files to clean, exiting." )); +clean_print_color (CLEAN_COLOR_RESET ); +break ; + }continue ; } + }else { +quit_cmd (); } - } -strbuf_release (&confirm); +free (chosen); + chosen =NULL ; +break ; + } }int cmd_clean (int argc,const char **argv,const char *prefix)
Signature · 1 line
1.8.3.rc2.380.g956c2b2
PATCH v11 11/15 git-clean: add filter by pattern interactive action
In reply to Jiang XinAdd a new action for interactive git-clean: filter by pattern. When the user chooses this action, user can input space-separated patterns (the same syntax as gitignore), and each clean candidate that matches with one of the patterns will be excluded from cleaning. When the user feels it's OK, presses ENTER and backs to the confirmation dialog.
--- builtin/clean.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+)
builtin/clean.c +68 -0
@@ -614,6 +614,72 @@ static int clean_cmd(void)return MENU_RETURN_NO_LOOP ; } +static int filter_by_patterns_cmd (void ) +{ +struct dir_struct dir; +struct strbuf confirm =STRBUF_INIT ; +struct strbuf **ignore_list; +struct string_list_item *item; +struct exclude_list *el; +int changed =-1 , i; + +for (;;) { +if (!del_list.nr) +break ; + +if (changed) +pretty_print_dels (); + +clean_print_color (CLEAN_COLOR_PROMPT ); +printf (_ ("Input ignore patterns>> " )); +clean_print_color (CLEAN_COLOR_RESET ); +if (strbuf_getline (&confirm, stdin,'\n' ) !=EOF ) +strbuf_trim (&confirm); +else +putchar ('\n' ); + +/* quit filter_by_pattern mode if press ENTER or Ctrl-D */ +if (!confirm.len) +break ; + +memset (&dir,0 ,sizeof (dir)); + el =add_exclude_list (&dir,EXC_CMDL ,"manual exclude" ); + ignore_list =strbuf_split_max (&confirm,' ' ,0 ); + +for (i =0 ; ignore_list[i]; i++) { +strbuf_trim (ignore_list[i]); +if (!ignore_list[i]->len) +continue ; + +add_exclude (ignore_list[i]->buf,"" ,0 , el, -(i+1 )); + } + + changed =0 ; +for_each_string_list_item (item, &del_list) { +int dtype =DT_UNKNOWN ; + +if (is_excluded (&dir, item->string, &dtype)) { + *item->string ='\0' ; + changed++; + } + } + +if (changed) { +string_list_remove_empty_items (&del_list,0 ); + }else { +clean_print_color (CLEAN_COLOR_ERROR ); +printf_ln (_ ("WARNING: Cannot find items matched by: %s" ), confirm.buf); +clean_print_color (CLEAN_COLOR_RESET ); + } + +strbuf_list_free (ignore_list); +clear_directory (&dir); + } + +strbuf_release (&confirm); +return 0 ; +} +static int quit_cmd (void ) {string_list_clear (&del_list,0 );
@@ -626,6 +692,7 @@ static int help_cmd(void)clean_print_color (CLEAN_COLOR_HELP ); printf_ln(_( "clean - start cleaning\n" + "filter by pattern - exclude items from deletion\n" "quit - stop cleaning\n" "help - this screen\n" "? - help for prompt selection"
@@ -641,6 +708,7 @@ static void interactive_main_loop(void)struct menu_stuff menu_stuff;struct menu_item menus[] = { {'c' ,"clean" ,0 , clean_cmd}, + {'f' ,"filter by pattern" ,0 , filter_by_patterns_cmd}, {'q' ,"quit" ,0 , quit_cmd}, {'h' ,"help" ,0 , help_cmd}, };
Signature · 1 line
1.8.3.rc2.380.g956c2b2
PATCH v11 12/15 git-clean: add select by numbers interactive action
In reply to Jiang XinDraw a multiple choice menu using list_and_choose to select items to be deleted by numbers.
User can input:
* 1,5-7 : select 1,5,6,7 items to be deleted * * : select all items to be deleted * -* : unselect all, nothing will be deleted
* : (empty) finish selecting, and return back to main menu
--- builtin/clean.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+)
builtin/clean.c +39 -0
@@ -680,6 +680,43 @@ static int filter_by_patterns_cmd(void)return 0 ; } +static int select_by_numbers_cmd (void ) +{ +struct menu_opts menu_opts; +struct menu_stuff menu_stuff; +struct string_list_item *items; +int *chosen; +int i, j; + + menu_opts.header =NULL ; + menu_opts.prompt =N_ ("Select items to delete" ); + menu_opts.flags =0 ; + + menu_stuff.type =MENU_STUFF_TYPE_STRING_LIST ; + menu_stuff.stuff = &del_list; + menu_stuff.nr = del_list.nr; + + chosen =list_and_choose (&menu_opts, &menu_stuff); + items = del_list.items; +for (i =0 , j =0 ; i < del_list.nr; i++) { +if (i < chosen[j]) { + *(items[i].string) ='\0' ; + }else if (i == chosen[j]) { +/* delete selected item */ + j++; +continue ; + }else { +/* end of chosen (chosen[j] == EOF), won't delete */ + *(items[i].string) ='\0' ; + } + } + +string_list_remove_empty_items (&del_list,0 ); + +free (chosen); +return 0 ; +} +static int quit_cmd (void ) {string_list_clear (&del_list,0 );
@@ -693,6 +730,7 @@ static int help_cmd(void) printf_ln(_( "clean - start cleaning\n" "filter by pattern - exclude items from deletion\n" + "select by numbers - select items to be deleted by numbers\n" "quit - stop cleaning\n" "help - this screen\n" "? - help for prompt selection"
@@ -709,6 +747,7 @@ static void interactive_main_loop(void)struct menu_item menus[] = { {'c' ,"clean" ,0 , clean_cmd}, {'f' ,"filter by pattern" ,0 , filter_by_patterns_cmd}, + {'s' ,"select by numbers" ,0 , select_by_numbers_cmd}, {'q' ,"quit" ,0 , quit_cmd}, {'h' ,"help" ,0 , help_cmd}, };
Signature · 1 line
1.8.3.rc2.380.g956c2b2
v12 cover Interactive git-clean
In reply to Jiang XinI feel change the order of patch 01/15 and patch 02/15 is better. I.E. Add test cases for relative_path first, then refactor relative_path and fix the test cases.
Also fix wrong indent in t/7301.
Differences with gitster/jx/clean-interactive:
Documentation/git-clean.txt +2 -2
@@ -82,8 +82,8 @@ and type return, like this: ------------ *** Commands *** - 1: clean 2: filter by pattern 3: select by numbers - 4: ask each 5: quit 6: help + 1: clean 2: filter by pattern 3: select by numbers + 4: ask each 5: quit 6: help What now> 1 ------------
builtin/clean.c +36 -31
128 lines not on this page · raw message
builtin/ls-files.c +2 -2
@@ -46,11 +46,11 @@ static const char *tag_modified = "";static const char *tag_skip_worktree ="" ;static const char *tag_resolve_undo ="" ; -static void write_name (const char * name) +static void write_name (const char *name) {/* turn off prefix, if run with "--full-name" */ -write_name_quoted_relative (name, prefix_len >0 ? prefix :NULL , +write_name_quoted_relative (name, prefix_len ? prefix :NULL , stdout, line_terminator); }
t/t0060-path-utils.sh +23 -0
@@ -12,6 +12,11 @@ norm_path() { "test \"\$(test-path-utils normalize_path_copy '$1')\" = '$2'" } +relative_path() { + test_expect_success $4 "relative path: $1 $2 => $3" \ + "test \"\$(test-path-utils relative_path '$1' '$2')\" = '$3'" +} + # On Windows, we are using MSYS's bash, which mangles the paths. # Absolute paths are anchored at the MSYS installation directory, # which means that the path / accounts for this many characters:
@@ -183,4 +188,22 @@ test_expect_success SYMLINKS 'real path works on symlinks' ' test "$sym" = "$(test-path-utils real_path "$dir2/syml")" ' +relative_path /a/b/c/ /a/b/ c/ +relative_path /a/b/c/ /a/b c/ +relative_path /a//b//c/ //a/b// c/ +relative_path /a/b /a/b ./ +relative_path /a/b/ /a/b ./ +relative_path /a /a/b ../ +relative_path / /a/b/ ../../ +relative_path /a/c /a/b/ ../c +relative_path /a/c /a/b ../c +relative_path /a/b "<empty>" /a/b +relative_path /a/b "<null>" /a/b +relative_path "<empty>" /a/b ./ +relative_path "<empty>" "<empty>" ./ +relative_path "<empty>" "<null>" ./ +relative_path "<null>" "<empty>" ./ +relative_path "<null>" "<null>" ./ +relative_path "<null>" /a/b ./ + test_done
test-path-utils.c +27 -0
@@ -1,6 +1,19 @@#include "cache.h" #include "string-list.h" +#define PARSE_ARGV_STRING (var, input) do { \ + if (!strcmp(input, "<null>")) { \ + var = NULL; \ + } else if (!strcmp(input, "<empty>")) { \ + var = ""; \ + } else if (*input == '<' || *input == '(') { \ + fprintf(stderr, "Bad value: %s\n", input); \ + return 1; \ + } else { \ + var = input; \ + } \ +} while (0) +/* * A "string_list_each_func_t" function that normalizes an entry from * GIT_CEILING_DIRECTORIES. If the path is unusable for some reason,
@@ -103,6 +116,20 @@ int main(int argc, char **argv)return 0 ; } +if (argc ==4 && !strcmp (argv[1 ],"relative_path" )) { +struct strbuf sb =STRBUF_INIT ; +const char *abs, *base, *rel; +PARSE_ARGV_STRING (abs, argv[2 ]); +PARSE_ARGV_STRING (base, argv[3 ]); + rel =relative_path (abs, base, &sb); +if (!rel) +puts ("(null)" ); +else +puts (strlen (rel) >0 ? rel :"(empty)" ); +strbuf_release (&sb); +return 0 ; + } +fprintf (stderr,"%s: unknown function name: %s\n" , argv[0 ], argv[1 ] ? argv[1 ] :"(there was none)" );return 1 ;
Jiang Xin (15):
test: add test cases for relative_path
path.c: refactor relative_path(), not only strip prefix quote.c: remove path_relative, use relative_path instead Refactor quote_path_relative, remove unused params Refactor write_name_quoted_relative, remove unused params
git-clean: refactor git-clean into two phases
git-clean: add support for -i/--interactive
git-clean: show items of del_list in columns
git-clean: add colors to interactive git-clean
git-clean: use a git-add-interactive compatible UI
git-clean: add filter by pattern interactive action
git-clean: add select by numbers interactive action
git-clean: add ask each interactive action
git-clean: add documentation for interactive git-clean
test: add t7301 for git-clean--interactive
Documentation/config.txt | 21 +- Documentation/git-clean.txt | 71 +++- builtin/clean.c | 779 +++++++++++++++++++++++++++++++++++++++++-- builtin/grep.c | 5 +- builtin/ls-files.c | 16 +- cache.h | 2 +- path.c | 112 +++++-- quote.c | 65 +--- quote.h | 7 +- setup.c | 5 +- t/t0060-path-utils.sh | 23 ++ t/t7301-clean-interactive.sh | 439 ++++++++++++++++++++++++ test-path-utils.c | 27 ++ wt-status.c | 17 +- 14 files changed, 1438 insertions(+), 151 deletions(-) create mode 100755 t/t7301-clean-interactive.sh
Signature · 1 line
1.8.3.rc2.26.g7472058
PATCH v12 1/15 test: add test cases for relative_path
Add subcommand "relative_path" in test-path-utils, and add test cases in t0060.
--- t/t0060-path-utils.sh | 26 ++++++++++++++++++++++++++ test-path-utils.c | 25 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+)
t/t0060-path-utils.sh +26 -0
@@ -12,6 +12,11 @@ norm_path() { "test \"\$(test-path-utils normalize_path_copy '$1')\" = '$2'" } +relative_path() { + test_expect_success $4 "relative path: $1 $2 => $3" \ + "test \"\$(test-path-utils relative_path '$1' '$2')\" = '$3'" +} + # On Windows, we are using MSYS's bash, which mangles the paths. # Absolute paths are anchored at the MSYS installation directory, # which means that the path / accounts for this many characters:
@@ -183,4 +188,25 @@ test_expect_success SYMLINKS 'real path works on symlinks' ' test "$sym" = "$(test-path-utils real_path "$dir2/syml")" ' +relative_path /a/b/c/ /a/b/ c/ +relative_path /a/b/c/ /a/b c/ +relative_path /a//b//c/ //a/b// c/ +relative_path /a/b /a/b . +relative_path /a/b/ /a/b . +relative_path /a /a/b /a +relative_path / /a/b/ / +relative_path /a/c /a/b/ /a/c +relative_path /a/c /a/b /a/c +relative_path /a/b "<empty>" /a/b +relative_path /a/b "<null>" /a/b +relative_path "<empty>" /a/b "(empty)" +relative_path "<empty>" "<empty>" "(empty)" +relative_path "<empty>" "<null>" "(empty)" +relative_path "<null>" "<empty>" "(null)" +relative_path "<null>" "<null>" "(null)" + +test_expect_failure 'relative path: <null> /a/b => segfault' ' + test-path-utils relative_path "<null>" "/a/b" +' + test_done
test-path-utils.c +25 -0
@@ -1,6 +1,19 @@#include "cache.h" #include "string-list.h" +#define PARSE_ARGV_STRING (var, input) do { \ + if (!strcmp(input, "<null>")) { \ + var = NULL; \ + } else if (!strcmp(input, "<empty>")) { \ + var = ""; \ + } else if (*input == '<' || *input == '(') { \ + fprintf(stderr, "Bad value: %s\n", input); \ + return 1; \ + } else { \ + var = input; \ + } \ +} while (0) +/* * A "string_list_each_func_t" function that normalizes an entry from * GIT_CEILING_DIRECTORIES. If the path is unusable for some reason,
@@ -103,6 +116,18 @@ int main(int argc, char **argv)return 0 ; } +if (argc ==4 && !strcmp (argv[1 ],"relative_path" )) { +const char *abs, *base, *rel; +PARSE_ARGV_STRING (abs, argv[2 ]); +PARSE_ARGV_STRING (base, argv[3 ]); + rel =relative_path (abs, base); +if (!rel) +puts ("(null)" ); +else +puts (strlen (rel) >0 ? rel :"(empty)" ); +return 0 ; + } +fprintf (stderr,"%s: unknown function name: %s\n" , argv[0 ], argv[1 ] ? argv[1 ] :"(there was none)" );return 1 ;
Signature · 1 line
1.8.3.rc2.26.g7472058
Re: [PATCH v12 01/15] test: add test cases for relative_path
Quoted 10 lines · Jiang Xin
test-path-utils.c +2
-0@@ -1,6 +1,19 @@#include "cache.h" #include "string-list.h" +#define PARSE_ARGV_STRING (var, input) do { \ + if (!strcmp(input, "<null>")) { \
test-path-utils.c line 15
Quoted 10 lines
test-path-utils.c +10
-0+ var =NULL ; \ + }else if (!strcmp (input,"<empty>" )) { \ + var ="" ; \ + }else if (*input =='<' || *input =='(' ) { \ +fprintf (stderr,"Bad value: %s\n" , input); \ +return 1 ; \ + }else { \ + var = input; \ + } \ +}while (0 )
Yuck. Why does it have to be a macro like this? I do not think "because we may return" justifies it.
Quoted 22 lines
test-path-utils.c +12
-0/* * A "string_list_each_func_t" function that normalizes an entry from * GIT_CEILING_DIRECTORIES. If the path is unusable for some reason, @@ -103,6 +116,18 @@ int main(int argc, char **argv)return 0 ; } +if (argc ==4 && !strcmp (argv[1 ],"relative_path" )) { +const char *abs, *base, *rel; +PARSE_ARGV_STRING (abs, argv[2 ]); +PARSE_ARGV_STRING (base, argv[3 ]); + rel =relative_path (abs, base); +if (!rel) +puts ("(null)" ); +else +puts (strlen (rel) >0 ? rel :"(empty)" ); +return 0 ; + } +fprintf (stderr,"%s: unknown function name: %s\n" , argv[0 ], argv[1 ] ? argv[1 ] :"(there was none)" );return 1 ;
v13 cover interactive git-clean
2013/5/22 Junio C Hamano <[email protected]>:
Quoted 10 lines
Quoted 8 lines · Jiang Xin
test-path-utils.c +0
-0@@ -1,6 +1,19 @@#include "cache.h" #include "string-list.h"
Quoted 15 lines
test-path-utils.c line 15
Quoted 12 lines
test-path-utils.c +12
-0+#define PARSE_ARGV_STRING (var, input) do { \ + if (!strcmp(input, "<null>")) { \ + var = NULL; \ + } else if (!strcmp(input, "<empty>")) { \ + var = ""; \ + } else if (*input == '<' || *input == '(') { \ + fprintf(stderr, "Bad value: %s\n", input); \ + return 1; \ + } else { \ + var = input; \ + } \ +} while (0)Yuck. Why does it have to be a macro like this? I do not think "because we may return" justifies it.
Rewrite patch 01/15 for this, and only patch 01/15 and 02/15 need update. Please review.
Jiang Xin (15):
test: add test cases for relative_path
path.c: refactor relative_path(), not only strip prefix quote.c: remove path_relative, use relative_path instead Refactor quote_path_relative, remove unused params Refactor write_name_quoted_relative, remove unused params
git-clean: refactor git-clean into two phases
git-clean: add support for -i/--interactive
git-clean: show items of del_list in columns
git-clean: add colors to interactive git-clean
git-clean: use a git-add-interactive compatible UI
git-clean: add filter by pattern interactive action
git-clean: add select by numbers interactive action
git-clean: add ask each interactive action
git-clean: add documentation for interactive git-clean
test: add t7301 for git-clean--interactive
Documentation/config.txt | 21 +- Documentation/git-clean.txt | 71 +++- builtin/clean.c | 778 +++++++++++++++++++++++++++++++++++++++++-- builtin/grep.c | 5 +- builtin/ls-files.c | 16 +- cache.h | 2 +- path.c | 112 +++++-- quote.c | 65 +--- quote.h | 7 +- setup.c | 5 +- t/t0060-path-utils.sh | 23 ++ t/t7301-clean-interactive.sh | 439 ++++++++++++++++++++++++ test-path-utils.c | 27 ++ wt-status.c | 17 +- 14 files changed, 1437 insertions(+), 151 deletions(-) create mode 100755 t/t7301-clean-interactive.sh
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v13 1/15 test: add test cases for relative_path
Add subcommand "relative_path" in test-path-utils, and add test cases in t0060.
--- t/t0060-path-utils.sh | 26 ++++++++++++++++++++++++++ test-path-utils.c | 25 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+)
t/t0060-path-utils.sh +26 -0
@@ -12,6 +12,11 @@ norm_path() { "test \"\$(test-path-utils normalize_path_copy '$1')\" = '$2'" } +relative_path() { + test_expect_success $4 "relative path: $1 $2 => $3" \ + "test \"\$(test-path-utils relative_path '$1' '$2')\" = '$3'" +} + # On Windows, we are using MSYS's bash, which mangles the paths. # Absolute paths are anchored at the MSYS installation directory, # which means that the path / accounts for this many characters:
@@ -183,4 +188,25 @@ test_expect_success SYMLINKS 'real path works on symlinks' ' test "$sym" = "$(test-path-utils real_path "$dir2/syml")" ' +relative_path /a/b/c/ /a/b/ c/ +relative_path /a/b/c/ /a/b c/ +relative_path /a//b//c/ //a/b// c/ +relative_path /a/b /a/b . +relative_path /a/b/ /a/b . +relative_path /a /a/b /a +relative_path / /a/b/ / +relative_path /a/c /a/b/ /a/c +relative_path /a/c /a/b /a/c +relative_path /a/b "<empty>" /a/b +relative_path /a/b "<null>" /a/b +relative_path "<empty>" /a/b "(empty)" +relative_path "<empty>" "<empty>" "(empty)" +relative_path "<empty>" "<null>" "(empty)" +relative_path "<null>" "<empty>" "(null)" +relative_path "<null>" "<null>" "(null)" + +test_expect_failure 'relative path: <null> /a/b => segfault' ' + test-path-utils relative_path "<null>" "/a/b" +' + test_done
test-path-utils.c +25 -0
@@ -28,6 +28,19 @@ static int normalize_ceiling_entry(struct string_list_item *item, void *unused)return 1 ; } +static void normalize_argv_string (const char **var,const char *input) +{ +if (!strcmp (input,"<null>" )) + *var =NULL ; +else if (!strcmp (input,"<empty>" )) + *var ="" ; +else + *var = input; + +if (*var && (**var =='<' || **var =='(' )) +die ("Bad value: %s\n" , input); +} +int main (int argc,char **argv) {if (argc ==3 && !strcmp (argv[1 ],"normalize_path_copy" )) {
@@ -103,6 +116,18 @@ int main(int argc, char **argv)return 0 ; } +if (argc ==4 && !strcmp (argv[1 ],"relative_path" )) { +const char *abs, *base, *rel; +normalize_argv_string (&abs, argv[2 ]); +normalize_argv_string (&base, argv[3 ]); + rel =relative_path (abs, base); +if (!rel) +puts ("(null)" ); +else +puts (strlen (rel) >0 ? rel :"(empty)" ); +return 0 ; + } +fprintf (stderr,"%s: unknown function name: %s\n" , argv[0 ], argv[1 ] ? argv[1 ] :"(there was none)" );return 1 ;
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v13 2/15 path.c: refactor relative_path(), not only strip prefix
In reply to Jiang XinOriginal design of relative_path() is simple, just strip the prefix (*base) from the absolute path (*abs). In most cases, we need a real relative path, such as: ../foo, ../../bar. That's why there is another reimplementation (path_relative()) in quote.c.
Refactor relative_path() in path.c to return real relative path, so that user can reuse this function without reimplement his/her own. I will use this method for interactive git-clean later. Some of the implementations are borrowed from path_relative() in quote.c.
Different results for relative_path() before and after this refactor:
abs path base path relative (original) relative (refactor)
======== ========= =================== ===================
/a/b/c/ /a/b c/ c/
/a/b//c/ //a///b/ c/ c/
/a/b /a/b . ./
/a/b/ /a/b . ./
/a /a/b/ /a ../
/ /a/b/ / ../../
/a/c /a/b/ /a/c ../c
/a/b (empty) /a/b /a/b
/a/b (null) /a/b /a/b
(empty) /a/b (empty) ./
(null) (empty) (null) ./
(null) /a/b (segfault) ./--- cache.h | 2 +- path.c | 112 +++++++++++++++++++++++++++++++++++++++----------- setup.c | 5 ++- t/t0060-path-utils.sh | 27 ++++++------ test-path-utils.c | 4 +- 5 files changed, 107 insertions(+), 43 deletions(-)
cache.h +1 -1
@@ -737,7 +737,7 @@ int is_directory(const char *);const char *real_path (const char *path);const char *real_path_if_valid (const char *path);const char *absolute_path (const char *path); -const char *relative_path (const char *abs,const char *base); +const char *relative_path (const char *abs,const char *base,struct strbuf *sb);int normalize_path_copy (char *dst,const char *src);int longest_ancestor_length (const char *path,struct string_list *prefixes);char *strip_path_suffix (const char *path,const char *suffix);
path.c +87 -25
129 lines not on this page · raw message
setup.c +4 -1
@@ -360,6 +360,7 @@ int is_inside_work_tree(void)void setup_work_tree (void ) { +struct strbuf sb =STRBUF_INIT ;const char *work_tree, *git_dir;static int initialized =0 ;
@@ -379,8 +380,10 @@ void setup_work_tree(void)if (getenv (GIT_WORK_TREE_ENVIRONMENT ))setenv (GIT_WORK_TREE_ENVIRONMENT ,"." ,1 ); -set_git_dir (relative_path (git_dir, work_tree)); +set_git_dir (relative_path (git_dir, work_tree, &sb)); initialized =1 ; + +strbuf_release (&sb); }static int check_repository_format_gently (const char *gitdir,int *nongit_ok)
t/t0060-path-utils.sh +12 -15
@@ -191,22 +191,19 @@ test_expect_success SYMLINKS 'real path works on symlinks' ' relative_path /a/b/c/ /a/b/ c/ relative_path /a/b/c/ /a/b c/ relative_path /a//b//c/ //a/b// c/ -relative_path /a/b /a/b . -relative_path /a/b/ /a/b . -relative_path /a /a/b /a -relative_path / /a/b/ / -relative_path /a/c /a/b/ /a/c -relative_path /a/c /a/b /a/c +relative_path /a/b /a/b ./ +relative_path /a/b/ /a/b ./ +relative_path /a /a/b ../ +relative_path / /a/b/ ../../ +relative_path /a/c /a/b/ ../c +relative_path /a/c /a/b ../c relative_path /a/b "<empty>" /a/b relative_path /a/b "<null>" /a/b -relative_path "<empty>" /a/b "(empty)" -relative_path "<empty>" "<empty>" "(empty)" -relative_path "<empty>" "<null>" "(empty)" -relative_path "<null>" "<empty>" "(null)" -relative_path "<null>" "<null>" "(null)" - -test_expect_failure 'relative path: <null> /a/b => segfault' ' - test-path-utils relative_path "<null>" "/a/b" -' +relative_path "<empty>" /a/b ./ +relative_path "<empty>" "<empty>" ./ +relative_path "<empty>" "<null>" ./ +relative_path "<null>" "<empty>" ./ +relative_path "<null>" "<null>" ./ +relative_path "<null>" /a/b ./ test_done
test-path-utils.c +3 -1
@@ -117,14 +117,16 @@ int main(int argc, char **argv) }if (argc ==4 && !strcmp (argv[1 ],"relative_path" )) { +struct strbuf sb =STRBUF_INIT ;const char *abs, *base, *rel;normalize_argv_string (&abs, argv[2 ]);normalize_argv_string (&base, argv[3 ]); - rel =relative_path (abs, base); + rel =relative_path (abs, base, &sb);if (!rel)puts ("(null)" );else puts (strlen (rel) >0 ? rel :"(empty)" ); +strbuf_release (&sb);return 0 ; }
Signature · 1 line
1.8.3.rc3.368.g7c798dd
Re: [PATCH v13 02/15] path.c: refactor relative_path(), not only strip prefix
Sorry for coming late to the party.
Quoted 12 lines · Jiang Xin
Original design of
relative_path()is simple, just strip the prefix (*base) from the absolute path (*abs). In most cases, we need a real relative path, such as: ../foo, ../../bar. That's why there is another reimplementation (path_relative()) inquote.c.Refactor
relative_path()inpath.cto return real relative path, so that user can reuse this function without reimplement his/her own. I will use this method for interactive git-clean later. Some of the implementations are borrowed frompath_relative()inquote.c.Different results for
relative_path()before and after this refactor:
Quoted 14 lines
abs path base path relative (original) relative (refactor)
======== ========= =================== ===================
/a/b/c/ /a/b c/ c/
/a/b//c/ //a///b/ c/ c/
/a/b /a/b . ./
/a/b/ /a/b . ./
/a /a/b/ /a ../
/ /a/b/ / ../../
/a/c /a/b/ /a/c ../c
/a/b (empty) /a/b /a/b
/a/b (null) /a/b /a/b
(empty) /a/b (empty) ./
(null) (empty) (null) ./
(null) /a/b (segfault) ./The old and new versions both seem to be (differently) inconsistent about when the output has a trailing slash. What is the rule?
Quoted 7 lines
path.c +0
-0@@ -441,42 +441,104 @@ int adjust_shared_perm(const char *path)return 0 ; }
path.c int adjust_shared_perm(const char *path) · line 451
Quoted 10 lines
path.c +8
-1-const char *relative_path (const char *abs,const char *base) +/* +* Give path as relative to prefix. +* +* The strbuf may or may not be used, so do not assume it contains the +* returned path. +*/ +const char *relative_path (const char *abs,const char *base, +struct strbuf *sb)
Thanks for adding documentation. But I think it could be improved:
* The comment refers to "path" and "prefix" but the function parameters are "abs" and "base". I suggest making them agree.
* Who owns the memory pointed to by the return value?
* The comment says that "the strbuf may or may not be used". So why is it part of the interface? (I suppose it is because the strbuf might be given ownership of the returned memory if it has to be allocated.) Would it be more straightforward to *always* return the result in the strbuf?
* Please document when the return value contains a trailing slash and also that superfluous internal slashes are (apparently) normalized away.
* Leading double-slashes have a special meaning on some operating systems. The old and new versions of this function both seem to ignore differences between initial slashes. Perhaps somebody who knows the rules better could say whether this is an issue but I guess the problem would rarely be encountered in practice.
Michael
Signature · 3 lines
Michael Haggerty [email protected] http://softwareswirl.blogspot.com/
Re: [PATCH v13 02/15] path.c: refactor relative_path(), not only strip prefix
Quoted 17 lines · Michael Haggerty
Quoted 16 lines
Different results for
relative_path()before and after this refactor:abs path base path relative (original) relative (refactor) ======== ========= =================== =================== /a/b/c/ /a/b c/ c/ /a/b//c/ //a///b/ c/ c/ /a/b /a/b . ./ /a/b/ /a/b . ./ /a /a/b/ /a ../ / /a/b/ / ../../ /a/c /a/b/ /a/c ../c /a/b (empty) /a/b /a/b /a/b (null) /a/b /a/b (empty) /a/b (empty) ./ (null) (empty) (null) ./ (null) /a/b (segfault) ./
Quoted 2 lines
The old and new versions both seem to be (differently) inconsistent about when the output has a trailing slash. What is the rule?
That is a good point. At least adding / at the end of "." seems unneeded, given that the output in some cases have no trailing slash, forcing a caller who wanted to get a directory to append a trailing path components to it to check if it needs to add one before doing so. Always adding a slash / to the output may sound consistent, but it is not quite; e.g. "/a/c based on /a/b is ../c" case may be referring to a non directory /a/c and ensuring a trailing slash to produce ../c/ is actively wrong.
"The caller knows" rule might work (I am thinking aloud, without looking at existing callers to see what would break, only to see if a consistent and simple-to-explain rule is possible).
When the caller asks to turn /a/b relative to /a/b (or /a/b/, /a//b/./), then we do not know (or we do not want to know) if the caller means it to be a directory with the intention of appending something after it, so just return ".". When the caller asks to turn /a/b/ relative to the same base, return "./" with the trailing slash. Remember if the "abs path" side had a trailing slash, normalize both input (turning "/./" into "/" and "foo/bar/../" into foo/", squashing multiple slashes into one, etc.) and then strip the trailing slash from them, do the usual "comparison and replacement of leading path components with series of ../" and then append a slash if the original had one, or something?
Quoted 22 lines
Quoted 6 lines
index 04ff..0174d 100644
path.c +0
-0@@ -441,42 +441,104 @@ int adjust_shared_perm(const char *path)return 0 ; }path.c int adjust_shared_perm(const char *path) · line 451
Quoted 10 lines
path.c +8
-1-const char *relative_path (const char *abs,const char *base) +/* +* Give path as relative to prefix. +* +* The strbuf may or may not be used, so do not assume it contains the +* returned path. +*/ +const char *relative_path (const char *abs,const char *base, +struct strbuf *sb)Thanks for adding documentation. But I think it could be improved:
* The comment refers to "path" and "prefix" but the function parameters are "abs" and "base". I suggest making them agree.
Quoted 7 lines
* Who owns the memory pointed to by the return value?
* The comment says that "the strbuf may or may not be used". So why is it part of the interface? (I suppose it is because the strbuf might be given ownership of the returned memory if it has to be allocated.) Would it be more straightforward to *always* return the result in the strbuf?
This comes from the original in quote.c, I think. The caller supplies a strbuf as a scratchpad area and releasing it is the caller's responsibility. If the function does not need any scratchpad area (i.e. the answer is a substring of the input), the result may point into the abs.
So the calling convention is:
struct strbuf scratch = STRBUF_INIT;
const char *result = relative(abs, base, &scratch);
use(result);
strbuf_release(&scratch);and the lifetime rule is "consume it before either abs or scratch is changed".
Re: [PATCH v13 02/15] path.c: refactor relative_path(), not only strip prefix
In reply to Michael Haggerty2013/5/22 Michael Haggerty <[email protected]>:
Quoted 1 line
Sorry for coming late to the party.
I am on a business travel, and respond late also. ;-)
Quoted 19 lines · Jiang Xin
Quoted 16 lines
Different results for
relative_path()before and after this refactor:abs path base path relative (original) relative (refactor) ======== ========= =================== =================== /a/b/c/ /a/b c/ c/ /a/b//c/ //a///b/ c/ c/ /a/b /a/b . ./ /a/b/ /a/b . ./ /a /a/b/ /a ../ / /a/b/ / ../../ /a/c /a/b/ /a/c ../c /a/b (empty) /a/b /a/b /a/b (null) /a/b /a/b (empty) /a/b (empty) ./ (null) (empty) (null) ./ (null) /a/b (segfault) ./
Quoted 2 lines
The old and new versions both seem to be (differently) inconsistent about when the output has a trailing slash. What is the rule?
The reason for introducing patch 02/15 is that we don't want to reinvent the wheel. Patch 06/15 (git-clean: refactor git-clean into two phases) needs to save relative_path of each git-clean candidate file/directory in del_list, but the public method in path.c (i.e. relative_path) is not powerful, and static method in quote.c (i.e. path_relative) can note be used directly. One way is to enhanced relative_path in path.c, like this patch.
Since we combine the two methods (relative_path in path.c and path_relative in quote.c), the new relative_path must be compatible with the original two methods.
relative_path in path.c
=======================
relative_path is called in one place:
if (getenv(GIT_WORK_TREE_ENVIRONMENT))
setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1);
set_git_dir(relative_path(git_dir, work_tree));
initialized = 1;and set_git_dir only set the environment GIT_DIR_ENVIRONMENT like this:
int set_git_dir(const char *path)
{
if (setenv(GIT_DIR_ENVIRONMENT, path, 1))
return error("Could not set GIT_DIR to '%s'", path);
setup_git_env();
return 0;
}So the only restraint for relative_path is that the return value can not be blank. If the abs and base arguments for relative_path are the same, the return value should be "." ("./" is also OK), then set the envionment GIT_DIR_ENVIRONMENT to "." (or "./").
path_relative in quote.c
========================
We can not simply move "path_relative" in quote.c to "relative_path" in path.c directly. It is because:
* The arguments for "relative_path" are from user input. So must validate (remove duplicate slash) before use. But "path_relative" does not check duplicate slash in arguments.
* "path_relative" will return blank string, if abs and base are the same.
Also I noticed that "quote_path_relative" of quote.c (which calls path_relative) will transform the blank string from path_relative to "./" (not ".")
char *quote_path_relative(const char *in, int len,
...
const char *rel = path_relative(in, len, &sb, prefix, -1);
...
if (!out->len)
strbuf_addstr(out, "./");That's why the "path_relative" in path.c refactor the output of "." into "./".
Quoted 21 lines
Quoted 8 lines
path.c +0
-0@@ -441,42 +441,104 @@ int adjust_shared_perm(const char *path)return 0 ; }path.c int adjust_shared_perm(const char *path) · line 451
Quoted 10 lines
path.c +8
-1-const char *relative_path (const char *abs,const char *base) +/* +* Give path as relative to prefix. +* +* The strbuf may or may not be used, so do not assume it contains the +* returned path. +*/ +const char *relative_path (const char *abs,const char *base, +struct strbuf *sb)Thanks for adding documentation. But I think it could be improved:
Quoted 2 lines
* The comment refers to "path" and "prefix" but the function parameters are "abs" and "base". I suggest making them agree.
Yes, it will be nice to update the comments.
Quoted 8 lines
* Who owns the memory pointed to by the return value?
* The comment says that "the strbuf may or may not be used". So why is it part of the interface? (I suppose it is because the strbuf might be given ownership of the returned memory if it has to be allocated.) Would it be more straightforward to *always* return the result in the strbuf?
Quoted 8 lines
* Please document when the return value contains a trailing slash and also that superfluous internal slashes are (apparently) normalized away.
* Leading double-slashes have a special meaning on some operating systems. The old and new versions of this function both seem to ignore differences between initial slashes. Perhaps somebody who knows the rules better could say whether this is an issue but I guess the problem would rarely be encountered in practice.
See Junio's reply.
Quoted 7 lines
Michael
--
Michael Haggerty
[email protected]
http://softwareswirl.blogspot.com/
Signature · 8 lines
蒋鑫 北京群英汇信息技术有限公司 邮件: [email protected] 网址: http://www.ossxp.com/ 博客: http://www.worldhello.net/ 微博: http://weibo.com/gotgit/ 电话: 18601196889
Re: [PATCH v13 02/15] path.c: refactor relative_path(), not only strip prefix
2013/5/26 Jiang Xin <[email protected]>:
Quoted 60 lines
2013/5/22 Michael Haggerty <[email protected]>:
Quoted 2 lines
The old and new versions both seem to be (differently) inconsistent about when the output has a trailing slash. What is the rule?
The reason for introducing patch 02/15 is that we don't want to reinvent the wheel. Patch 06/15 (git-clean: refactor git-clean into two phases) needs to save
relative_pathof each git-clean candidate file/directory indel_list, but the public method inpath.c(i.e.relative_path) is not powerful, and static method inquote.c(i.e.path_relative) can note be used directly. One way is to enhancedrelative_pathinpath.c, like this patch.Since we combine the two methods (
relative_pathinpath.candpath_relativeinquote.c), the newrelative_pathmust be compatible with the original two methods.
relative_pathinpath.c
=======================
relative_pathis called only once insetup.c:if (getenv(GIT_WORK_TREE_ENVIRONMENT)) setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1); set_git_dir(relative_path(git_dir, work_tree)); initialized = 1;and
set_git_dironly set the environmentGIT_DIR_ENVIRONMENTlike this:int set_git_dir(const char *path) { if (setenv(GIT_DIR_ENVIRONMENT, path, 1)) return error("Could not set GIT_DIR to '%s'", path); setup_git_env(); return 0; }So the only restriction for
relative_pathis that the return value can not be blank. If the abs and base arguments forrelative_pathare the same, the return value should be "." ("./" is also OK), then set the envionmentGIT_DIR_ENVIRONMENTto "." (or "./").
path_relativeinquote.c
========================We can not simply move "
path_relative" inquote.cto "relative_path" inpath.cdirectly. It is because:* The arguments for "
relative_path" are from user input. So must validate (remove duplicate slash) before use. But "path_relative" does not check duplicate slash in arguments.* "
path_relative" will return blank string, if abs and base are the same.Also I noticed that "
quote_path_relative" ofquote.c(which callspath_relative) will transform the blank string frompath_relativeto "./" (not ".")
Quoted 9 lines
char *quote_path_relative(const char *in, int len, ... const char *rel = path_relative(in, len, &sb, prefix, -1); ... if (!out->len) strbuf_addstr(out, "./");That's why the "
path_relative" inpath.crefactor the output of "." into "./".
Hi, Junio
I have updated comment and commit log in this patch. You can substitute commit bd4d1 (path.c: refactor relative_path(), not only strip prefix) in jx/clean-interactive branch with this one.
-- 8< --
Subject: [PATCH] path.c: refactor relative_path(), not only strip prefix
Original design of relative_path() is simple, just strip the prefix (*base) from the absolute path (*abs). In most cases, we need a real relative path, such as: ../foo, ../../bar. That's why there is another reimplementation (path_relative()) in quote.c.
Borrowed some codes from path_relative() in quote.c to refactor relative_path() in path.c, so that it could return real relative path, and user can reuse this function without reimplement his/her own. The function path_relative() in quote.c will be substituted, and I would use the new relative_path() function when implement the interactive git-clean later.
Different results for relative_path() before and after this refactor:
abs path base path relative (original) relative (refactor)
======== ========= =================== ===================
/a/b/c/ /a/b c/ c/
/a/b//c/ //a///b/ c/ c/
/a/b /a/b . ./
/a/b/ /a/b . ./
/a /a/b/ /a ../
/ /a/b/ / ../../
/a/c /a/b/ /a/c ../c
/a/b (empty) /a/b /a/b
/a/b (null) /a/b /a/b
(empty) /a/b (empty) ./
(null) (empty) (null) ./
(null) /a/b (segfault) ./You may notice that return value "." has been changed to "./". It is because:
* Function quote_path_relative() in quote.c will show the relative path as "./" if abs(in) and base(prefix) are the same.
* Function relative_path() is called only once (in setup.c), and it will be OK for the return value as "./" instead of ".".
--- cache.h | 2 +- path.c | 112 +++++++++++++++++++++++++++++++++++++++----------- setup.c | 5 ++- t/t0060-path-utils.sh | 27 ++++++------ test-path-utils.c | 4 +- 5 files changed, 107 insertions(+), 43 deletions(-)
cache.h +1 -1
@@ -737,7 +737,7 @@ int is_directory(const char *);const char *real_path (const char *path);const char *real_path_if_valid (const char *path);const char *absolute_path (const char *path); -const char *relative_path (const char *abs,const char *base); +const char *relative_path (const char *abs,const char *base,struct strbuf *sb);int normalize_path_copy (char *dst,const char *src);int longest_ancestor_length (const char *path,struct string_list *prefixes);char *strip_path_suffix (const char *path,const char *suffix);
path.c +87 -25
129 lines not on this page · raw message
setup.c +4 -1
@@ -360,6 +360,7 @@ int is_inside_work_tree(void)void setup_work_tree (void ) { +struct strbuf sb =STRBUF_INIT ;const char *work_tree, *git_dir;static int initialized =0 ;
@@ -379,8 +380,10 @@ void setup_work_tree(void)if (getenv (GIT_WORK_TREE_ENVIRONMENT ))setenv (GIT_WORK_TREE_ENVIRONMENT ,"." ,1 ); -set_git_dir (relative_path (git_dir, work_tree)); +set_git_dir (relative_path (git_dir, work_tree, &sb)); initialized =1 ; + +strbuf_release (&sb); }static int check_repository_format_gently (const char *gitdir,int *nongit_ok)
t/t0060-path-utils.sh +12 -15
@@ -191,22 +191,19 @@ test_expect_success SYMLINKS 'real path works on symlinks' ' relative_path /a/b/c/ /a/b/ c/ relative_path /a/b/c/ /a/b c/ relative_path /a//b//c/ //a/b// c/ -relative_path /a/b /a/b . -relative_path /a/b/ /a/b . -relative_path /a /a/b /a -relative_path / /a/b/ / -relative_path /a/c /a/b/ /a/c -relative_path /a/c /a/b /a/c +relative_path /a/b /a/b ./ +relative_path /a/b/ /a/b ./ +relative_path /a /a/b ../ +relative_path / /a/b/ ../../ +relative_path /a/c /a/b/ ../c +relative_path /a/c /a/b ../c relative_path /a/b "<empty>" /a/b relative_path /a/b "<null>" /a/b -relative_path "<empty>" /a/b "(empty)" -relative_path "<empty>" "<empty>" "(empty)" -relative_path "<empty>" "<null>" "(empty)" -relative_path "<null>" "<empty>" "(null)" -relative_path "<null>" "<null>" "(null)" - -test_expect_failure 'relative path: <null> /a/b => segfault' ' - test-path-utils relative_path "<null>" "/a/b" -' +relative_path "<empty>" /a/b ./ +relative_path "<empty>" "<empty>" ./ +relative_path "<empty>" "<null>" ./ +relative_path "<null>" "<empty>" ./ +relative_path "<null>" "<null>" ./ +relative_path "<null>" /a/b ./ test_done
test-path-utils.c +3 -1
@@ -117,14 +117,16 @@ int main(int argc, char **argv) }if (argc ==4 && !strcmp (argv[1 ],"relative_path" )) { +struct strbuf sb =STRBUF_INIT ;const char *abs, *base, *rel;normalize_argv_string (&abs, argv[2 ]);normalize_argv_string (&base, argv[3 ]); - rel =relative_path (abs, base); + rel =relative_path (abs, base, &sb);if (!rel)puts ("(null)" );else puts (strlen (rel) >0 ? rel :"(empty)" ); +strbuf_release (&sb);return 0 ; }
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v13 3/15 quote.c: remove path_relative, use relative_path instead
In reply to Jiang XinSince there is an enhanced version of relative_path() in path.c, remove duplicate counterpart path_relative() in quote.c.
--- quote.c | 55 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 53 deletions(-)
quote.c +2 -53
@@ -312,75 +312,24 @@ void write_name_quotedpfx(const char *pfx, size_t pfxlen,fputc (terminator, fp); } -static const char *path_relative (const char *in,int len, -struct strbuf *sb,const char *prefix, -int prefix_len); -void write_name_quoted_relative (const char *name,size_t len,const char *prefix,size_t prefix_len,FILE *fp,int terminator) {struct strbuf sb =STRBUF_INIT ; - name =path_relative (name, len, &sb, prefix, prefix_len); + name =relative_path (name, prefix, &sb);write_name_quoted (name, fp, terminator);strbuf_release (&sb); } -/* -* Give path as relative to prefix. -* -* The strbuf may or may not be used, so do not assume it contains the -* returned path. -*/ -static const char *path_relative (const char *in,int len, -struct strbuf *sb,const char *prefix, -int prefix_len) -{ -int off, i; - -if (len <0 ) - len =strlen (in); -if (prefix_len <0 ) { -if (prefix) - prefix_len =strlen (prefix); -else - prefix_len =0 ; - } - - off =0 ; - i =0 ; -while (i < prefix_len && i < len && prefix[i] == in[i]) { -if (prefix[i] =='/' ) - off = i +1 ; - i++; - } - in += off; - len -= off; - -if (i >= prefix_len) -return in; - -strbuf_reset (sb); -strbuf_grow (sb, len); - -while (i < prefix_len) { -if (prefix[i] =='/' ) -strbuf_addstr (sb,"../" ); - i++; - } -strbuf_add (sb, in, len); - -return sb->buf; -} -/* quote path as relative to the given prefix */ char *quote_path_relative (const char *in,int len,struct strbuf *out,const char *prefix) {struct strbuf sb =STRBUF_INIT ; -const char *rel =path_relative (in, len, &sb, prefix,-1 ); +const char *rel =relative_path (in, prefix, &sb);strbuf_reset (out);quote_c_style_counted (rel,strlen (rel), out,NULL ,0 );strbuf_release (&sb);
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v13 4/15 Refactor quote_path_relative, remove unused params
In reply to Jiang XinAfter substitute path_relative() in quote.c with relative_path() from path.c, parameters (such as len and prefix_len) are obsolete in function quote_path_relative(). Remove unused parameters and change the order of parameters for quote_path_relative() function.
--- builtin/clean.c | 18 +++++++++--------- builtin/grep.c | 5 ++--- builtin/ls-files.c | 2 +- quote.c | 7 ++----- quote.h | 4 ++-- wt-status.c | 17 ++++++++--------- 6 files changed, 24 insertions(+), 29 deletions(-)
builtin/clean.c +9 -9
@@ -56,7 +56,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,if ((force_flag &REMOVE_DIR_KEEP_NESTED_GIT ) && !resolve_gitlink_ref (path->buf,"HEAD" , submodule_head)) {if (!quiet) { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);printf (dry_run ?_ (msg_would_skip_git_dir) :_ (msg_skip_git_dir), quoted.buf); }
@@ -70,7 +70,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,/* an empty dir could be removed even if it is unreadble */ res = dry_run ?0 :rmdir (path->buf);if (res) { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);warning (_ (msg_warn_remove_failed), quoted.buf); *dir_gone =0 ; }
@@ -94,7 +94,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,if (remove_dirs (path, prefix, force_flag, dry_run, quiet, &gone)) ret =1 ;if (gone) { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);string_list_append (&dels, quoted.buf); }else *dir_gone =0 ;
@@ -102,10 +102,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, }else { res = dry_run ?0 :unlink (path->buf);if (!res) { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);string_list_append (&dels, quoted.buf); }else { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);warning (_ (msg_warn_remove_failed), quoted.buf); *dir_gone =0 ; ret =1 ;
@@ -127,7 +127,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,if (!res) *dir_gone =1 ;else { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);warning (_ (msg_warn_remove_failed), quoted.buf); *dir_gone =0 ; ret =1 ;
@@ -262,7 +262,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)if (remove_dirs (&directory, prefix, rm_flags, dry_run, quiet, &gone)) errors++;if (gone && !quiet) { - qname =quote_path_relative (directory.buf, directory.len, &buf, prefix); + qname =quote_path_relative (directory.buf, prefix, &buf);printf (dry_run ?_ (msg_would_remove) :_ (msg_remove), qname); } }
@@ -272,11 +272,11 @@ int cmd_clean(int argc, const char **argv, const char *prefix)continue ; res = dry_run ?0 :unlink (ent->name);if (res) { - qname =quote_path_relative (ent->name,-1 , &buf, prefix); + qname =quote_path_relative (ent->name, prefix, &buf);warning (_ (msg_warn_remove_failed), qname); errors++; }else if (!quiet) { - qname =quote_path_relative (ent->name,-1 , &buf, prefix); + qname =quote_path_relative (ent->name, prefix, &buf);printf (dry_run ?_ (msg_would_remove) :_ (msg_remove), qname); } }
builtin/grep.c +2 -3
@@ -286,8 +286,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,struct strbuf pathbuf =STRBUF_INIT ;if (opt->relative && opt->prefix_length) { -quote_path_relative (filename + tree_name_len,-1 , &pathbuf, - opt->prefix); +quote_path_relative (filename + tree_name_len, opt->prefix, &pathbuf);strbuf_insert (&pathbuf,0 , filename, tree_name_len); }else {strbuf_addstr (&pathbuf, filename);
@@ -318,7 +317,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)struct strbuf buf =STRBUF_INIT ;if (opt->relative && opt->prefix_length) -quote_path_relative (filename,-1 , &buf, opt->prefix); +quote_path_relative (filename, opt->prefix, &buf);else strbuf_addstr (&buf, filename);
builtin/ls-files.c +1 -1
@@ -389,7 +389,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, const charif (found_dup)continue ; - name =quote_path_relative (pathspec[num],-1 , &sb, prefix); + name =quote_path_relative (pathspec[num], prefix, &sb);error ("pathspec '%s' did not match any file(s) known to git." , name); errors++;
quote.c +2 -5
@@ -325,8 +325,8 @@ void write_name_quoted_relative(const char *name, size_t len, }/* quote path as relative to the given prefix */ -char *quote_path_relative (const char *in,int len, -struct strbuf *out,const char *prefix) +char *quote_path_relative (const char *in,const char *prefix, +struct strbuf *out) {struct strbuf sb =STRBUF_INIT ;const char *rel =relative_path (in, prefix, &sb);
@@ -334,9 +334,6 @@ char *quote_path_relative(const char *in, int len,quote_c_style_counted (rel,strlen (rel), out,NULL ,0 );strbuf_release (&sb); -if (!out->len) -strbuf_addstr (out,"./" ); -return out->buf; }
quote.h +2 -2
@@ -65,8 +65,8 @@ extern void write_name_quoted_relative(const char *name, size_t len, FILE *fp, int terminator);/* quote path as relative to the given prefix */ -extern char *quote_path_relative (const char *in,int len, -struct strbuf *out,const char *prefix); +extern char *quote_path_relative (const char *in,const char *prefix, +struct strbuf *out);/* quoting as a string literal for other languages */ extern void perl_quote_print (FILE *stream,const char *src);
wt-status.c +8 -9
@@ -243,7 +243,7 @@ static void wt_status_print_unmerged_data(struct wt_status *s,struct strbuf onebuf =STRBUF_INIT ;const char *one, *how =_ ("bug" ); - one =quote_path (it->string,-1 , &onebuf, s->prefix); + one =quote_path (it->string, s->prefix, &onebuf);status_printf (s,color (WT_STATUS_HEADER , s),"\t" );switch (d->stagemask) {case 1 : how =_ ("both deleted:" );break ;
@@ -297,8 +297,8 @@ static void wt_status_print_change_data(struct wt_status *s, change_type); } - one =quote_path (one_name,-1 , &onebuf, s->prefix); - two =quote_path (two_name,-1 , &twobuf, s->prefix); + one =quote_path (one_name, s->prefix, &onebuf); + two =quote_path (two_name, s->prefix, &twobuf);status_printf (s,color (WT_STATUS_HEADER , s),"\t" );switch (status) {
@@ -706,8 +706,7 @@ static void wt_status_print_other(struct wt_status *s,struct string_list_item *it;const char *path; it = &(l->items[i]); - path =quote_path (it->string,strlen (it->string), - &buf, s->prefix); + path =quote_path (it->string, s->prefix, &buf);if (column_active (s->colopts)) {string_list_append (&output, path);continue ;
@@ -1289,7 +1288,7 @@ static void wt_shortstatus_unmerged(struct string_list_item *it, }else {struct strbuf onebuf =STRBUF_INIT ;const char *one; - one =quote_path (it->string,-1 , &onebuf, s->prefix); + one =quote_path (it->string, s->prefix, &onebuf);printf (" %s\n" , one);strbuf_release (&onebuf); }
@@ -1317,7 +1316,7 @@ static void wt_shortstatus_status(struct string_list_item *it,struct strbuf onebuf =STRBUF_INIT ;const char *one;if (d->head_path) { - one =quote_path (d->head_path,-1 , &onebuf, s->prefix); + one =quote_path (d->head_path, s->prefix, &onebuf);if (*one !='"' &&strchr (one,' ' ) !=NULL ) {putchar ('"' );strbuf_addch (&onebuf,'"' );
@@ -1326,7 +1325,7 @@ static void wt_shortstatus_status(struct string_list_item *it,printf ("%s -> " , one);strbuf_release (&onebuf); } - one =quote_path (it->string,-1 , &onebuf, s->prefix); + one =quote_path (it->string, s->prefix, &onebuf);if (*one !='"' &&strchr (one,' ' ) !=NULL ) {putchar ('"' );strbuf_addch (&onebuf,'"' );
@@ -1345,7 +1344,7 @@ static void wt_shortstatus_other(struct string_list_item *it, }else {struct strbuf onebuf =STRBUF_INIT ;const char *one; - one =quote_path (it->string,-1 , &onebuf, s->prefix); + one =quote_path (it->string, s->prefix, &onebuf);color_fprintf (s->fp,color (WT_STATUS_UNTRACKED , s),"%s" , sign);printf (" %s\n" , one);strbuf_release (&onebuf);
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v13 5/15 Refactor write_name_quoted_relative, remove unused params
In reply to Jiang XinAfter substitute path_relative() in quote.c with relative_path() from path.c, parameters (such as len and prefix_len) are obsolete in function write_name_quoted_relative(). Remove unused parameters from write_name_quoted_relative() and related functions.
--- builtin/ls-files.c | 14 ++++++++------ quote.c | 3 +-- quote.h | 3 +-- 3 files changed, 10 insertions(+), 10 deletions(-)
builtin/ls-files.c +8 -6
@@ -46,10 +46,12 @@ static const char *tag_modified = "";static const char *tag_skip_worktree ="" ;static const char *tag_resolve_undo ="" ; -static void write_name (const char * name,size_t len) +static void write_name (const char *name) { -write_name_quoted_relative (name, len, prefix, prefix_len, stdout, - line_terminator); + +/* turn off prefix, if run with "--full-name" */ +write_name_quoted_relative (name, prefix_len ? prefix :NULL , + stdout, line_terminator); }static void show_dir_entry (const char *tag,struct dir_entry *ent)
@@ -63,7 +65,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)return ;fputs (tag, stdout); -write_name (ent->name, ent->len); +write_name (ent->name); }static void show_other_files (struct dir_struct *dir)
@@ -163,7 +165,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)find_unique_abbrev (ce->sha1,abbrev), ce_stage(ce)); } -write_name (ce->name,ce_namelen (ce)); +write_name (ce->name);if (debug_mode) {printf (" ctime: %d:%d\n" , ce->ce_ctime.sec, ce->ce_ctime.nsec);printf (" mtime: %d:%d\n" , ce->ce_mtime.sec, ce->ce_mtime.nsec);
@@ -196,7 +198,7 @@ static void show_ru_info(void)printf ("%s%06o %s %d\t" , tag_resolve_undo, ui->mode[i],find_unique_abbrev (ui->sha1[i], abbrev), i +1 ); -write_name (path, len); +write_name (path); } } }
quote.c +1 -2
@@ -312,8 +312,7 @@ void write_name_quotedpfx(const char *pfx, size_t pfxlen,fputc (terminator, fp); } -void write_name_quoted_relative (const char *name,size_t len, -const char *prefix,size_t prefix_len, +void write_name_quoted_relative (const char *name,const char *prefix,FILE *fp,int terminator) {struct strbuf sb =STRBUF_INIT ;
quote.h +1 -2
@@ -60,8 +60,7 @@ extern void quote_two_c_style(struct strbuf *, const char *, const char *, int);extern void write_name_quoted (const char *name,FILE *,int terminator);extern void write_name_quotedpfx (const char *pfx,size_t pfxlen,const char *name,FILE *,int terminator); -extern void write_name_quoted_relative (const char *name,size_t len, -const char *prefix,size_t prefix_len, +extern void write_name_quoted_relative (const char *name,const char *prefix,FILE *fp,int terminator);/* quote path as relative to the given prefix */
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v13 6/15 git-clean: refactor git-clean into two phases
In reply to Jiang XinBefore introducing interactive git-clean, refactor git-clean operations into two phases:
* hold cleaning items in del_list,
* and remove them in a separate loop at the end.
We will introduce interactive git-clean between the two phases. The interactive git-clean will show what would be done and must confirm before do real cleaning.
--- builtin/clean.c | 64 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 19 deletions(-)
builtin/clean.c +45 -19
@@ -15,6 +15,7 @@#include "quote.h" static int force =-1 ;/* unset */ +static struct string_list del_list =STRING_LIST_INIT_DUP ;static const char *const builtin_clean_usage[] = {N_ ("git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..." ),
@@ -148,12 +149,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix)int dry_run =0 , remove_directories =0 , quiet =0 , ignored =0 ;int ignored_only =0 , config_set =0 , errors =0 , gone =1 ;int rm_flags =REMOVE_DIR_KEEP_NESTED_GIT ; -struct strbuf directory =STRBUF_INIT ; +struct strbuf abs_path =STRBUF_INIT ;struct dir_struct dir;static const char **pathspec;struct strbuf buf =STRBUF_INIT ;struct string_list exclude_list =STRING_LIST_INIT_NODUP ;struct exclude_list *el; +struct string_list_item *item;const char *qname;char *seen =NULL ;struct option options[] = {
@@ -223,6 +225,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)int matches =0 ;struct cache_entry *ce;struct stat st; +const char *rel;/* * Remove the '/' at the end that directory
@@ -242,13 +245,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)continue ;/* Yup, this one exists unmerged */ } -/* -* we might have removed this as part of earlier -* recursive directory removal, so lstat() here could -* fail with ENOENT. -*/ if (lstat (ent->name, &st)) -continue ; +die_errno ("Cannot lstat '%s'" , ent->name);if (pathspec) {memset (seen,0 , argc >0 ? argc :1 );
@@ -257,33 +255,61 @@ int cmd_clean(int argc, const char **argv, const char *prefix) }if (S_ISDIR (st.st_mode)) { -strbuf_addstr (&directory, ent->name);if (remove_directories || (matches ==MATCHED_EXACTLY )) { -if (remove_dirs (&directory, prefix, rm_flags, dry_run, quiet, &gone)) - errors++; -if (gone && !quiet) { - qname =quote_path_relative (directory.buf, prefix, &buf); -printf (dry_run ?_ (msg_would_remove) :_ (msg_remove), qname); - } + rel =relative_path (ent->name, prefix, &buf); +string_list_append (&del_list, rel); } -strbuf_reset (&directory); }else {if (pathspec && !matches)continue ; - res = dry_run ?0 :unlink (ent->name); + rel =relative_path (ent->name, prefix, &buf); +string_list_append (&del_list, rel); + } + } + +/* TODO: do interactive git-clean here, which will modify del_list */ + +for_each_string_list_item (item, &del_list) { +struct stat st; + +if (prefix) +strbuf_addstr (&abs_path, prefix); + +strbuf_addstr (&abs_path, item->string); + +/* +* we might have removed this as part of earlier +* recursive directory removal, so lstat() here could +* fail with ENOENT. +*/ +if (lstat (abs_path.buf, &st)) +continue ; + +if (S_ISDIR (st.st_mode)) { +if (remove_dirs (&abs_path, prefix, rm_flags, dry_run, quiet, &gone)) + errors++; +if (gone && !quiet) { + qname =quote_path_relative (item->string,NULL , &buf); +printf (dry_run ?_ (msg_would_remove) :_ (msg_remove), qname); + } + }else { + res = dry_run ?0 :unlink (abs_path.buf);if (res) { - qname =quote_path_relative (ent->name, prefix, &buf); + qname =quote_path_relative (item->string,NULL , &buf);warning (_ (msg_warn_remove_failed), qname); errors++; }else if (!quiet) { - qname =quote_path_relative (ent->name, prefix, &buf); + qname =quote_path_relative (item->string,NULL , &buf);printf (dry_run ?_ (msg_would_remove) :_ (msg_remove), qname); } } +strbuf_reset (&abs_path); }free (seen); -strbuf_release (&directory); +strbuf_release (&abs_path); +strbuf_release (&buf); +string_list_clear (&del_list,0 );string_list_clear (&exclude_list,0 );return (errors !=0 ); }
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v13 7/15 git-clean: add support for -i/--interactive
In reply to Jiang XinShow what would be done and the user must confirm before actually cleaning.
Would remove ...
Would remove ...
Would remove ...
Remove [y/n]?
Press "y" to start cleaning, and press "n" if you want to abort.
--- Documentation/git-clean.txt | 10 ++++++-- builtin/clean.c | 57 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 60 insertions(+), 7 deletions(-)
Documentation/git-clean.txt +8 -2
@@ -8,7 +8,7 @@ git-clean - Remove untracked files from the working tree SYNOPSIS -------- [verse] -'git clean' [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>... +'git clean' [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>... DESCRIPTION -----------
@@ -34,7 +34,13 @@ OPTIONS -f:: --force:: If the Git configuration variable clean.requireForce is not set - to false, 'git clean' will refuse to run unless given -f or -n. + to false, 'git clean' will refuse to run unless given -f, -n or + -i. + +-i:: +--interactive:: + Show what would be done and the user must confirm before actually + cleaning. -n:: --dry-run::
builtin/clean.c +52 -5
93 lines not on this page · raw message
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v13 8/15 git-clean: show items of del_list in columns
In reply to Jiang XinWhen there are lots of items to be cleaned, it is hard to see them all in one screen. Show them in columns will solve this problem.
--- Documentation/config.txt | 4 ++++ builtin/clean.c | 49 +++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 9 deletions(-)
Documentation/config.txt +4 -0
@@ -955,6 +955,10 @@ column.branch:: Specify whether to output branch listing in `git branch` in columns. See `column.ui` for details. +column.clean:: + Specify the layout when list items in `git clean -i`, which always + shows files and directories in columns. See `column.ui` for details. + column.status:: Specify whether to output untracked files in `git status` in columns. See `column.ui` for details.
builtin/clean.c +40 -9
86 lines not on this page · raw message
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v13 9/15 git-clean: add colors to interactive git-clean
In reply to Jiang XinShow header, help, error messages, and prompt in colors for interactive git-clean. Re-use config variables, such as "color.interactive" and "color.interactive.<slot>" for command git-add--interactive.
--- Documentation/config.txt | 17 +++++------ builtin/clean.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 9 deletions(-)
Documentation/config.txt +9 -8
@@ -876,16 +876,17 @@ The values of these variables may be specified as in color.branch.<slot>. color.interactive:: When set to `always`, always use colors for interactive prompts - and displays (such as those used by "git-add --interactive"). - When false (or `never`), never. When set to `true` or `auto`, use - colors only when the output is to the terminal. Defaults to false. + and displays (such as those used by "git-add --interactive" and + "git-clean --interactive"). When false (or `never`), never. + When set to `true` or `auto`, use colors only when the output is + to the terminal. Defaults to false. color.interactive.<slot>:: - Use customized color for 'git add --interactive' - output. `<slot>` may be `prompt`, `header`, `help` or `error`, for - four distinct types of normal output from interactive - commands. The values of these variables may be specified as - in color.branch.<slot>. + Use customized color for 'git add --interactive' and 'git clean + --interactive' output. `<slot>` may be `prompt`, `header`, `help` + or `error`, for four distinct types of normal output from + interactive commands. The values of these variables may be + specified as in color.branch.<slot>. color.pager:: A boolean to enable/disable colored output when the pager is in
builtin/clean.c +72 -1
108 lines not on this page · raw message
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v13 10/15 git-clean: use a git-add-interactive compatible UI
In reply to Jiang XinRewrite menu using a new method list_and_choose, which is borrowed from git-add--interactive.perl. We will use this framework to add new actions for interactive git-clean later.
Please NOTE:
* Method list_and_choose return an array of integers, and
* it is up to you to free the allocated memory of the array.
* The array ends with EOF.
* If user pressed CTRL-D (i.e. EOF), no selection returned.
--- builtin/clean.c | 456 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 427 insertions(+), 29 deletions(-)
builtin/clean.c +427 -29
@@ -50,6 +50,36 @@ enum color_clean { CLEAN_COLOR_ERROR = 5, }; +#define MENU_OPTS_SINGLETON 01 +#define MENU_OPTS_IMMEDIATE 02 +#define MENU_OPTS_LIST_ONLY 04 + +struct menu_opts { +const char *header; +const char *prompt; +int flags; +}; + +#define MENU_RETURN_NO_LOOP 10 + +struct menu_item { +char hotkey; +const char *title; +int selected; +int (*fn)(); +}; + +enum menu_stuff_type { +MENU_STUFF_TYPE_STRING_LIST =1 , +MENU_STUFF_TYPE_MENU_ITEM +}; + +struct menu_stuff { +enum menu_stuff_type type; +int nr; +void *stuff; +}; +static int parse_clean_color_slot (const char *var) {if (!strcasecmp (var,"reset" ))
@@ -240,54 +270,422 @@ static void pretty_print_dels(void) copts.indent =" " ; copts.padding =2 ;print_columns (&list, colopts, &copts); -putchar ('\n' );strbuf_release (&buf);string_list_clear (&list,0 ); } -static void interactive_main_loop (void ) +static void pretty_print_menus (struct string_list *menu_list) +{ +unsigned int local_colopts =0 ; +struct column_options copts; + + local_colopts =COL_ENABLED |COL_ROW ; +memset (&copts,0 ,sizeof (copts)); + copts.indent =" " ; + copts.padding =2 ; +print_columns (menu_list, local_colopts, &copts); +} + +static void prompt_help_cmd (int singleton) +{ +clean_print_color (CLEAN_COLOR_HELP ); +printf_ln (singleton ? +_ ("Prompt help:\n" +"1 - select a numbered item\n" +"foo - select item based on unique prefix\n" +" - (empty) select nothing" ) : +_ ("Prompt help:\n" +"1 - select a single item\n" +"3-5 - select a range of items\n" +"2-3,6-9 - select multiple ranges\n" +"foo - select item based on unique prefix\n" +"-... - unselect specified items\n" +"* - choose all items\n" +" - (empty) finish selecting" )); +clean_print_color (CLEAN_COLOR_RESET ); +} + +/* +* display menu stuff with number prefix and hotkey highlight +*/ +static void print_highlight_menu_stuff (struct menu_stuff *stuff,int **chosen) +{ +struct string_list menu_list =STRING_LIST_INIT_DUP ; +struct strbuf menu =STRBUF_INIT ; +struct strbuf buf =STRBUF_INIT ; +struct menu_item *menu_item; +struct string_list_item *string_list_item; +int i; + +switch (stuff->type) { +default : +die ("Bad type of menu_staff when print menu" ); +case MENU_STUFF_TYPE_MENU_ITEM : + menu_item = (struct menu_item *)stuff->stuff; +for (i =0 ; i < stuff->nr; i++, menu_item++) { +const char *p; +int highlighted =0 ; + + p = menu_item->title; +if ((*chosen)[i] <0 ) + (*chosen)[i] = menu_item->selected ?1 :0 ; +strbuf_addf (&menu,"%s%2d: " , (*chosen)[i] ?"*" :" " , i+1 ); +for (; *p; p++) { +if (!highlighted && *p == menu_item->hotkey) { +strbuf_addstr (&menu,clean_get_color (CLEAN_COLOR_PROMPT )); +strbuf_addch (&menu, *p); +strbuf_addstr (&menu,clean_get_color (CLEAN_COLOR_RESET )); + highlighted =1 ; + }else { +strbuf_addch (&menu, *p); + } + } +string_list_append (&menu_list, menu.buf); +strbuf_reset (&menu); + } +break ; +case MENU_STUFF_TYPE_STRING_LIST : + i =0 ; +for_each_string_list_item (string_list_item, (struct string_list *)stuff->stuff) { +if ((*chosen)[i] <0 ) + (*chosen)[i] =0 ; +strbuf_addf (&menu,"%s%2d: %s" , + (*chosen)[i] ?"*" :" " , i+1 , string_list_item->string); +string_list_append (&menu_list, menu.buf); +strbuf_reset (&menu); + i++; + } +break ; + } + +pretty_print_menus (&menu_list); + +strbuf_release (&menu); +strbuf_release (&buf); +string_list_clear (&menu_list,0 ); +} + +/* +* Parse user input, and return choice(s) for menu (menu_stuff). +* +* Input +* (for single choice) +* 1 - select a numbered item +* foo - select item based on menu title +* - (empty) select nothing +* +* (for multiple choice) +* 1 - select a single item +* 3-5 - select a range of items +* 2-3,6-9 - select multiple ranges +* foo - select item based on menu title +* -... - unselect specified items +* * - choose all items +* - (empty) finish selecting +* +* The parse result will be saved in array **chosen, and +* return number of total selections. +*/ +static int parse_choice (struct menu_stuff *menu_stuff, +int is_single, +struct strbuf input, +int **chosen) +{ +struct strbuf **choice_list, **ptr; +struct menu_item *menu_item; +struct string_list_item *string_list_item; +int nr =0 ; +int i; + +if (is_single) { + choice_list =strbuf_split_max (&input,'\n' ,0 ); + }else { +char *p = input.buf; +do { +if (*p ==',' ) + *p =' ' ; + }while (*p++); + choice_list =strbuf_split_max (&input,' ' ,0 ); + } + +for (ptr = choice_list; *ptr; ptr++) { +char *p; +int choose =1 ; +int bottom =0 , top =0 ; +int is_range, is_number; + +strbuf_trim (*ptr); +if (!(*ptr)->len) +continue ; + +/* Input that begins with '-'; unchoose */ +if (*(*ptr)->buf =='-' ) { + choose =0 ; +strbuf_remove ((*ptr),0 ,1 ); + } + + is_range =0 ; + is_number =1 ; +for (p = (*ptr)->buf; *p; p++) { +if ('-' == *p) { +if (!is_range) { + is_range =1 ; + is_number =0 ; + }else { + is_number =0 ; + is_range =0 ; +break ; + } + }else if (!isdigit (*p)) { + is_number =0 ; + is_range =0 ; +break ; + } + } + +if (is_number) { + bottom =atoi ((*ptr)->buf); + top = bottom; + }else if (is_range) { + bottom =atoi ((*ptr)->buf); +/* a range can be specified like 5-7 or 5- */ +if (!*(strchr ((*ptr)->buf,'-' ) +1 )) + top = menu_stuff->nr; +else + top =atoi (strchr ((*ptr)->buf,'-' ) +1 ); + }else if (!strcmp ((*ptr)->buf,"*" )) { + bottom =1 ; + top = menu_stuff->nr; + }else { +switch (menu_stuff->type) { +default : +die ("Bad type of menu_stuff when parse choice" ); +case MENU_STUFF_TYPE_MENU_ITEM : + menu_item = (struct menu_item *)menu_stuff->stuff; +for (i =0 ; i < menu_stuff->nr; i++, menu_item++) { +if (((*ptr)->len ==1 && + *(*ptr)->buf == menu_item->hotkey) || + !strcasecmp ((*ptr)->buf, menu_item->title)) { + bottom = i +1 ; + top = bottom; +break ; + } + } +break ; +case MENU_STUFF_TYPE_STRING_LIST : + string_list_item = ((struct string_list *)menu_stuff->stuff)->items; +for (i =0 ; i < menu_stuff->nr; i++, string_list_item++) { +if (!strcasecmp ((*ptr)->buf, string_list_item->string)) { + bottom = i +1 ; + top = bottom; +break ; + } + } +break ; + } + } + +if (top <=0 || bottom <=0 || top > menu_stuff->nr || bottom > top || + (is_single && bottom != top)) { +clean_print_color (CLEAN_COLOR_ERROR ); +printf_ln (_ ("Huh (%s)?" ), (*ptr)->buf); +clean_print_color (CLEAN_COLOR_RESET ); +continue ; + } + +for (i = bottom; i <= top; i++) + (*chosen)[i-1 ] = choose; + } + +strbuf_list_free (choice_list); + +for (i =0 ; i < menu_stuff->nr; i++) + nr += (*chosen)[i]; +return nr; +} + +/* +* Implement a git-add-interactive compatible UI, which is borrowed +* from git-add--interactive.perl. +* +* Return value: +* +* - Return an array of integers +* - , and it is up to you to free the allocated memory. +* - The array ends with EOF. +* - If user pressed CTRL-D (i.e. EOF), no selection returned. +*/ +static int *list_and_choose (struct menu_opts *opts,struct menu_stuff *stuff) +{ +struct strbuf choice =STRBUF_INIT ; +int *chosen, *result; +int nr =0 ; +int eof =0 ; +int i; + + chosen =xmalloc (sizeof (int ) * stuff->nr); +/* set chosen as uninitialized */ +for (i =0 ; i < stuff->nr; i++) + chosen[i] =-1 ; + +for (;;) { +if (opts->header) { +printf_ln ("%s%s%s" , +clean_get_color (CLEAN_COLOR_HEADER ), +_ (opts->header), +clean_get_color (CLEAN_COLOR_RESET )); + } + +/* chosen will be initialized by print_highlight_menu_stuff */ +print_highlight_menu_stuff (stuff, &chosen); + +if (opts->flags &MENU_OPTS_LIST_ONLY ) +break ; + +if (opts->prompt) { +printf ("%s%s%s%s" , +clean_get_color (CLEAN_COLOR_PROMPT ), +_ (opts->prompt), + opts->flags &MENU_OPTS_SINGLETON ?"> " :">> " , +clean_get_color (CLEAN_COLOR_RESET )); + } + +if (strbuf_getline (&choice, stdin,'\n' ) !=EOF ) { +strbuf_trim (&choice); + }else { + eof =1 ; +break ; + } + +/* help for prompt */ +if (!strcmp (choice.buf,"?" )) { +prompt_help_cmd (opts->flags &MENU_OPTS_SINGLETON ); +continue ; + } + +/* for a multiple-choice menu, press ENTER (empty) will return back */ +if (!(opts->flags &MENU_OPTS_SINGLETON ) && !choice.len) +break ; + + nr =parse_choice (stuff, + opts->flags &MENU_OPTS_SINGLETON , + choice, + &chosen); + +if (opts->flags &MENU_OPTS_SINGLETON ) { +if (nr) +break ; + }else if (opts->flags &MENU_OPTS_IMMEDIATE ) { +break ; + } + } + +if (eof) { + result =xmalloc (sizeof (int )); + *result =EOF ; + }else { +int j =0 ; + +/* +* recalculate nr, if return back from menu directly with +* default selections. +*/ +if (!nr) { +for (i =0 ; i < stuff->nr; i++) + nr += chosen[i]; + } + + result =xmalloc (sizeof (int ) * (nr +1 )); +memset (result,0 ,sizeof (int ) * (nr +1 )); +for (i =0 ; i < stuff->nr && j < nr; i++) { +if (chosen[i]) + result[j++] = i; + } + result[j] =EOF ; + } + +free (chosen); +strbuf_release (&choice); +return result; +} + +static int clean_cmd (void ) { -struct strbuf confirm =STRBUF_INIT ; +return MENU_RETURN_NO_LOOP ; +} + +static int quit_cmd (void ) +{ +string_list_clear (&del_list,0 ); +printf_ln (_ ("Bye." )); +return MENU_RETURN_NO_LOOP ; +} + +static int help_cmd (void ) +{ +clean_print_color (CLEAN_COLOR_HELP ); +printf_ln (_ ( +"clean - start cleaning\n" +"quit - stop cleaning\n" +"help - this screen\n" +"? - help for prompt selection" + )); +clean_print_color (CLEAN_COLOR_RESET ); +return 0 ; +} +static void interactive_main_loop (void ) +{while (del_list.nr) { -putchar ('\n' ); +struct menu_opts menu_opts; +struct menu_stuff menu_stuff; +struct menu_item menus[] = { + {'c' ,"clean" ,0 , clean_cmd}, + {'q' ,"quit" ,0 , quit_cmd}, + {'h' ,"help" ,0 , help_cmd}, + }; +int *chosen; + + menu_opts.header =N_ ("*** Commands ***" ); + menu_opts.prompt =N_ ("What now" ); + menu_opts.flags =MENU_OPTS_SINGLETON ; + + menu_stuff.type =MENU_STUFF_TYPE_MENU_ITEM ; + menu_stuff.stuff = menus; + menu_stuff.nr =sizeof (menus) /sizeof (struct menu_item ); +clean_print_color (CLEAN_COLOR_HEADER );printf_ln (Q_ ("Would remove the following item:" ,"Would remove the following items:" , del_list.nr));clean_print_color (CLEAN_COLOR_RESET ); -putchar ('\n' );pretty_print_dels (); -clean_print_color (CLEAN_COLOR_PROMPT ); -printf (_ ("Remove [y/n]? " )); -clean_print_color (CLEAN_COLOR_RESET ); -if (strbuf_getline (&confirm, stdin,'\n' ) !=EOF ) { -strbuf_trim (&confirm); - }else { -/* Ctrl-D is the same as "quit" */ -string_list_clear (&del_list,0 ); -putchar ('\n' ); -printf_ln ("Bye." ); -break ; - } - -if (confirm.len) { -if (!strncasecmp (confirm.buf,"yes" , confirm.len)) { -break ; - }else if (!strncasecmp (confirm.buf,"no" , confirm.len) || - !strncasecmp (confirm.buf,"quit" , confirm.len)) { -string_list_clear (&del_list,0 ); -printf_ln ("Bye." ); -break ; - }else { + chosen =list_and_choose (&menu_opts, &menu_stuff); + +if (*chosen !=EOF ) { +int ret; + ret = menus[*chosen].fn (); +if (ret !=MENU_RETURN_NO_LOOP ) { +free (chosen); + chosen =NULL ; +if (!del_list.nr) { +clean_print_color (CLEAN_COLOR_ERROR ); +printf_ln (_ ("No more files to clean, exiting." )); +clean_print_color (CLEAN_COLOR_RESET ); +break ; + }continue ; } + }else { +quit_cmd (); } - } -strbuf_release (&confirm); +free (chosen); + chosen =NULL ; +break ; + } }int cmd_clean (int argc,const char **argv,const char *prefix)
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v13 11/15 git-clean: add filter by pattern interactive action
In reply to Jiang XinAdd a new action for interactive git-clean: filter by pattern. When the user chooses this action, user can input space-separated patterns (the same syntax as gitignore), and each clean candidate that matches with one of the patterns will be excluded from cleaning. When the user feels it's OK, presses ENTER and backs to the confirmation dialog.
--- builtin/clean.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+)
builtin/clean.c +68 -0
@@ -614,6 +614,72 @@ static int clean_cmd(void)return MENU_RETURN_NO_LOOP ; } +static int filter_by_patterns_cmd (void ) +{ +struct dir_struct dir; +struct strbuf confirm =STRBUF_INIT ; +struct strbuf **ignore_list; +struct string_list_item *item; +struct exclude_list *el; +int changed =-1 , i; + +for (;;) { +if (!del_list.nr) +break ; + +if (changed) +pretty_print_dels (); + +clean_print_color (CLEAN_COLOR_PROMPT ); +printf (_ ("Input ignore patterns>> " )); +clean_print_color (CLEAN_COLOR_RESET ); +if (strbuf_getline (&confirm, stdin,'\n' ) !=EOF ) +strbuf_trim (&confirm); +else +putchar ('\n' ); + +/* quit filter_by_pattern mode if press ENTER or Ctrl-D */ +if (!confirm.len) +break ; + +memset (&dir,0 ,sizeof (dir)); + el =add_exclude_list (&dir,EXC_CMDL ,"manual exclude" ); + ignore_list =strbuf_split_max (&confirm,' ' ,0 ); + +for (i =0 ; ignore_list[i]; i++) { +strbuf_trim (ignore_list[i]); +if (!ignore_list[i]->len) +continue ; + +add_exclude (ignore_list[i]->buf,"" ,0 , el, -(i+1 )); + } + + changed =0 ; +for_each_string_list_item (item, &del_list) { +int dtype =DT_UNKNOWN ; + +if (is_excluded (&dir, item->string, &dtype)) { + *item->string ='\0' ; + changed++; + } + } + +if (changed) { +string_list_remove_empty_items (&del_list,0 ); + }else { +clean_print_color (CLEAN_COLOR_ERROR ); +printf_ln (_ ("WARNING: Cannot find items matched by: %s" ), confirm.buf); +clean_print_color (CLEAN_COLOR_RESET ); + } + +strbuf_list_free (ignore_list); +clear_directory (&dir); + } + +strbuf_release (&confirm); +return 0 ; +} +static int quit_cmd (void ) {string_list_clear (&del_list,0 );
@@ -626,6 +692,7 @@ static int help_cmd(void)clean_print_color (CLEAN_COLOR_HELP ); printf_ln(_( "clean - start cleaning\n" + "filter by pattern - exclude items from deletion\n" "quit - stop cleaning\n" "help - this screen\n" "? - help for prompt selection"
@@ -641,6 +708,7 @@ static void interactive_main_loop(void)struct menu_stuff menu_stuff;struct menu_item menus[] = { {'c' ,"clean" ,0 , clean_cmd}, + {'f' ,"filter by pattern" ,0 , filter_by_patterns_cmd}, {'q' ,"quit" ,0 , quit_cmd}, {'h' ,"help" ,0 , help_cmd}, };
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v13 12/15 git-clean: add select by numbers interactive action
In reply to Jiang XinDraw a multiple choice menu using list_and_choose to select items to be deleted by numbers.
User can input:
* 1,5-7 : select 1,5,6,7 items to be deleted * * : select all items to be deleted * -* : unselect all, nothing will be deleted
* : (empty) finish selecting, and return back to main menu
--- builtin/clean.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+)
builtin/clean.c +39 -0
@@ -680,6 +680,43 @@ static int filter_by_patterns_cmd(void)return 0 ; } +static int select_by_numbers_cmd (void ) +{ +struct menu_opts menu_opts; +struct menu_stuff menu_stuff; +struct string_list_item *items; +int *chosen; +int i, j; + + menu_opts.header =NULL ; + menu_opts.prompt =N_ ("Select items to delete" ); + menu_opts.flags =0 ; + + menu_stuff.type =MENU_STUFF_TYPE_STRING_LIST ; + menu_stuff.stuff = &del_list; + menu_stuff.nr = del_list.nr; + + chosen =list_and_choose (&menu_opts, &menu_stuff); + items = del_list.items; +for (i =0 , j =0 ; i < del_list.nr; i++) { +if (i < chosen[j]) { + *(items[i].string) ='\0' ; + }else if (i == chosen[j]) { +/* delete selected item */ + j++; +continue ; + }else { +/* end of chosen (chosen[j] == EOF), won't delete */ + *(items[i].string) ='\0' ; + } + } + +string_list_remove_empty_items (&del_list,0 ); + +free (chosen); +return 0 ; +} +static int quit_cmd (void ) {string_list_clear (&del_list,0 );
@@ -693,6 +730,7 @@ static int help_cmd(void) printf_ln(_( "clean - start cleaning\n" "filter by pattern - exclude items from deletion\n" + "select by numbers - select items to be deleted by numbers\n" "quit - stop cleaning\n" "help - this screen\n" "? - help for prompt selection"
@@ -709,6 +747,7 @@ static void interactive_main_loop(void)struct menu_item menus[] = { {'c' ,"clean" ,0 , clean_cmd}, {'f' ,"filter by pattern" ,0 , filter_by_patterns_cmd}, + {'s' ,"select by numbers" ,0 , select_by_numbers_cmd}, {'q' ,"quit" ,0 , quit_cmd}, {'h' ,"help" ,0 , help_cmd}, };
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v13 13/15 git-clean: add ask each interactive action
In reply to Jiang XinAdd a new action for interactive git-clean: ask each. It's just like the "rm -i" command, that the user must confirm one by one for each file or directory to be cleaned.
--- builtin/clean.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+)
builtin/clean.c +36 -0
@@ -717,6 +717,40 @@ static int select_by_numbers_cmd(void)return 0 ; } +static int ask_each_cmd (void ) +{ +struct strbuf confirm =STRBUF_INIT ; +struct strbuf buf =STRBUF_INIT ; +struct string_list_item *item; +const char *qname; +int changed =0 , eof =0 ; + +for_each_string_list_item (item, &del_list) { +/* Ctrl-D should stop removing files */ +if (!eof) { + qname =quote_path_relative (item->string,NULL , &buf); +printf (_ ("remove %s? " ), qname); +if (strbuf_getline (&confirm, stdin,'\n' ) !=EOF ) { +strbuf_trim (&confirm); + }else { +putchar ('\n' ); + eof =1 ; + } + } +if (!confirm.len ||strncasecmp (confirm.buf,"yes" , confirm.len)) { + *item->string ='\0' ; + changed++; + } + } + +if (changed) +string_list_remove_empty_items (&del_list,0 ); + +strbuf_release (&buf); +strbuf_release (&confirm); +return MENU_RETURN_NO_LOOP ; +} +static int quit_cmd (void ) {string_list_clear (&del_list,0 );
@@ -731,6 +765,7 @@ static int help_cmd(void)"clean - start cleaning\n" "filter by pattern - exclude items from deletion\n" "select by numbers - select items to be deleted by numbers\n" +"ask each - confirm each deletion (like \"rm -i\")\n" "quit - stop cleaning\n" "help - this screen\n" "? - help for prompt selection"
@@ -748,6 +783,7 @@ static void interactive_main_loop(void) {'c', "clean", 0, clean_cmd}, {'f', "filter by pattern", 0, filter_by_patterns_cmd}, {'s', "select by numbers", 0, select_by_numbers_cmd}, + {'a', "ask each", 0, ask_each_cmd}, {'q', "quit", 0, quit_cmd}, {'h', "help", 0, help_cmd}, };
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v13 14/15 git-clean: add documentation for interactive git-clean
In reply to Jiang XinAdd new section "Interactive mode" for documentation of interactive git-clean.
--- Documentation/git-clean.txt | 65 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-)
Documentation/git-clean.txt +63 -2
@@ -39,8 +39,8 @@ OPTIONS -i:: --interactive:: - Show what would be done and the user must confirm before actually - cleaning. + Show what would be done and clean files interactively. See + ``Interactive mode'' for details. -n:: --dry-run::
@@ -69,6 +69,67 @@ OPTIONS Remove only files ignored by Git. This may be useful to rebuild everything from scratch, but keep manually created files. +Interactive mode +---------------- +When the command enters the interactive mode, it shows the +files and directories to be cleaned, and goes into its +interactive command loop. + +The command loop shows the list of subcommands available, and +gives a prompt "What now> ". In general, when the prompt ends +with a single '>', you can pick only one of the choices given +and type return, like this: + +------------ + *** Commands *** + 1: clean 2: filter by pattern 3: select by numbers + 4: ask each 5: quit 6: help + What now> 1 +------------ + +You also could say `c` or `clean` above as long as the choice is unique. + +The main command loop has 6 subcommands. + +clean:: + + Start cleaning files and directories, and then quit. + +filter by pattern:: + + This shows the files and directories to be deleted and issues an + "Input ignore patterns>>" prompt. You can input space-seperated + patterns to exclude files and directories from deletion. + E.g. "*.c *.h" will excludes files end with ".c" and ".h" from + deletion. When you are satisfied with the filtered result, press + ENTER (empty) back to the main menu. + +select by numbers:: + + This shows the files and directories to be deleted and issues an + "Select items to delete>>" prompt. When the prompt ends with double + '>>' like this, you can make more than one selection, concatenated + with whitespace or comma. Also you can say ranges. E.g. "2-5 7,9" + to choose 2,3,4,5,7,9 from the list. If the second number in a + range is omitted, all remaining patches are taken. E.g. "7-" to + choose 7,8,9 from the list. You can say '*' to choose everything. + Also when you are satisfied with the filtered result, press ENTER + (empty) back to the main menu. + +ask each:: + + This will start to clean, and you must confirm one by one in order + to delete items. Please note that this action is not as efficient + as the above two actions. + +quit:: + + This lets you quit without do cleaning. + +help:: + + Show brief usage of interactive git-clean. + SEE ALSO -------- linkgit:gitignore[5]
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v13 15/15 test: add t7301 for git-clean--interactive
In reply to Jiang XinAdd test cases for git-clean--interactive.
--- t/t7301-clean-interactive.sh | 439 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 439 insertions(+) create mode 100755 t/t7301-clean-interactive.sh
t/t7301-clean-interactive.sh new file +439 -0
@@ -0,0 +1,439 @@ +#!/bin/sh + +test_description='git clean -i basic tests' + +. ./test-lib.sh + +test_expect_success 'setup' ' + + mkdir -p src && + touch src/part1.c Makefile && + echo build >.gitignore && + echo \*.o >>.gitignore && + git add . && + git commit -m setup && + touch src/part2.c README && + git add . + +' + +test_expect_success 'git clean -i (clean)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + echo c | git clean -i && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -i (quit)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + echo q | git clean -i && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -i (Ctrl+D)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + echo "\04" | git clean -i && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (filter all)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo f; echo "*"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (filter patterns)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo f; echo "part3.* *.out"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (filter patterns 2)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo f; echo "* !*.out"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - all)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo "*"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - none)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - number)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 3; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - number 2)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 2 3; echo 5; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - number 3)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 3,4 5; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - range)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 1,3-4; echo 2; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test ! -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - range 2)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 4- 1; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (inverse select)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo "*"; echo -5- 1 -2; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (ask)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f docs/manual.txt && + test -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (ask - Ctrl+D)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo a; echo Y; echo no; echo yes; echo "\04") | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id with prefix and path (filter)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (cd build/ && \ + (echo f; echo "docs"; echo "*.h"; echo ; echo c) | \ + git clean -id ..) && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id with prefix and path (select by name)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (cd build/ && \ + (echo s; echo "../docs/"; echo "../src/part3.c"; \ + echo "../src/part4.c"; echo; echo c) | \ + git clean -id ..) && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id with prefix and path (ask)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (cd build/ && \ + (echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \ + git clean -id ..) && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f docs/manual.txt && + test -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_done
Signature · 1 line
1.8.3.rc3.368.g7c798dd
PATCH v12 2/15 path.c: refactor relative_path(), not only strip prefix
In reply to Jiang XinOriginal design of relative_path() is simple, just strip the prefix (*base) from the absolute path (*abs). In most cases, we need a real relative path, such as: ../foo, ../../bar. That's why there is another reimplementation (path_relative()) in quote.c.
Refactor relative_path() in path.c to return real relative path, so that user can reuse this function without reimplement his/her own. I will use this method for interactive git-clean later. Some of the implementations are borrowed from path_relative() in quote.c.
Different results for relative_path() before and after this refactor:
abs path base path relative (original) relative (refactor)
======== ========= =================== ===================
/a/b/c/ /a/b c/ c/
/a/b//c/ //a///b/ c/ c/
/a/b /a/b . ./
/a/b/ /a/b . ./
/a /a/b/ /a ../
/ /a/b/ / ../../
/a/c /a/b/ /a/c ../c
/a/b (empty) /a/b /a/b
/a/b (null) /a/b /a/b
(empty) /a/b (empty) ./
(null) (empty) (null) ./
(null) /a/b (segfault) ./--- cache.h | 2 +- path.c | 112 +++++++++++++++++++++++++++++++++++++++----------- setup.c | 5 ++- t/t0060-path-utils.sh | 27 ++++++------ test-path-utils.c | 4 +- 5 files changed, 107 insertions(+), 43 deletions(-)
cache.h +1 -1
@@ -737,7 +737,7 @@ int is_directory(const char *);const char *real_path (const char *path);const char *real_path_if_valid (const char *path);const char *absolute_path (const char *path); -const char *relative_path (const char *abs,const char *base); +const char *relative_path (const char *abs,const char *base,struct strbuf *sb);int normalize_path_copy (char *dst,const char *src);int longest_ancestor_length (const char *path,struct string_list *prefixes);char *strip_path_suffix (const char *path,const char *suffix);
path.c +87 -25
129 lines not on this page · raw message
setup.c +4 -1
@@ -360,6 +360,7 @@ int is_inside_work_tree(void)void setup_work_tree (void ) { +struct strbuf sb =STRBUF_INIT ;const char *work_tree, *git_dir;static int initialized =0 ;
@@ -379,8 +380,10 @@ void setup_work_tree(void)if (getenv (GIT_WORK_TREE_ENVIRONMENT ))setenv (GIT_WORK_TREE_ENVIRONMENT ,"." ,1 ); -set_git_dir (relative_path (git_dir, work_tree)); +set_git_dir (relative_path (git_dir, work_tree, &sb)); initialized =1 ; + +strbuf_release (&sb); }static int check_repository_format_gently (const char *gitdir,int *nongit_ok)
t/t0060-path-utils.sh +12 -15
@@ -191,22 +191,19 @@ test_expect_success SYMLINKS 'real path works on symlinks' ' relative_path /a/b/c/ /a/b/ c/ relative_path /a/b/c/ /a/b c/ relative_path /a//b//c/ //a/b// c/ -relative_path /a/b /a/b . -relative_path /a/b/ /a/b . -relative_path /a /a/b /a -relative_path / /a/b/ / -relative_path /a/c /a/b/ /a/c -relative_path /a/c /a/b /a/c +relative_path /a/b /a/b ./ +relative_path /a/b/ /a/b ./ +relative_path /a /a/b ../ +relative_path / /a/b/ ../../ +relative_path /a/c /a/b/ ../c +relative_path /a/c /a/b ../c relative_path /a/b "<empty>" /a/b relative_path /a/b "<null>" /a/b -relative_path "<empty>" /a/b "(empty)" -relative_path "<empty>" "<empty>" "(empty)" -relative_path "<empty>" "<null>" "(empty)" -relative_path "<null>" "<empty>" "(null)" -relative_path "<null>" "<null>" "(null)" - -test_expect_failure 'relative path: <null> /a/b => segfault' ' - test-path-utils relative_path "<null>" "/a/b" -' +relative_path "<empty>" /a/b ./ +relative_path "<empty>" "<empty>" ./ +relative_path "<empty>" "<null>" ./ +relative_path "<null>" "<empty>" ./ +relative_path "<null>" "<null>" ./ +relative_path "<null>" /a/b ./ test_done
test-path-utils.c +3 -1
@@ -117,14 +117,16 @@ int main(int argc, char **argv) }if (argc ==4 && !strcmp (argv[1 ],"relative_path" )) { +struct strbuf sb =STRBUF_INIT ;const char *abs, *base, *rel;PARSE_ARGV_STRING (abs, argv[2 ]);PARSE_ARGV_STRING (base, argv[3 ]); - rel =relative_path (abs, base); + rel =relative_path (abs, base, &sb);if (!rel)puts ("(null)" );else puts (strlen (rel) >0 ? rel :"(empty)" ); +strbuf_release (&sb);return 0 ; }
Signature · 1 line
1.8.3.rc2.26.g7472058
PATCH v12 3/15 quote.c: remove path_relative, use relative_path instead
In reply to Jiang XinSince there is an enhanced version of relative_path() in path.c, remove duplicate counterpart path_relative() in quote.c.
--- quote.c | 55 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 53 deletions(-)
quote.c +2 -53
@@ -312,75 +312,24 @@ void write_name_quotedpfx(const char *pfx, size_t pfxlen,fputc (terminator, fp); } -static const char *path_relative (const char *in,int len, -struct strbuf *sb,const char *prefix, -int prefix_len); -void write_name_quoted_relative (const char *name,size_t len,const char *prefix,size_t prefix_len,FILE *fp,int terminator) {struct strbuf sb =STRBUF_INIT ; - name =path_relative (name, len, &sb, prefix, prefix_len); + name =relative_path (name, prefix, &sb);write_name_quoted (name, fp, terminator);strbuf_release (&sb); } -/* -* Give path as relative to prefix. -* -* The strbuf may or may not be used, so do not assume it contains the -* returned path. -*/ -static const char *path_relative (const char *in,int len, -struct strbuf *sb,const char *prefix, -int prefix_len) -{ -int off, i; - -if (len <0 ) - len =strlen (in); -if (prefix_len <0 ) { -if (prefix) - prefix_len =strlen (prefix); -else - prefix_len =0 ; - } - - off =0 ; - i =0 ; -while (i < prefix_len && i < len && prefix[i] == in[i]) { -if (prefix[i] =='/' ) - off = i +1 ; - i++; - } - in += off; - len -= off; - -if (i >= prefix_len) -return in; - -strbuf_reset (sb); -strbuf_grow (sb, len); - -while (i < prefix_len) { -if (prefix[i] =='/' ) -strbuf_addstr (sb,"../" ); - i++; - } -strbuf_add (sb, in, len); - -return sb->buf; -} -/* quote path as relative to the given prefix */ char *quote_path_relative (const char *in,int len,struct strbuf *out,const char *prefix) {struct strbuf sb =STRBUF_INIT ; -const char *rel =path_relative (in, len, &sb, prefix,-1 ); +const char *rel =relative_path (in, prefix, &sb);strbuf_reset (out);quote_c_style_counted (rel,strlen (rel), out,NULL ,0 );strbuf_release (&sb);
Signature · 1 line
1.8.3.rc2.26.g7472058
PATCH v12 4/15 Refactor quote_path_relative, remove unused params
In reply to Jiang XinAfter substitute path_relative() in quote.c with relative_path() from path.c, parameters (such as len and prefix_len) are obsolete in function quote_path_relative(). Remove unused parameters and change the order of parameters for quote_path_relative() function.
--- builtin/clean.c | 18 +++++++++--------- builtin/grep.c | 5 ++--- builtin/ls-files.c | 2 +- quote.c | 7 ++----- quote.h | 4 ++-- wt-status.c | 17 ++++++++--------- 6 files changed, 24 insertions(+), 29 deletions(-)
builtin/clean.c +9 -9
@@ -56,7 +56,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,if ((force_flag &REMOVE_DIR_KEEP_NESTED_GIT ) && !resolve_gitlink_ref (path->buf,"HEAD" , submodule_head)) {if (!quiet) { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);printf (dry_run ?_ (msg_would_skip_git_dir) :_ (msg_skip_git_dir), quoted.buf); }
@@ -70,7 +70,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,/* an empty dir could be removed even if it is unreadble */ res = dry_run ?0 :rmdir (path->buf);if (res) { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);warning (_ (msg_warn_remove_failed), quoted.buf); *dir_gone =0 ; }
@@ -94,7 +94,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,if (remove_dirs (path, prefix, force_flag, dry_run, quiet, &gone)) ret =1 ;if (gone) { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);string_list_append (&dels, quoted.buf); }else *dir_gone =0 ;
@@ -102,10 +102,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, }else { res = dry_run ?0 :unlink (path->buf);if (!res) { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);string_list_append (&dels, quoted.buf); }else { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);warning (_ (msg_warn_remove_failed), quoted.buf); *dir_gone =0 ; ret =1 ;
@@ -127,7 +127,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,if (!res) *dir_gone =1 ;else { -quote_path_relative (path->buf,strlen (path->buf), "ed, prefix); +quote_path_relative (path->buf, prefix, "ed);warning (_ (msg_warn_remove_failed), quoted.buf); *dir_gone =0 ; ret =1 ;
@@ -262,7 +262,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)if (remove_dirs (&directory, prefix, rm_flags, dry_run, quiet, &gone)) errors++;if (gone && !quiet) { - qname =quote_path_relative (directory.buf, directory.len, &buf, prefix); + qname =quote_path_relative (directory.buf, prefix, &buf);printf (dry_run ?_ (msg_would_remove) :_ (msg_remove), qname); } }
@@ -272,11 +272,11 @@ int cmd_clean(int argc, const char **argv, const char *prefix)continue ; res = dry_run ?0 :unlink (ent->name);if (res) { - qname =quote_path_relative (ent->name,-1 , &buf, prefix); + qname =quote_path_relative (ent->name, prefix, &buf);warning (_ (msg_warn_remove_failed), qname); errors++; }else if (!quiet) { - qname =quote_path_relative (ent->name,-1 , &buf, prefix); + qname =quote_path_relative (ent->name, prefix, &buf);printf (dry_run ?_ (msg_would_remove) :_ (msg_remove), qname); } }
builtin/grep.c +2 -3
@@ -286,8 +286,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,struct strbuf pathbuf =STRBUF_INIT ;if (opt->relative && opt->prefix_length) { -quote_path_relative (filename + tree_name_len,-1 , &pathbuf, - opt->prefix); +quote_path_relative (filename + tree_name_len, opt->prefix, &pathbuf);strbuf_insert (&pathbuf,0 , filename, tree_name_len); }else {strbuf_addstr (&pathbuf, filename);
@@ -318,7 +317,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)struct strbuf buf =STRBUF_INIT ;if (opt->relative && opt->prefix_length) -quote_path_relative (filename,-1 , &buf, opt->prefix); +quote_path_relative (filename, opt->prefix, &buf);else strbuf_addstr (&buf, filename);
builtin/ls-files.c +1 -1
@@ -389,7 +389,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, const charif (found_dup)continue ; - name =quote_path_relative (pathspec[num],-1 , &sb, prefix); + name =quote_path_relative (pathspec[num], prefix, &sb);error ("pathspec '%s' did not match any file(s) known to git." , name); errors++;
quote.c +2 -5
@@ -325,8 +325,8 @@ void write_name_quoted_relative(const char *name, size_t len, }/* quote path as relative to the given prefix */ -char *quote_path_relative (const char *in,int len, -struct strbuf *out,const char *prefix) +char *quote_path_relative (const char *in,const char *prefix, +struct strbuf *out) {struct strbuf sb =STRBUF_INIT ;const char *rel =relative_path (in, prefix, &sb);
@@ -334,9 +334,6 @@ char *quote_path_relative(const char *in, int len,quote_c_style_counted (rel,strlen (rel), out,NULL ,0 );strbuf_release (&sb); -if (!out->len) -strbuf_addstr (out,"./" ); -return out->buf; }
quote.h +2 -2
@@ -65,8 +65,8 @@ extern void write_name_quoted_relative(const char *name, size_t len, FILE *fp, int terminator);/* quote path as relative to the given prefix */ -extern char *quote_path_relative (const char *in,int len, -struct strbuf *out,const char *prefix); +extern char *quote_path_relative (const char *in,const char *prefix, +struct strbuf *out);/* quoting as a string literal for other languages */ extern void perl_quote_print (FILE *stream,const char *src);
wt-status.c +8 -9
@@ -243,7 +243,7 @@ static void wt_status_print_unmerged_data(struct wt_status *s,struct strbuf onebuf =STRBUF_INIT ;const char *one, *how =_ ("bug" ); - one =quote_path (it->string,-1 , &onebuf, s->prefix); + one =quote_path (it->string, s->prefix, &onebuf);status_printf (s,color (WT_STATUS_HEADER , s),"\t" );switch (d->stagemask) {case 1 : how =_ ("both deleted:" );break ;
@@ -297,8 +297,8 @@ static void wt_status_print_change_data(struct wt_status *s, change_type); } - one =quote_path (one_name,-1 , &onebuf, s->prefix); - two =quote_path (two_name,-1 , &twobuf, s->prefix); + one =quote_path (one_name, s->prefix, &onebuf); + two =quote_path (two_name, s->prefix, &twobuf);status_printf (s,color (WT_STATUS_HEADER , s),"\t" );switch (status) {
@@ -706,8 +706,7 @@ static void wt_status_print_other(struct wt_status *s,struct string_list_item *it;const char *path; it = &(l->items[i]); - path =quote_path (it->string,strlen (it->string), - &buf, s->prefix); + path =quote_path (it->string, s->prefix, &buf);if (column_active (s->colopts)) {string_list_append (&output, path);continue ;
@@ -1289,7 +1288,7 @@ static void wt_shortstatus_unmerged(struct string_list_item *it, }else {struct strbuf onebuf =STRBUF_INIT ;const char *one; - one =quote_path (it->string,-1 , &onebuf, s->prefix); + one =quote_path (it->string, s->prefix, &onebuf);printf (" %s\n" , one);strbuf_release (&onebuf); }
@@ -1317,7 +1316,7 @@ static void wt_shortstatus_status(struct string_list_item *it,struct strbuf onebuf =STRBUF_INIT ;const char *one;if (d->head_path) { - one =quote_path (d->head_path,-1 , &onebuf, s->prefix); + one =quote_path (d->head_path, s->prefix, &onebuf);if (*one !='"' &&strchr (one,' ' ) !=NULL ) {putchar ('"' );strbuf_addch (&onebuf,'"' );
@@ -1326,7 +1325,7 @@ static void wt_shortstatus_status(struct string_list_item *it,printf ("%s -> " , one);strbuf_release (&onebuf); } - one =quote_path (it->string,-1 , &onebuf, s->prefix); + one =quote_path (it->string, s->prefix, &onebuf);if (*one !='"' &&strchr (one,' ' ) !=NULL ) {putchar ('"' );strbuf_addch (&onebuf,'"' );
@@ -1345,7 +1344,7 @@ static void wt_shortstatus_other(struct string_list_item *it, }else {struct strbuf onebuf =STRBUF_INIT ;const char *one; - one =quote_path (it->string,-1 , &onebuf, s->prefix); + one =quote_path (it->string, s->prefix, &onebuf);color_fprintf (s->fp,color (WT_STATUS_UNTRACKED , s),"%s" , sign);printf (" %s\n" , one);strbuf_release (&onebuf);
Signature · 1 line
1.8.3.rc2.26.g7472058
PATCH v12 5/15 Refactor write_name_quoted_relative, remove unused params
In reply to Jiang XinAfter substitute path_relative() in quote.c with relative_path() from path.c, parameters (such as len and prefix_len) are obsolete in function write_name_quoted_relative(). Remove unused parameters from write_name_quoted_relative() and related functions.
--- builtin/ls-files.c | 14 ++++++++------ quote.c | 3 +-- quote.h | 3 +-- 3 files changed, 10 insertions(+), 10 deletions(-)
builtin/ls-files.c +8 -6
@@ -46,10 +46,12 @@ static const char *tag_modified = "";static const char *tag_skip_worktree ="" ;static const char *tag_resolve_undo ="" ; -static void write_name (const char * name,size_t len) +static void write_name (const char *name) { -write_name_quoted_relative (name, len, prefix, prefix_len, stdout, - line_terminator); + +/* turn off prefix, if run with "--full-name" */ +write_name_quoted_relative (name, prefix_len ? prefix :NULL , + stdout, line_terminator); }static void show_dir_entry (const char *tag,struct dir_entry *ent)
@@ -63,7 +65,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)return ;fputs (tag, stdout); -write_name (ent->name, ent->len); +write_name (ent->name); }static void show_other_files (struct dir_struct *dir)
@@ -163,7 +165,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)find_unique_abbrev (ce->sha1,abbrev), ce_stage(ce)); } -write_name (ce->name,ce_namelen (ce)); +write_name (ce->name);if (debug_mode) {printf (" ctime: %d:%d\n" , ce->ce_ctime.sec, ce->ce_ctime.nsec);printf (" mtime: %d:%d\n" , ce->ce_mtime.sec, ce->ce_mtime.nsec);
@@ -196,7 +198,7 @@ static void show_ru_info(void)printf ("%s%06o %s %d\t" , tag_resolve_undo, ui->mode[i],find_unique_abbrev (ui->sha1[i], abbrev), i +1 ); -write_name (path, len); +write_name (path); } } }
quote.c +1 -2
@@ -312,8 +312,7 @@ void write_name_quotedpfx(const char *pfx, size_t pfxlen,fputc (terminator, fp); } -void write_name_quoted_relative (const char *name,size_t len, -const char *prefix,size_t prefix_len, +void write_name_quoted_relative (const char *name,const char *prefix,FILE *fp,int terminator) {struct strbuf sb =STRBUF_INIT ;
quote.h +1 -2
@@ -60,8 +60,7 @@ extern void quote_two_c_style(struct strbuf *, const char *, const char *, int);extern void write_name_quoted (const char *name,FILE *,int terminator);extern void write_name_quotedpfx (const char *pfx,size_t pfxlen,const char *name,FILE *,int terminator); -extern void write_name_quoted_relative (const char *name,size_t len, -const char *prefix,size_t prefix_len, +extern void write_name_quoted_relative (const char *name,const char *prefix,FILE *fp,int terminator);/* quote path as relative to the given prefix */
Signature · 1 line
1.8.3.rc2.26.g7472058
PATCH v12 6/15 git-clean: refactor git-clean into two phases
In reply to Jiang XinBefore introducing interactive git-clean, refactor git-clean operations into two phases:
* hold cleaning items in del_list,
* and remove them in a separate loop at the end.
We will introduce interactive git-clean between the two phases. The interactive git-clean will show what would be done and must confirm before do real cleaning.
--- builtin/clean.c | 64 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 19 deletions(-)
builtin/clean.c +45 -19
@@ -15,6 +15,7 @@#include "quote.h" static int force =-1 ;/* unset */ +static struct string_list del_list =STRING_LIST_INIT_DUP ;static const char *const builtin_clean_usage[] = {N_ ("git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..." ),
@@ -148,12 +149,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix)int dry_run =0 , remove_directories =0 , quiet =0 , ignored =0 ;int ignored_only =0 , config_set =0 , errors =0 , gone =1 ;int rm_flags =REMOVE_DIR_KEEP_NESTED_GIT ; -struct strbuf directory =STRBUF_INIT ; +struct strbuf abs_path =STRBUF_INIT ;struct dir_struct dir;static const char **pathspec;struct strbuf buf =STRBUF_INIT ;struct string_list exclude_list =STRING_LIST_INIT_NODUP ;struct exclude_list *el; +struct string_list_item *item;const char *qname;char *seen =NULL ;struct option options[] = {
@@ -223,6 +225,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)int matches =0 ;struct cache_entry *ce;struct stat st; +const char *rel;/* * Remove the '/' at the end that directory
@@ -242,13 +245,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)continue ;/* Yup, this one exists unmerged */ } -/* -* we might have removed this as part of earlier -* recursive directory removal, so lstat() here could -* fail with ENOENT. -*/ if (lstat (ent->name, &st)) -continue ; +die_errno ("Cannot lstat '%s'" , ent->name);if (pathspec) {memset (seen,0 , argc >0 ? argc :1 );
@@ -257,33 +255,61 @@ int cmd_clean(int argc, const char **argv, const char *prefix) }if (S_ISDIR (st.st_mode)) { -strbuf_addstr (&directory, ent->name);if (remove_directories || (matches ==MATCHED_EXACTLY )) { -if (remove_dirs (&directory, prefix, rm_flags, dry_run, quiet, &gone)) - errors++; -if (gone && !quiet) { - qname =quote_path_relative (directory.buf, prefix, &buf); -printf (dry_run ?_ (msg_would_remove) :_ (msg_remove), qname); - } + rel =relative_path (ent->name, prefix, &buf); +string_list_append (&del_list, rel); } -strbuf_reset (&directory); }else {if (pathspec && !matches)continue ; - res = dry_run ?0 :unlink (ent->name); + rel =relative_path (ent->name, prefix, &buf); +string_list_append (&del_list, rel); + } + } + +/* TODO: do interactive git-clean here, which will modify del_list */ + +for_each_string_list_item (item, &del_list) { +struct stat st; + +if (prefix) { +strbuf_addstr (&abs_path, prefix); + } +strbuf_addstr (&abs_path, item->string); + +/* +* we might have removed this as part of earlier +* recursive directory removal, so lstat() here could +* fail with ENOENT. +*/ +if (lstat (abs_path.buf, &st)) +continue ; + +if (S_ISDIR (st.st_mode)) { +if (remove_dirs (&abs_path, prefix, rm_flags, dry_run, quiet, &gone)) + errors++; +if (gone && !quiet) { + qname =quote_path_relative (item->string,NULL , &buf); +printf (dry_run ?_ (msg_would_remove) :_ (msg_remove), qname); + } + }else { + res = dry_run ?0 :unlink (abs_path.buf);if (res) { - qname =quote_path_relative (ent->name, prefix, &buf); + qname =quote_path_relative (item->string,NULL , &buf);warning (_ (msg_warn_remove_failed), qname); errors++; }else if (!quiet) { - qname =quote_path_relative (ent->name, prefix, &buf); + qname =quote_path_relative (item->string,NULL , &buf);printf (dry_run ?_ (msg_would_remove) :_ (msg_remove), qname); } } +strbuf_reset (&abs_path); }free (seen); -strbuf_release (&directory); +strbuf_release (&abs_path); +strbuf_release (&buf); +string_list_clear (&del_list,0 );string_list_clear (&exclude_list,0 );return (errors !=0 ); }
Signature · 1 line
1.8.3.rc2.26.g7472058
PATCH v12 7/15 git-clean: add support for -i/--interactive
In reply to Jiang XinShow what would be done and the user must confirm before actually cleaning.
Would remove ...
Would remove ...
Would remove ...
Remove [y/n]?
Press "y" to start cleaning, and press "n" if you want to abort.
--- Documentation/git-clean.txt | 10 ++++++-- builtin/clean.c | 57 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 60 insertions(+), 7 deletions(-)
Documentation/git-clean.txt +8 -2
@@ -8,7 +8,7 @@ git-clean - Remove untracked files from the working tree SYNOPSIS -------- [verse] -'git clean' [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>... +'git clean' [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>... DESCRIPTION -----------
@@ -34,7 +34,13 @@ OPTIONS -f:: --force:: If the Git configuration variable clean.requireForce is not set - to false, 'git clean' will refuse to run unless given -f or -n. + to false, 'git clean' will refuse to run unless given -f, -n or + -i. + +-i:: +--interactive:: + Show what would be done and the user must confirm before actually + cleaning. -n:: --dry-run::
builtin/clean.c +52 -5
93 lines not on this page · raw message
Signature · 1 line
1.8.3.rc2.26.g7472058
PATCH v12 8/15 git-clean: show items of del_list in columns
In reply to Jiang XinWhen there are lots of items to be cleaned, it is hard to see them all in one screen. Show them in columns will solve this problem.
--- Documentation/config.txt | 4 ++++ builtin/clean.c | 49 +++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 9 deletions(-)
Documentation/config.txt +4 -0
@@ -955,6 +955,10 @@ column.branch:: Specify whether to output branch listing in `git branch` in columns. See `column.ui` for details. +column.clean:: + Specify the layout when list items in `git clean -i`, which always + shows files and directories in columns. See `column.ui` for details. + column.status:: Specify whether to output untracked files in `git status` in columns. See `column.ui` for details.
builtin/clean.c +40 -9
86 lines not on this page · raw message
Signature · 1 line
1.8.3.rc2.26.g7472058
PATCH v12 9/15 git-clean: add colors to interactive git-clean
In reply to Jiang XinShow header, help, error messages, and prompt in colors for interactive git-clean. Re-use config variables, such as "color.interactive" and "color.interactive.<slot>" for command git-add--interactive.
--- Documentation/config.txt | 17 +++++------ builtin/clean.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 9 deletions(-)
Documentation/config.txt +9 -8
@@ -876,16 +876,17 @@ The values of these variables may be specified as in color.branch.<slot>. color.interactive:: When set to `always`, always use colors for interactive prompts - and displays (such as those used by "git-add --interactive"). - When false (or `never`), never. When set to `true` or `auto`, use - colors only when the output is to the terminal. Defaults to false. + and displays (such as those used by "git-add --interactive" and + "git-clean --interactive"). When false (or `never`), never. + When set to `true` or `auto`, use colors only when the output is + to the terminal. Defaults to false. color.interactive.<slot>:: - Use customized color for 'git add --interactive' - output. `<slot>` may be `prompt`, `header`, `help` or `error`, for - four distinct types of normal output from interactive - commands. The values of these variables may be specified as - in color.branch.<slot>. + Use customized color for 'git add --interactive' and 'git clean + --interactive' output. `<slot>` may be `prompt`, `header`, `help` + or `error`, for four distinct types of normal output from + interactive commands. The values of these variables may be + specified as in color.branch.<slot>. color.pager:: A boolean to enable/disable colored output when the pager is in
builtin/clean.c +72 -1
108 lines not on this page · raw message
Signature · 1 line
1.8.3.rc2.26.g7472058
PATCH v12 10/15 git-clean: use a git-add-interactive compatible UI
In reply to Jiang XinRewrite menu using a new method list_and_choose, which is borrowed from git-add--interactive.perl. We will use this framework to add new actions for interactive git-clean later.
Please NOTE:
* Method list_and_choose return an array of integers, and
* it is up to you to free the allocated memory of the array.
* The array ends with EOF.
* If user pressed CTRL-D (i.e. EOF), no selection returned.
--- builtin/clean.c | 457 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 428 insertions(+), 29 deletions(-)
builtin/clean.c +428 -29
@@ -50,6 +50,36 @@ enum color_clean { CLEAN_COLOR_ERROR = 5, }; +#define MENU_OPTS_SINGLETON 01 +#define MENU_OPTS_IMMEDIATE 02 +#define MENU_OPTS_LIST_ONLY 04 + +struct menu_opts { +const char *header; +const char *prompt; +int flags; +}; + +#define MENU_RETURN_NO_LOOP 10 + +struct menu_item { +char hotkey; +const char *title; +int selected; +int (*fn)(); +}; + +enum menu_stuff_type { +MENU_STUFF_TYPE_STRING_LIST =1 , +MENU_STUFF_TYPE_MENU_ITEM +}; + +struct menu_stuff { +enum menu_stuff_type type; +int nr; +void *stuff; +}; +static int parse_clean_color_slot (const char *var) {if (!strcasecmp (var,"reset" ))
@@ -240,54 +270,423 @@ static void pretty_print_dels(void) copts.indent =" " ; copts.padding =2 ;print_columns (&list, colopts, &copts); -putchar ('\n' );strbuf_release (&buf);string_list_clear (&list,0 ); } -static void interactive_main_loop (void ) +static void pretty_print_menus (struct string_list *menu_list) +{ +unsigned int local_colopts =0 ; +struct column_options copts; + + local_colopts =COL_ENABLED |COL_ROW ; +memset (&copts,0 ,sizeof (copts)); + copts.indent =" " ; + copts.padding =2 ; +print_columns (menu_list, local_colopts, &copts); +} + +static void prompt_help_cmd (int singleton) +{ +clean_print_color (CLEAN_COLOR_HELP ); +printf_ln (singleton ? +_ ("Prompt help:\n" +"1 - select a numbered item\n" +"foo - select item based on unique prefix\n" +" - (empty) select nothing" ) : +_ ("Prompt help:\n" +"1 - select a single item\n" +"3-5 - select a range of items\n" +"2-3,6-9 - select multiple ranges\n" +"foo - select item based on unique prefix\n" +"-... - unselect specified items\n" +"* - choose all items\n" +" - (empty) finish selecting" )); +clean_print_color (CLEAN_COLOR_RESET ); +} + +/* +* display menu stuff with number prefix and hotkey highlight +*/ +static void print_highlight_menu_stuff (struct menu_stuff *stuff,int **chosen) +{ +struct string_list menu_list =STRING_LIST_INIT_DUP ; +struct strbuf menu =STRBUF_INIT ; +struct strbuf buf =STRBUF_INIT ; +struct menu_item *menu_item; +struct string_list_item *string_list_item; +int i; + +switch (stuff->type) { +default : +die ("Bad type of menu_staff when print menu" ); +case MENU_STUFF_TYPE_MENU_ITEM : + menu_item = (struct menu_item *)stuff->stuff; +for (i =0 ; i < stuff->nr; i++, menu_item++) { +const char *p; +int highlighted =0 ; + + p = menu_item->title; +if ((*chosen)[i] <0 ) + (*chosen)[i] = menu_item->selected ?1 :0 ; +strbuf_addf (&menu,"%s%2d: " , (*chosen)[i] ?"*" :" " , i+1 ); +for (; *p; p++) { +if (!highlighted && *p == menu_item->hotkey) { +strbuf_addstr (&menu,clean_get_color (CLEAN_COLOR_PROMPT )); +strbuf_addch (&menu, *p); +strbuf_addstr (&menu,clean_get_color (CLEAN_COLOR_RESET )); + highlighted =1 ; + }else { +strbuf_addch (&menu, *p); + } + } +string_list_append (&menu_list, menu.buf); +strbuf_reset (&menu); + } +break ; +case MENU_STUFF_TYPE_STRING_LIST : + i =0 ; +for_each_string_list_item (string_list_item, (struct string_list *)stuff->stuff) { +if ((*chosen)[i] <0 ) + (*chosen)[i] =0 ; +strbuf_addf (&menu,"%s%2d: %s" , + (*chosen)[i] ?"*" :" " , i+1 , string_list_item->string); +string_list_append (&menu_list, menu.buf); +strbuf_reset (&menu); + i++; + } +break ; + } + +pretty_print_menus (&menu_list); + +strbuf_release (&menu); +strbuf_release (&buf); +string_list_clear (&menu_list,0 ); +} + +/* +* Parse user input, and return choice(s) for menu (menu_stuff). +* +* Input +* (for single choice) +* 1 - select a numbered item +* foo - select item based on menu title +* - (empty) select nothing +* +* (for multiple choice) +* 1 - select a single item +* 3-5 - select a range of items +* 2-3,6-9 - select multiple ranges +* foo - select item based on menu title +* -... - unselect specified items +* * - choose all items +* - (empty) finish selecting +* +* The parse result will be saved in array **chosen, and +* return number of total selections. +*/ +static int parse_choice (struct menu_stuff *menu_stuff, +int is_single, +struct strbuf input, +int **chosen) +{ +struct strbuf **choice_list, **ptr; +struct menu_item *menu_item; +struct string_list_item *string_list_item; +int nr =0 ; +int i; + +if (is_single) { + choice_list =strbuf_split_max (&input,'\n' ,0 ); + }else { +char *p = input.buf; +do { +if (*p ==',' ) + *p =' ' ; + }while (*p++); + choice_list =strbuf_split_max (&input,' ' ,0 ); + } + +for (ptr = choice_list; *ptr; ptr++) { +char *p; +int choose =1 ; +int bottom =0 , top =0 ; +int is_range, is_number; + +strbuf_trim (*ptr); +if (!(*ptr)->len) +continue ; + +/* Input that begins with '-'; unchoose */ +if (*(*ptr)->buf =='-' ) { + choose =0 ; +strbuf_remove ((*ptr),0 ,1 ); + } + + is_range =0 ; + is_number =1 ; +for (p = (*ptr)->buf; *p; p++) { +if ('-' == *p) { +if (!is_range) { + is_range =1 ; + is_number =0 ; + }else { + is_number =0 ; + is_range =0 ; +break ; + } + }else if (!isdigit (*p)) { + is_number =0 ; + is_range =0 ; +break ; + } + } + +if (is_number) { + bottom =atoi ((*ptr)->buf); + top = bottom; + }else if (is_range) { + bottom =atoi ((*ptr)->buf); +/* a range can be specified like 5-7 or 5- */ +if (!*(strchr ((*ptr)->buf,'-' ) +1 )) { + top = menu_stuff->nr; + }else { + top =atoi (strchr ((*ptr)->buf,'-' ) +1 ); + } + }else if (!strcmp ((*ptr)->buf,"*" )) { + bottom =1 ; + top = menu_stuff->nr; + }else { +switch (menu_stuff->type) { +default : +die ("Bad type of menu_stuff when parse choice" ); +case MENU_STUFF_TYPE_MENU_ITEM : + menu_item = (struct menu_item *)menu_stuff->stuff; +for (i =0 ; i < menu_stuff->nr; i++, menu_item++) { +if (((*ptr)->len ==1 && + *(*ptr)->buf == menu_item->hotkey) || + !strcasecmp ((*ptr)->buf, menu_item->title)) { + bottom = i +1 ; + top = bottom; +break ; + } + } +break ; +case MENU_STUFF_TYPE_STRING_LIST : + string_list_item = ((struct string_list *)menu_stuff->stuff)->items; +for (i =0 ; i < menu_stuff->nr; i++, string_list_item++) { +if (!strcasecmp ((*ptr)->buf, string_list_item->string)) { + bottom = i +1 ; + top = bottom; +break ; + } + } +break ; + } + } + +if (top <=0 || bottom <=0 || top > menu_stuff->nr || bottom > top || + (is_single && bottom != top)) { +clean_print_color (CLEAN_COLOR_ERROR ); +printf_ln (_ ("Huh (%s)?" ), (*ptr)->buf); +clean_print_color (CLEAN_COLOR_RESET ); +continue ; + } + +for (i = bottom; i <= top; i++) + (*chosen)[i-1 ] = choose; + } + +strbuf_list_free (choice_list); + +for (i =0 ; i < menu_stuff->nr; i++) + nr += (*chosen)[i]; +return nr; +} + +/* +* Implement a git-add-interactive compatible UI, which is borrowed +* from git-add--interactive.perl. +* +* Return value: +* +* - Return an array of integers +* - , and it is up to you to free the allocated memory. +* - The array ends with EOF. +* - If user pressed CTRL-D (i.e. EOF), no selection returned. +*/ +static int *list_and_choose (struct menu_opts *opts,struct menu_stuff *stuff) { -struct strbuf confirm =STRBUF_INIT ; +struct strbuf choice =STRBUF_INIT ; +int *chosen, *result; +int nr =0 ; +int eof =0 ; +int i; + + chosen =xmalloc (sizeof (int ) * stuff->nr); +/* set chosen as uninitialized */ +for (i =0 ; i < stuff->nr; i++) + chosen[i] =-1 ; + +for (;;) { +if (opts->header) { +printf_ln ("%s%s%s" , +clean_get_color (CLEAN_COLOR_HEADER ), +_ (opts->header), +clean_get_color (CLEAN_COLOR_RESET )); + } +/* chosen will be initialized by print_highlight_menu_stuff */ +print_highlight_menu_stuff (stuff, &chosen); + +if (opts->flags &MENU_OPTS_LIST_ONLY ) +break ; + +if (opts->prompt) { +printf ("%s%s%s%s" , +clean_get_color (CLEAN_COLOR_PROMPT ), +_ (opts->prompt), + opts->flags &MENU_OPTS_SINGLETON ?"> " :">> " , +clean_get_color (CLEAN_COLOR_RESET )); + } + +if (strbuf_getline (&choice, stdin,'\n' ) !=EOF ) { +strbuf_trim (&choice); + }else { + eof =1 ; +break ; + } + +/* help for prompt */ +if (!strcmp (choice.buf,"?" )) { +prompt_help_cmd (opts->flags &MENU_OPTS_SINGLETON ); +continue ; + } + +/* for a multiple-choice menu, press ENTER (empty) will return back */ +if (!(opts->flags &MENU_OPTS_SINGLETON ) && !choice.len) +break ; + + nr =parse_choice (stuff, + opts->flags &MENU_OPTS_SINGLETON , + choice, + &chosen); + +if (opts->flags &MENU_OPTS_SINGLETON ) { +if (nr) +break ; + }else if (opts->flags &MENU_OPTS_IMMEDIATE ) { +break ; + } + } + +if (eof) { + result =xmalloc (sizeof (int )); + *result =EOF ; + }else { +int j =0 ; + +/* +* recalculate nr, if return back from menu directly with +* default selections. +*/ +if (!nr) { +for (i =0 ; i < stuff->nr; i++) + nr += chosen[i]; + } + + result =xmalloc (sizeof (int ) * (nr +1 )); +memset (result,0 ,sizeof (int ) * (nr +1 )); +for (i =0 ; i < stuff->nr && j < nr; i++) { +if (chosen[i]) + result[j++] = i; + } + result[j] =EOF ; + } + +free (chosen); +strbuf_release (&choice); +return result; +} + +static int clean_cmd (void ) +{ +return MENU_RETURN_NO_LOOP ; +} + +static int quit_cmd (void ) +{ +string_list_clear (&del_list,0 ); +printf_ln (_ ("Bye." )); +return MENU_RETURN_NO_LOOP ; +} + +static int help_cmd (void ) +{ +clean_print_color (CLEAN_COLOR_HELP ); +printf_ln (_ ( +"clean - start cleaning\n" +"quit - stop cleaning\n" +"help - this screen\n" +"? - help for prompt selection" + )); +clean_print_color (CLEAN_COLOR_RESET ); +return 0 ; +} + +static void interactive_main_loop (void ) +{while (del_list.nr) { -putchar ('\n' ); +struct menu_opts menu_opts; +struct menu_stuff menu_stuff; +struct menu_item menus[] = { + {'c' ,"clean" ,0 , clean_cmd}, + {'q' ,"quit" ,0 , quit_cmd}, + {'h' ,"help" ,0 , help_cmd}, + }; +int *chosen; + + menu_opts.header =N_ ("*** Commands ***" ); + menu_opts.prompt =N_ ("What now" ); + menu_opts.flags =MENU_OPTS_SINGLETON ; + + menu_stuff.type =MENU_STUFF_TYPE_MENU_ITEM ; + menu_stuff.stuff = menus; + menu_stuff.nr =sizeof (menus) /sizeof (struct menu_item ); +clean_print_color (CLEAN_COLOR_HEADER );printf_ln (Q_ ("Would remove the following item:" ,"Would remove the following items:" , del_list.nr));clean_print_color (CLEAN_COLOR_RESET ); -putchar ('\n' );pretty_print_dels (); -clean_print_color (CLEAN_COLOR_PROMPT ); -printf (_ ("Remove [y/n]? " )); -clean_print_color (CLEAN_COLOR_RESET ); -if (strbuf_getline (&confirm, stdin,'\n' ) !=EOF ) { -strbuf_trim (&confirm); - }else { -/* Ctrl-D is the same as "quit" */ -string_list_clear (&del_list,0 ); -putchar ('\n' ); -printf_ln ("Bye." ); -break ; - } - -if (confirm.len) { -if (!strncasecmp (confirm.buf,"yes" , confirm.len)) { -break ; - }else if (!strncasecmp (confirm.buf,"no" , confirm.len) || - !strncasecmp (confirm.buf,"quit" , confirm.len)) { -string_list_clear (&del_list,0 ); -printf_ln ("Bye." ); -break ; - }else { + chosen =list_and_choose (&menu_opts, &menu_stuff); + +if (*chosen !=EOF ) { +int ret; + ret = menus[*chosen].fn (); +if (ret !=MENU_RETURN_NO_LOOP ) { +free (chosen); + chosen =NULL ; +if (!del_list.nr) { +clean_print_color (CLEAN_COLOR_ERROR ); +printf_ln (_ ("No more files to clean, exiting." )); +clean_print_color (CLEAN_COLOR_RESET ); +break ; + }continue ; } + }else { +quit_cmd (); } - } -strbuf_release (&confirm); +free (chosen); + chosen =NULL ; +break ; + } }int cmd_clean (int argc,const char **argv,const char *prefix)
Signature · 1 line
1.8.3.rc2.26.g7472058
PATCH v12 11/15 git-clean: add filter by pattern interactive action
In reply to Jiang XinAdd a new action for interactive git-clean: filter by pattern. When the user chooses this action, user can input space-separated patterns (the same syntax as gitignore), and each clean candidate that matches with one of the patterns will be excluded from cleaning. When the user feels it's OK, presses ENTER and backs to the confirmation dialog.
--- builtin/clean.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+)
builtin/clean.c +68 -0
@@ -615,6 +615,72 @@ static int clean_cmd(void)return MENU_RETURN_NO_LOOP ; } +static int filter_by_patterns_cmd (void ) +{ +struct dir_struct dir; +struct strbuf confirm =STRBUF_INIT ; +struct strbuf **ignore_list; +struct string_list_item *item; +struct exclude_list *el; +int changed =-1 , i; + +for (;;) { +if (!del_list.nr) +break ; + +if (changed) +pretty_print_dels (); + +clean_print_color (CLEAN_COLOR_PROMPT ); +printf (_ ("Input ignore patterns>> " )); +clean_print_color (CLEAN_COLOR_RESET ); +if (strbuf_getline (&confirm, stdin,'\n' ) !=EOF ) +strbuf_trim (&confirm); +else +putchar ('\n' ); + +/* quit filter_by_pattern mode if press ENTER or Ctrl-D */ +if (!confirm.len) +break ; + +memset (&dir,0 ,sizeof (dir)); + el =add_exclude_list (&dir,EXC_CMDL ,"manual exclude" ); + ignore_list =strbuf_split_max (&confirm,' ' ,0 ); + +for (i =0 ; ignore_list[i]; i++) { +strbuf_trim (ignore_list[i]); +if (!ignore_list[i]->len) +continue ; + +add_exclude (ignore_list[i]->buf,"" ,0 , el, -(i+1 )); + } + + changed =0 ; +for_each_string_list_item (item, &del_list) { +int dtype =DT_UNKNOWN ; + +if (is_excluded (&dir, item->string, &dtype)) { + *item->string ='\0' ; + changed++; + } + } + +if (changed) { +string_list_remove_empty_items (&del_list,0 ); + }else { +clean_print_color (CLEAN_COLOR_ERROR ); +printf_ln (_ ("WARNING: Cannot find items matched by: %s" ), confirm.buf); +clean_print_color (CLEAN_COLOR_RESET ); + } + +strbuf_list_free (ignore_list); +clear_directory (&dir); + } + +strbuf_release (&confirm); +return 0 ; +} +static int quit_cmd (void ) {string_list_clear (&del_list,0 );
@@ -627,6 +693,7 @@ static int help_cmd(void)clean_print_color (CLEAN_COLOR_HELP ); printf_ln(_( "clean - start cleaning\n" + "filter by pattern - exclude items from deletion\n" "quit - stop cleaning\n" "help - this screen\n" "? - help for prompt selection"
@@ -642,6 +709,7 @@ static void interactive_main_loop(void)struct menu_stuff menu_stuff;struct menu_item menus[] = { {'c' ,"clean" ,0 , clean_cmd}, + {'f' ,"filter by pattern" ,0 , filter_by_patterns_cmd}, {'q' ,"quit" ,0 , quit_cmd}, {'h' ,"help" ,0 , help_cmd}, };
Signature · 1 line
1.8.3.rc2.26.g7472058
PATCH v12 12/15 git-clean: add select by numbers interactive action
In reply to Jiang XinDraw a multiple choice menu using list_and_choose to select items to be deleted by numbers.
User can input:
* 1,5-7 : select 1,5,6,7 items to be deleted * * : select all items to be deleted * -* : unselect all, nothing will be deleted
* : (empty) finish selecting, and return back to main menu
--- builtin/clean.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+)
builtin/clean.c +39 -0
@@ -681,6 +681,43 @@ static int filter_by_patterns_cmd(void)return 0 ; } +static int select_by_numbers_cmd (void ) +{ +struct menu_opts menu_opts; +struct menu_stuff menu_stuff; +struct string_list_item *items; +int *chosen; +int i, j; + + menu_opts.header =NULL ; + menu_opts.prompt =N_ ("Select items to delete" ); + menu_opts.flags =0 ; + + menu_stuff.type =MENU_STUFF_TYPE_STRING_LIST ; + menu_stuff.stuff = &del_list; + menu_stuff.nr = del_list.nr; + + chosen =list_and_choose (&menu_opts, &menu_stuff); + items = del_list.items; +for (i =0 , j =0 ; i < del_list.nr; i++) { +if (i < chosen[j]) { + *(items[i].string) ='\0' ; + }else if (i == chosen[j]) { +/* delete selected item */ + j++; +continue ; + }else { +/* end of chosen (chosen[j] == EOF), won't delete */ + *(items[i].string) ='\0' ; + } + } + +string_list_remove_empty_items (&del_list,0 ); + +free (chosen); +return 0 ; +} +static int quit_cmd (void ) {string_list_clear (&del_list,0 );
@@ -694,6 +731,7 @@ static int help_cmd(void) printf_ln(_( "clean - start cleaning\n" "filter by pattern - exclude items from deletion\n" + "select by numbers - select items to be deleted by numbers\n" "quit - stop cleaning\n" "help - this screen\n" "? - help for prompt selection"
@@ -710,6 +748,7 @@ static void interactive_main_loop(void)struct menu_item menus[] = { {'c' ,"clean" ,0 , clean_cmd}, {'f' ,"filter by pattern" ,0 , filter_by_patterns_cmd}, + {'s' ,"select by numbers" ,0 , select_by_numbers_cmd}, {'q' ,"quit" ,0 , quit_cmd}, {'h' ,"help" ,0 , help_cmd}, };
Signature · 1 line
1.8.3.rc2.26.g7472058
PATCH v12 13/15 git-clean: add ask each interactive action
In reply to Jiang XinAdd a new action for interactive git-clean: ask each. It's just like the "rm -i" command, that the user must confirm one by one for each file or directory to be cleaned.
--- builtin/clean.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+)
builtin/clean.c +36 -0
@@ -718,6 +718,40 @@ static int select_by_numbers_cmd(void)return 0 ; } +static int ask_each_cmd (void ) +{ +struct strbuf confirm =STRBUF_INIT ; +struct strbuf buf =STRBUF_INIT ; +struct string_list_item *item; +const char *qname; +int changed =0 , eof =0 ; + +for_each_string_list_item (item, &del_list) { +/* Ctrl-D should stop removing files */ +if (!eof) { + qname =quote_path_relative (item->string,NULL , &buf); +printf (_ ("remove %s? " ), qname); +if (strbuf_getline (&confirm, stdin,'\n' ) !=EOF ) { +strbuf_trim (&confirm); + }else { +putchar ('\n' ); + eof =1 ; + } + } +if (!confirm.len ||strncasecmp (confirm.buf,"yes" , confirm.len)) { + *item->string ='\0' ; + changed++; + } + } + +if (changed) +string_list_remove_empty_items (&del_list,0 ); + +strbuf_release (&buf); +strbuf_release (&confirm); +return MENU_RETURN_NO_LOOP ; +} +static int quit_cmd (void ) {string_list_clear (&del_list,0 );
@@ -732,6 +766,7 @@ static int help_cmd(void)"clean - start cleaning\n" "filter by pattern - exclude items from deletion\n" "select by numbers - select items to be deleted by numbers\n" +"ask each - confirm each deletion (like \"rm -i\")\n" "quit - stop cleaning\n" "help - this screen\n" "? - help for prompt selection"
@@ -749,6 +784,7 @@ static void interactive_main_loop(void) {'c', "clean", 0, clean_cmd}, {'f', "filter by pattern", 0, filter_by_patterns_cmd}, {'s', "select by numbers", 0, select_by_numbers_cmd}, + {'a', "ask each", 0, ask_each_cmd}, {'q', "quit", 0, quit_cmd}, {'h', "help", 0, help_cmd}, };
Signature · 1 line
1.8.3.rc2.26.g7472058
PATCH v12 14/15 git-clean: add documentation for interactive git-clean
In reply to Jiang XinAdd new section "Interactive mode" for documentation of interactive git-clean.
--- Documentation/git-clean.txt | 65 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-)
Documentation/git-clean.txt +63 -2
@@ -39,8 +39,8 @@ OPTIONS -i:: --interactive:: - Show what would be done and the user must confirm before actually - cleaning. + Show what would be done and clean files interactively. See + ``Interactive mode'' for details. -n:: --dry-run::
@@ -69,6 +69,67 @@ OPTIONS Remove only files ignored by Git. This may be useful to rebuild everything from scratch, but keep manually created files. +Interactive mode +---------------- +When the command enters the interactive mode, it shows the +files and directories to be cleaned, and goes into its +interactive command loop. + +The command loop shows the list of subcommands available, and +gives a prompt "What now> ". In general, when the prompt ends +with a single '>', you can pick only one of the choices given +and type return, like this: + +------------ + *** Commands *** + 1: clean 2: filter by pattern 3: select by numbers + 4: ask each 5: quit 6: help + What now> 1 +------------ + +You also could say `c` or `clean` above as long as the choice is unique. + +The main command loop has 6 subcommands. + +clean:: + + Start cleaning files and directories, and then quit. + +filter by pattern:: + + This shows the files and directories to be deleted and issues an + "Input ignore patterns>>" prompt. You can input space-seperated + patterns to exclude files and directories from deletion. + E.g. "*.c *.h" will excludes files end with ".c" and ".h" from + deletion. When you are satisfied with the filtered result, press + ENTER (empty) back to the main menu. + +select by numbers:: + + This shows the files and directories to be deleted and issues an + "Select items to delete>>" prompt. When the prompt ends with double + '>>' like this, you can make more than one selection, concatenated + with whitespace or comma. Also you can say ranges. E.g. "2-5 7,9" + to choose 2,3,4,5,7,9 from the list. If the second number in a + range is omitted, all remaining patches are taken. E.g. "7-" to + choose 7,8,9 from the list. You can say '*' to choose everything. + Also when you are satisfied with the filtered result, press ENTER + (empty) back to the main menu. + +ask each:: + + This will start to clean, and you must confirm one by one in order + to delete items. Please note that this action is not as efficient + as the above two actions. + +quit:: + + This lets you quit without do cleaning. + +help:: + + Show brief usage of interactive git-clean. + SEE ALSO -------- linkgit:gitignore[5]
Signature · 1 line
1.8.3.rc2.26.g7472058
PATCH v12 15/15 test: add t7301 for git-clean--interactive
In reply to Jiang XinAdd test cases for git-clean--interactive.
--- t/t7301-clean-interactive.sh | 439 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 439 insertions(+) create mode 100755 t/t7301-clean-interactive.sh
t/t7301-clean-interactive.sh new file +439 -0
@@ -0,0 +1,439 @@ +#!/bin/sh + +test_description='git clean -i basic tests' + +. ./test-lib.sh + +test_expect_success 'setup' ' + + mkdir -p src && + touch src/part1.c Makefile && + echo build >.gitignore && + echo \*.o >>.gitignore && + git add . && + git commit -m setup && + touch src/part2.c README && + git add . + +' + +test_expect_success 'git clean -i (clean)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + echo c | git clean -i && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -i (quit)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + echo q | git clean -i && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -i (Ctrl+D)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + echo "\04" | git clean -i && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (filter all)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo f; echo "*"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (filter patterns)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo f; echo "part3.* *.out"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (filter patterns 2)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo f; echo "* !*.out"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - all)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo "*"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - none)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - number)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 3; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - number 2)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 2 3; echo 5; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - number 3)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 3,4 5; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - range)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 1,3-4; echo 2; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test ! -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - range 2)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 4- 1; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (inverse select)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo "*"; echo -5- 1 -2; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (ask)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f docs/manual.txt && + test -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (ask - Ctrl+D)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo a; echo Y; echo no; echo yes; echo "\04") | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id with prefix and path (filter)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (cd build/ && \ + (echo f; echo "docs"; echo "*.h"; echo ; echo c) | \ + git clean -id ..) && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id with prefix and path (select by name)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (cd build/ && \ + (echo s; echo "../docs/"; echo "../src/part3.c"; \ + echo "../src/part4.c"; echo; echo c) | \ + git clean -id ..) && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id with prefix and path (ask)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (cd build/ && \ + (echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \ + git clean -id ..) && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f docs/manual.txt && + test -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_done
Signature · 1 line
1.8.3.rc2.26.g7472058
Re: [PATCH v12 00/15] Interactive git-clean
In reply to Jiang XinWill replace what has been queued on 'pu' with trivial style fixups (haven't had a chance to make time to read it through). Thanks.
PATCH v11 13/15 git-clean: add ask each interactive action
In reply to Jiang XinAdd a new action for interactive git-clean: ask each. It's just like the "rm -i" command, that the user must confirm one by one for each file or directory to be cleaned.
--- builtin/clean.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+)
builtin/clean.c +36 -0
@@ -717,6 +717,40 @@ static int select_by_numbers_cmd(void)return 0 ; } +static int ask_each_cmd (void ) +{ +struct strbuf confirm =STRBUF_INIT ; +struct strbuf buf =STRBUF_INIT ; +struct string_list_item *item; +const char *qname; +int changed =0 , eof =0 ; + +for_each_string_list_item (item, &del_list) { +/* Ctrl-D should stop removing files */ +if (!eof) { + qname =quote_path_relative (item->string,NULL , &buf); +printf (_ ("remove %s? " ), qname); +if (strbuf_getline (&confirm, stdin,'\n' ) !=EOF ) { +strbuf_trim (&confirm); + }else { +putchar ('\n' ); + eof =1 ; + } + } +if (!confirm.len ||strncasecmp (confirm.buf,"yes" , confirm.len)) { + *item->string ='\0' ; + changed++; + } + } + +if (changed) +string_list_remove_empty_items (&del_list,0 ); + +strbuf_release (&buf); +strbuf_release (&confirm); +return MENU_RETURN_NO_LOOP ; +} +static int quit_cmd (void ) {string_list_clear (&del_list,0 );
@@ -731,6 +765,7 @@ static int help_cmd(void)"clean - start cleaning\n" "filter by pattern - exclude items from deletion\n" "select by numbers - select items to be deleted by numbers\n" +"ask each - confirm each deletion (like \"rm -i\")\n" "quit - stop cleaning\n" "help - this screen\n" "? - help for prompt selection"
@@ -748,6 +783,7 @@ static void interactive_main_loop(void) {'c', "clean", 0, clean_cmd}, {'f', "filter by pattern", 0, filter_by_patterns_cmd}, {'s', "select by numbers", 0, select_by_numbers_cmd}, + {'a', "ask each", 0, ask_each_cmd}, {'q', "quit", 0, quit_cmd}, {'h', "help", 0, help_cmd}, };
Signature · 1 line
1.8.3.rc2.380.g956c2b2
PATCH v11 14/15 git-clean: add documentation for interactive git-clean
In reply to Jiang XinAdd new section "Interactive mode" for documentation of interactive git-clean.
--- Documentation/git-clean.txt | 65 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-)
Documentation/git-clean.txt +63 -2
@@ -39,8 +39,8 @@ OPTIONS -i:: --interactive:: - Show what would be done and the user must confirm before actually - cleaning. + Show what would be done and clean files interactively. See + ``Interactive mode'' for details. -n:: --dry-run::
@@ -69,6 +69,67 @@ OPTIONS Remove only files ignored by Git. This may be useful to rebuild everything from scratch, but keep manually created files. +Interactive mode +---------------- +When the command enters the interactive mode, it shows the +files and directories to be cleaned, and goes into its +interactive command loop. + +The command loop shows the list of subcommands available, and +gives a prompt "What now> ". In general, when the prompt ends +with a single '>', you can pick only one of the choices given +and type return, like this: + +------------ + *** Commands *** + 1: clean 2: filter by pattern 3: select by numbers + 4: ask each 5: quit 6: help + What now> 1 +------------ + +You also could say `c` or `clean` above as long as the choice is unique. + +The main command loop has 6 subcommands. + +clean:: + + Start cleaning files and directories, and then quit. + +filter by pattern:: + + This shows the files and directories to be deleted and issues an + "Input ignore patterns>>" prompt. You can input space-seperated + patterns to exclude files and directories from deletion. + E.g. "*.c *.h" will excludes files end with ".c" and ".h" from + deletion. When you are satisfied with the filtered result, press + ENTER (empty) back to the main menu. + +select by numbers:: + + This shows the files and directories to be deleted and issues an + "Select items to delete>>" prompt. When the prompt ends with double + '>>' like this, you can make more than one selection, concatenated + with whitespace or comma. Also you can say ranges. E.g. "2-5 7,9" + to choose 2,3,4,5,7,9 from the list. If the second number in a + range is omitted, all remaining patches are taken. E.g. "7-" to + choose 7,8,9 from the list. You can say '*' to choose everything. + Also when you are satisfied with the filtered result, press ENTER + (empty) back to the main menu. + +ask each:: + + This will start to clean, and you must confirm one by one in order + to delete items. Please note that this action is not as efficient + as the above two actions. + +quit:: + + This lets you quit without do cleaning. + +help:: + + Show brief usage of interactive git-clean. + SEE ALSO -------- linkgit:gitignore[5]
Signature · 1 line
1.8.3.rc2.380.g956c2b2
PATCH v11 15/15 test: add t7301 for git-clean--interactive
In reply to Jiang XinAdd test cases for git-clean--interactive.
--- t/t7301-clean-interactive.sh | 439 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 439 insertions(+) create mode 100755 t/t7301-clean-interactive.sh
t/t7301-clean-interactive.sh new file +439 -0
@@ -0,0 +1,439 @@ +#!/bin/sh + +test_description='git clean -i basic tests' + +. ./test-lib.sh + +test_expect_success 'setup' ' + + mkdir -p src && + touch src/part1.c Makefile && + echo build >.gitignore && + echo \*.o >>.gitignore && + git add . && + git commit -m setup && + touch src/part2.c README && + git add . + +' + +test_expect_success 'git clean -i (clean)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + echo c | git clean -i && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -i (quit)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + echo q | git clean -i && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -i (Ctrl+D)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + echo "\04" | git clean -i && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (filter all)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo f; echo "*"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (filter patterns)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo f; echo "part3.* *.out"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (filter patterns 2)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo f; echo "* !*.out"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - all)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo "*"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - none)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - number)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 3; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - number 2)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 2 3; echo 5; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - number 3)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 3,4 5; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - range)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 1,3-4; echo 2; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test ! -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - range 2)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 4- 1; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (inverse select)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo "*"; echo -5- 1 -2; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (ask)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f docs/manual.txt && + test -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (ask - Ctrl+D)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo a; echo Y; echo no; echo yes; echo "\04") | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id with prefix and path (filter)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (cd build/ && \ + (echo f; echo "docs"; echo "*.h"; echo ; echo c) | \ + git clean -id ..) && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id with prefix and path (select by name)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (cd build/ && \ + (echo s; echo "../docs/"; echo "../src/part3.c"; \ + echo "../src/part4.c"; echo; echo c) | \ + git clean -id ..) && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id with prefix and path (ask)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (cd build/ && \ + (echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \ + git clean -id ..) && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f docs/manual.txt && + test -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_done
Signature · 1 line
1.8.3.rc2.380.g956c2b2