Probably not much to say. A big portion of this series is the
conversion to struct pathspec, which enables more use of pathspec
magic. :(glob) magic is added to verify that the conversion makes
sense.
Andrew Wong (1):
setup.c: check that the pathspec magic ends with ")"
Nguyễn Thái Ngọc Duy (44):
clean: remove unused variable "seen"
Move struct pathspec and related functions to pathspec.[ch]
pathspec: i18n-ize error strings in pathspec parsing code
pathspec: add copy_pathspec
Add parse_pathspec() that converts cmdline args to struct pathspec
parse_pathspec: save original pathspec for reporting
parse_pathspec: add PATHSPEC_PREFER_{CWD,FULL}
Convert some get_pathspec() calls to parse_pathspec()
parse_pathspec: a special flag for max_depth feature
parse_pathspec: support stripping submodule trailing slashes
parse_pathspec: support stripping/checking submodule paths
parse_pathspec: support prefixing original patterns
Guard against new pathspec magic in pathspec matching code
clean: convert to use parse_pathspec
commit: convert to use parse_pathspec
status: convert to use parse_pathspec
rerere: convert to use parse_pathspec
checkout: convert to use parse_pathspec
rm: convert to use parse_pathspec
ls-files: convert to use parse_pathspec
archive: convert to use parse_pathspec
check-ignore: convert to use parse_pathspec
add: convert to use parse_pathspec
reset: convert to use parse_pathspec
Convert read_cache_preload() to take struct pathspec
Convert run_add_interactive to use struct pathspec
Convert unmerge_cache to take struct pathspec
checkout: convert read_tree_some to take struct pathspec
Convert report_path_error to take struct pathspec
Convert refresh_index to take struct pathspec
Convert {read,fill}_directory to take struct pathspec
Convert add_files_to_cache to take struct pathspec
Convert common_prefix() to use struct pathspec
Remove diff_tree_{setup,release}_paths
Remove init_pathspec() in favor of parse_pathspec()
Remove match_pathspec() in favor of match_pathspec_depth()
tree-diff: remove the use of pathspec's raw[] in follow-rename codepath
parse_pathspec: make sure the prefix part is wildcard-free
parse_pathspec: preserve prefix length via PATHSPEC_PREFIX_ORIGIN
Kill limit_pathspec_to_literal() as it's only used by parse_pathspec()
pathspec: support :(literal) syntax for noglob pathspec
pathspec: make --literal-pathspecs disable pathspec magic
pathspec: support :(glob) syntax
Rename field "raw" to "_raw" in struct pathspec
Documentation/git.txt | 23 +-
Documentation/glossary-content.txt | 33 +++
archive.c | 18 +-
archive.h | 4 +-
builtin/add.c | 156 ++++++--------
builtin/blame.c | 14 +-
builtin/check-ignore.c | 34 +--
builtin/checkout.c | 46 ++--
builtin/clean.c | 24 +--
builtin/commit.c | 37 ++--
builtin/diff-files.c | 2 +-
builtin/diff-index.c | 2 +-
builtin/diff.c | 6 +-
builtin/grep.c | 10 +-
builtin/log.c | 2 +-
builtin/ls-files.c | 75 +++----
builtin/ls-tree.c | 13 +-
builtin/mv.c | 13 +-
builtin/rerere.c | 8 +-
builtin/reset.c | 33 +--
builtin/rm.c | 24 +--
builtin/update-index.c | 6 +-
cache.h | 34 +--
commit.h | 2 +-
diff-lib.c | 3 +-
diff.h | 3 +-
dir.c | 261 +++++-----------------
dir.h | 18 +-
git.c | 8 +
merge-recursive.c | 2 +-
notes-merge.c | 4 +-
path.c | 15 +-
pathspec.c | 431 +++++++++++++++++++++++++++++++++----
pathspec.h | 59 ++++-
preload-index.c | 21 +-
read-cache.c | 5 +-
rerere.c | 7 +-
rerere.h | 4 +-
resolve-undo.c | 4 +-
resolve-undo.h | 2 +-
revision.c | 11 +-
setup.c | 157 +-------------
t/t0008-ignores.sh | 8 +-
t/t6130-pathspec-noglob.sh | 18 ++
tree-diff.c | 48 +++--
tree-walk.c | 21 +-
tree.c | 4 +-
tree.h | 2 +-
wt-status.c | 18 +-
wt-status.h | 2 +-
50 files changed, 983 insertions(+), 772 deletions(-)
--
1.8.0.rc0.19.g7bbb31d
From: Andrew Wong <redacted>
The previous code did not diagnose an incorrectly spelled ":(top"
as an error.
Signed-off-by: Andrew Wong <redacted>
Signed-off-by: Junio C Hamano <redacted>
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
setup.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
@@ -223,8 +223,9 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const chardie("Invalid pathspec magic '%.*s' in '%s'",(int)len,copyfrom,elt);}-if(*copyfrom==')')-copyfrom++;+if(*copyfrom!=')')+die("Missing ')' at the end of pathspec magic in '%s'",elt);+copyfrom++;}else{/* shorthand */for(copyfrom=elt+1;
@@ -99,3 +99,152 @@ void die_if_path_beyond_symlink(const char *path, const char *prefix)die(_("'%s' is beyond a symbolic link"),path+len);}}++/*+*Magicpathspec+*+*NEEDSWORK:Theseneedtobemovedtodir.horeventoanew+*pathspec.hwhenwerestructureget_pathspec()userstousethe+*"struct pathspec"interface.+*+*Possiblefuturemagicsemanticsincludestufflike:+*+*{PATHSPEC_NOGLOB,'!',"noglob"},+*{PATHSPEC_ICASE,'\0',"icase"},+*{PATHSPEC_RECURSIVE,'*',"recursive"},+*{PATHSPEC_REGEXP,'\0',"regexp"},+*+*/+#define PATHSPEC_FROMTOP (1<<0)++staticstructpathspec_magic{+unsignedbit;+charmnemonic;/* this cannot be ':'! */+constchar*name;+}pathspec_magic[]={+{PATHSPEC_FROMTOP,'/',"top"},+};++/*+*Takeanelementofapathspecandcheckformagicsignatures.+*Appendtheresulttotheprefix.+*+*Fornow,weonlyparsethesyntaxandthrowoutanythingotherthan+*"top"magic.+*+*NEEDSWORK:Thisneedstoberewrittenwhenwestartmigrating+*get_pathspec()userstousethe"struct pathspec"interface.For+*example,apathspecelementmaybemarkedascase-insensitive,but+*theprefixpartmustalwaysmatchliterally,andasinglestupid+*stringcannotexpresssuchacase.+*/+staticconstchar*prefix_pathspec(constchar*prefix,intprefixlen,constchar*elt)+{+unsignedmagic=0;+constchar*copyfrom=elt;+inti;++if(elt[0]!=':'){+;/* nothing to do */+}elseif(elt[1]=='('){+/* longhand */+constchar*nextat;+for(copyfrom=elt+2;+*copyfrom&&*copyfrom!=')';+copyfrom=nextat){+size_tlen=strcspn(copyfrom,",)");+if(copyfrom[len]==')')+nextat=copyfrom+len;+else+nextat=copyfrom+len+1;+if(!len)+continue;+for(i=0;i<ARRAY_SIZE(pathspec_magic);i++)+if(strlen(pathspec_magic[i].name)==len&&+!strncmp(pathspec_magic[i].name,copyfrom,len)){+magic|=pathspec_magic[i].bit;+break;+}+if(ARRAY_SIZE(pathspec_magic)<=i)+die("Invalid pathspec magic '%.*s' in '%s'",+(int)len,copyfrom,elt);+}+if(*copyfrom!=')')+die("Missing ')' at the end of pathspec magic in '%s'",elt);+copyfrom++;+}else{+/* shorthand */+for(copyfrom=elt+1;+*copyfrom&&*copyfrom!=':';+copyfrom++){+charch=*copyfrom;++if(!is_pathspec_magic(ch))+break;+for(i=0;i<ARRAY_SIZE(pathspec_magic);i++)+if(pathspec_magic[i].mnemonic==ch){+magic|=pathspec_magic[i].bit;+break;+}+if(ARRAY_SIZE(pathspec_magic)<=i)+die("Unimplemented pathspec magic '%c' in '%s'",+ch,elt);+}+if(*copyfrom==':')+copyfrom++;+}++if(magic&PATHSPEC_FROMTOP)+returnxstrdup(copyfrom);+else+returnprefix_path(prefix,prefixlen,copyfrom);+}++/*+*N.B.get_pathspec()isdeprecatedinfavorofthe"struct pathspec"+*basedinterface-seepathspec_magicabove.+*+*Arguments:+*-prefix-apathrelativetotherootoftheworkingtree+*-pathspec-alistofpathsunderneaththeprefixpath+*+*Iteratesoverpathspec,prependingeachpathwithprefix,+*andreturntheresultinglist.+*+*Ifpathspecisempty,returnasingletonlistcontainingprefix.+*+*Ifpathspecandprefixarebothempty,returnanemptylist.+*+*Thisistypicallyusedbybuilt-incommandssuchasadd.c,inorder+*tonormalizeargvargumentsprovidedtothebuilt-inintoalistof+*pathstoprocess,allrelativetotherootoftheworkingtree.+*/+constchar**get_pathspec(constchar*prefix,constchar**pathspec)+{+constchar*entry=*pathspec;+constchar**src,**dst;+intprefixlen;++if(!prefix&&!entry)+returnNULL;++if(!entry){+staticconstchar*spec[2];+spec[0]=prefix;+spec[1]=NULL;+returnspec;+}++/* Otherwise we have to re-write the entries.. */+src=pathspec;+dst=pathspec;+prefixlen=prefix?strlen(prefix):0;+while(*src){+*(dst++)=prefix_pathspec(prefix,prefixlen,*src);+src++;+}+*dst=NULL;+if(!*pathspec)+returnNULL;+returnpathspec;+}
@@ -155,155 +155,6 @@ void verify_non_filename(const char *prefix, const char *arg)}/*-*Magicpathspec-*-*NEEDSWORK:Theseneedtobemovedtodir.horeventoanew-*pathspec.hwhenwerestructureget_pathspec()userstousethe-*"struct pathspec"interface.-*-*Possiblefuturemagicsemanticsincludestufflike:-*-*{PATHSPEC_NOGLOB,'!',"noglob"},-*{PATHSPEC_ICASE,'\0',"icase"},-*{PATHSPEC_RECURSIVE,'*',"recursive"},-*{PATHSPEC_REGEXP,'\0',"regexp"},-*-*/-#define PATHSPEC_FROMTOP (1<<0)--staticstructpathspec_magic{-unsignedbit;-charmnemonic;/* this cannot be ':'! */-constchar*name;-}pathspec_magic[]={-{PATHSPEC_FROMTOP,'/',"top"},-};--/*-*Takeanelementofapathspecandcheckformagicsignatures.-*Appendtheresulttotheprefix.-*-*Fornow,weonlyparsethesyntaxandthrowoutanythingotherthan-*"top"magic.-*-*NEEDSWORK:Thisneedstoberewrittenwhenwestartmigrating-*get_pathspec()userstousethe"struct pathspec"interface.For-*example,apathspecelementmaybemarkedascase-insensitive,but-*theprefixpartmustalwaysmatchliterally,andasinglestupid-*stringcannotexpresssuchacase.-*/-staticconstchar*prefix_pathspec(constchar*prefix,intprefixlen,constchar*elt)-{-unsignedmagic=0;-constchar*copyfrom=elt;-inti;--if(elt[0]!=':'){-;/* nothing to do */-}elseif(elt[1]=='('){-/* longhand */-constchar*nextat;-for(copyfrom=elt+2;-*copyfrom&&*copyfrom!=')';-copyfrom=nextat){-size_tlen=strcspn(copyfrom,",)");-if(copyfrom[len]==')')-nextat=copyfrom+len;-else-nextat=copyfrom+len+1;-if(!len)-continue;-for(i=0;i<ARRAY_SIZE(pathspec_magic);i++)-if(strlen(pathspec_magic[i].name)==len&&-!strncmp(pathspec_magic[i].name,copyfrom,len)){-magic|=pathspec_magic[i].bit;-break;-}-if(ARRAY_SIZE(pathspec_magic)<=i)-die("Invalid pathspec magic '%.*s' in '%s'",-(int)len,copyfrom,elt);-}-if(*copyfrom!=')')-die("Missing ')' at the end of pathspec magic in '%s'",elt);-copyfrom++;-}else{-/* shorthand */-for(copyfrom=elt+1;-*copyfrom&&*copyfrom!=':';-copyfrom++){-charch=*copyfrom;--if(!is_pathspec_magic(ch))-break;-for(i=0;i<ARRAY_SIZE(pathspec_magic);i++)-if(pathspec_magic[i].mnemonic==ch){-magic|=pathspec_magic[i].bit;-break;-}-if(ARRAY_SIZE(pathspec_magic)<=i)-die("Unimplemented pathspec magic '%c' in '%s'",-ch,elt);-}-if(*copyfrom==':')-copyfrom++;-}--if(magic&PATHSPEC_FROMTOP)-returnxstrdup(copyfrom);-else-returnprefix_path(prefix,prefixlen,copyfrom);-}--/*-*N.B.get_pathspec()isdeprecatedinfavorofthe"struct pathspec"-*basedinterface-seepathspec_magicabove.-*-*Arguments:-*-prefix-apathrelativetotherootoftheworkingtree-*-pathspec-alistofpathsunderneaththeprefixpath-*-*Iteratesoverpathspec,prependingeachpathwithprefix,-*andreturntheresultinglist.-*-*Ifpathspecisempty,returnasingletonlistcontainingprefix.-*-*Ifpathspecandprefixarebothempty,returnanemptylist.-*-*Thisistypicallyusedbybuilt-incommandssuchasadd.c,inorder-*tonormalizeargvargumentsprovidedtothebuilt-inintoalistof-*pathstoprocess,allrelativetotherootoftheworkingtree.-*/-constchar**get_pathspec(constchar*prefix,constchar**pathspec)-{-constchar*entry=*pathspec;-constchar**src,**dst;-intprefixlen;--if(!prefix&&!entry)-returnNULL;--if(!entry){-staticconstchar*spec[2];-spec[0]=prefix;-spec[1]=NULL;-returnspec;-}--/* Otherwise we have to re-write the entries.. */-src=pathspec;-dst=pathspec;-prefixlen=prefix?strlen(prefix):0;-while(*src){-*(dst++)=prefix_pathspec(prefix,prefixlen,*src);-src++;-}-*dst=NULL;-if(!*pathspec)-returnNULL;-returnpathspec;-}--/**Testifitlookslikewe'reatagitdirectory.*Wewanttosee:*
@@ -166,11 +166,11 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const charbreak;}if(ARRAY_SIZE(pathspec_magic)<=i)-die("Invalid pathspec magic '%.*s' in '%s'",+die(_("Invalid pathspec magic '%.*s' in '%s'"),(int)len,copyfrom,elt);}if(*copyfrom!=')')-die("Missing ')' at the end of pathspec magic in '%s'",elt);+die(_("Missing ')' at the end of pathspec magic in '%s'"),elt);copyfrom++;}else{/* shorthand */
@@ -187,7 +187,7 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const charbreak;}if(ARRAY_SIZE(pathspec_magic)<=i)-die("Unimplemented pathspec magic '%c' in '%s'",+die(_("Unimplemented pathspec magic '%c' in '%s'"),ch,elt);}if(*copyfrom==':')
The function is made to use with free_pathspec() because a simple
struct assignment is not enough (free_pathspec wants to free "items"
pointer).
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
builtin/mv.c | 13 +++++++------
pathspec.c | 8 ++++++++
pathspec.h | 1 +
3 files changed, 16 insertions(+), 6 deletions(-)
@@ -81,17 +82,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix)if(read_cache()<0)die(_("index file corrupt"));-source=copy_pathspec(prefix,argv,argc,0);+source=internal_copy_pathspec(prefix,argv,argc,0);modes=xcalloc(argc,sizeof(enumupdate_mode));-dest_path=copy_pathspec(prefix,argv+argc,1,0);+dest_path=internal_copy_pathspec(prefix,argv+argc,1,0);if(dest_path[0][0]=='\0')/* special case: "." was normalized to "" */-destination=copy_pathspec(dest_path[0],argv,argc,1);+destination=internal_copy_pathspec(dest_path[0],argv,argc,1);elseif(!lstat(dest_path[0],&st)&&S_ISDIR(st.st_mode)){dest_path[0]=add_slash(dest_path[0]);-destination=copy_pathspec(dest_path[0],argv,argc,1);+destination=internal_copy_pathspec(dest_path[0],argv,argc,1);}else{if(argc!=1)die("destination '%s' is not a directory",dest_path[0]);
We usually use pathspec_item's match field for pathspec error
reporting. However "match" (or "raw") does not show the magic part,
which will play more important role later on. Preserve exact user
input for reporting.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
dir.c | 1 +
pathspec.c | 2 ++
pathspec.h | 1 +
3 files changed, 4 insertions(+)
Currently to fill a struct pathspec, we do:
const char **paths;
paths = get_pathspec(prefix, argv);
...
init_pathspec(&pathspec, paths);
"paths" can only carry bare strings, which loses information from
command line arguments such as pathspec magic or the prefix part's
length for each argument.
parse_pathspec() is introduced to combine the two calls into one. The
plan is gradually replace all get_pathspec() and init_pathspec() with
parse_pathspec(). get_pathspec() now becomes a thin wrapper of
parse_pathspec().
parse_pathspec() allows the caller to reject the pathspec magics that
it does not support. When a new pathspec magic is introduced, we can
enable it per command after making sure that all underlying code has no
problem with the new magic.
"flags" parameter is currently unused. But it would allow callers to
pass certain instructions to parse_pathspec, for example forcing
literal pathspec when no magic is used.
With the introduction of parse_pathspec, there are now two functions
that can initialize struct pathspec: init_pathspec and
parse_pathspec. Any semantic changes in struct pathspec must be
reflected in both functions. init_pathspec() will be phased out in
favor of parse_pathspec().
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
dir.c | 4 +-
dir.h | 2 +
pathspec.c | 167 +++++++++++++++++++++++++++++++++++++++++++++++--------------
pathspec.h | 11 ++++
4 files changed, 144 insertions(+), 40 deletions(-)
@@ -194,15 +194,127 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const charcopyfrom++;}+magic|=short_magic;+*p_short_magic=short_magic;+if(magic&PATHSPEC_FROMTOP)-returnxstrdup(copyfrom);+match=xstrdup(copyfrom);else-returnprefix_path(prefix,prefixlen,copyfrom);+match=prefix_path(prefix,prefixlen,copyfrom);+*raw=item->match=match;+item->len=strlen(item->match);+if(limit_pathspec_to_literal())+item->nowildcard_len=item->len;+else+item->nowildcard_len=simple_length(item->match);+item->flags=0;+if(item->nowildcard_len<item->len&&+item->match[item->nowildcard_len]=='*'&&+no_wildcard(item->match+item->nowildcard_len+1))+item->flags|=PATHSPEC_ONESTAR;+returnmagic;+}++staticintpathspec_item_cmp(constvoid*a_,constvoid*b_)+{+structpathspec_item*a,*b;++a=(structpathspec_item*)a_;+b=(structpathspec_item*)b_;+returnstrcmp(a->match,b->match);+}++staticvoidNORETURNunsupported_magic(constchar*pattern,+unsignedmagic,+unsignedshort_magic)+{+structstrbufsb=STRBUF_INIT;+inti,n;+for(n=i=0;i<ARRAY_SIZE(pathspec_magic);i++){+conststructpathspec_magic*m=pathspec_magic+i;+if(!(magic&m->bit))+continue;+if(sb.len)+strbuf_addstr(&sb," ");+if(short_magic&m->bit)+strbuf_addf(&sb,"'%c'",m->mnemonic);+else+strbuf_addf(&sb,"'%s'",m->name);+n++;+}+/*+*Wemaywanttosubstitue"this command"withacommand+*name.E.g.whenadd--interactivedieswhenrunning+*"checkout -p"+*/+die(_("%s: pathspec magic not supported by this command: %s"),+pattern,sb.buf);+}++/*+*Givencommandlineargumentsandaprefix,converttheinputto+*pathspec.die()anymagicinmagic_maskisused.+*/+voidparse_pathspec(structpathspec*pathspec,+unsignedmagic_mask,unsignedflags,+constchar*prefix,constchar**argv)+{+structpathspec_item*item;+constchar*entry=argv?*argv:NULL;+inti,n,prefixlen;++memset(pathspec,0,sizeof(*pathspec));++/* No arguments, no prefix -> no pathspec */+if(!entry&&!prefix)+return;++/* No arguments with prefix -> prefix pathspec */+if(!entry){+staticconstchar*raw[2];++pathspec->items=item=xmalloc(sizeof(*item));+item->match=prefix;+item->nowildcard_len=item->len=strlen(prefix);+raw[0]=prefix;+raw[1]=NULL;+pathspec->nr=1;+pathspec->raw=raw;+return;+}++n=0;+while(argv[n])+n++;++pathspec->nr=n;+pathspec->items=item=xmalloc(sizeof(*item)*n);+pathspec->raw=argv;+prefixlen=prefix?strlen(prefix):0;++for(i=0;i<n;i++){+unsignedshort_magic;+entry=argv[i];++item[i].magic=prefix_pathspec(item+i,&short_magic,+argv+i,flags,+prefix,prefixlen,entry);+if(item[i].magic&magic_mask)+unsupported_magic(entry,+item[i].magic&magic_mask,+short_magic);+if(item[i].nowildcard_len<item[i].len)+pathspec->has_wildcard=1;+pathspec->magic|=item[i].magic;+}++qsort(pathspec->items,pathspec->nr,+sizeof(structpathspec_item),pathspec_item_cmp);}/**N.B.get_pathspec()isdeprecatedinfavorofthe"struct pathspec"-*basedinterface-seepathspec_magicabove.+*basedinterface-seepathspec.c:parse_pathspec().**Arguments:*-prefix-apathrelativetotherootoftheworkingtree
@@ -221,32 +333,11 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const char*/constchar**get_pathspec(constchar*prefix,constchar**pathspec){-constchar*entry=*pathspec;-constchar**src,**dst;-intprefixlen;--if(!prefix&&!entry)-returnNULL;--if(!entry){-staticconstchar*spec[2];-spec[0]=prefix;-spec[1]=NULL;-returnspec;-}--/* Otherwise we have to re-write the entries.. */-src=pathspec;-dst=pathspec;-prefixlen=prefix?strlen(prefix):0;-while(*src){-*(dst++)=prefix_pathspec(prefix,prefixlen,*src);-src++;-}-*dst=NULL;-if(!*pathspec)-returnNULL;-returnpathspec;+structpathspecps;+parse_pathspec(&ps,+PATHSPEC_ALL_MAGIC&~PATHSPEC_FROMTOP,+0,prefix,pathspec);+returnps.raw;}voidcopy_pathspec(structpathspec*dst,conststructpathspec*src)
We have two ways of dealing with empty pathspec:
1. limit it to current prefix
2. match the entire working directory
Some commands go with #1, some #2. get_pathspec() and parse_pathspec()
only support #1. Make parse_pathspec() reject empty pathspec by
default. #1 and #2 can be specified via new flags. This makes it more
expressive about default behavior at command level.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
pathspec.c | 13 ++++++++++++-
pathspec.h | 4 ++++
2 files changed, 16 insertions(+), 1 deletion(-)
@@ -270,10 +270,20 @@ void parse_pathspec(struct pathspec *pathspec,if(!entry&&!prefix)return;+if((flags&PATHSPEC_PREFER_CWD)&&+(flags&PATHSPEC_PREFER_FULL))+die("BUG: PATHSPEC_PREFER_CWD and PATHSPEC_PREFER_FULL are incompatible");+/* No arguments with prefix -> prefix pathspec */if(!entry){staticconstchar*raw[2];+if(flags&PATHSPEC_PREFER_FULL)+return;++if(!(flags&PATHSPEC_PREFER_CWD))+die("BUG: parse_pathspec cannot take no arguments in this case");+pathspec->items=item=xmalloc(sizeof(*item));item->match=prefix;item->original=prefix;
@@ -24,6 +24,10 @@ struct pathspec {}*items;};+/* parse_pathspec flags */+#define PATHSPEC_PREFER_CWD (1<<0) /* No args means match cwd */+#define PATHSPEC_PREFER_FULL (1<<1) /* No args means match everything */+externintinit_pathspec(structpathspec*,constchar**);externvoidparse_pathspec(structpathspec*pathspec,unsignedmagic_mask,
@@ -547,10 +547,11 @@ static int do_reupdate(int ac, const char **av,*/intpos;inthas_head=1;-constchar**paths=get_pathspec(prefix,av+1);structpathspecpathspec;-init_pathspec(&pathspec,paths);+parse_pathspec(&pathspec,0,+PATHSPEC_PREFER_CWD,+prefix,av+1);if(read_ref("HEAD",head_sha1))/* If there is no HEAD, that means it is an initial
match_pathspec_depth() and tree_entry_interesting() check max_depth
field in order to support "git grep --max-depth". The feature
activation is tied to "recursive" field, which led to some unwated
activation, e.g. 5c8eeb8 (diff-index: enable recursive pathspec
matching in unpack_trees - 2012-01-15).
This patch decouples the activation from "recursive" field, puts it in
"magic" field instead. This makes sure that only "git grep" can
activate this feature. And because parse_pathspec knows when the
feature is not used, it does not need to sort pathspec (required for
max_depth to work correctly). A small win for non-grep cases.
Even though a new magic flag is introduced, no magic syntax is. The
magic can be only enabled by parse_pathspec() caller. We might someday
want to support ":(maxdepth:10)src." It all depends on actual use
cases.
max_depth feature cannot be enabled via init_pathspec() anymore. But
that's ok because init_pathspec() is on its way to /dev/null.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
builtin/grep.c | 3 ++-
diff-lib.c | 1 -
dir.c | 8 ++++++--
pathspec.c | 8 ++++++--
pathspec.h | 6 +++++-
tree-diff.c | 1 -
tree-walk.c | 8 ++++++--
7 files changed, 25 insertions(+), 10 deletions(-)
@@ -27,6 +30,7 @@ struct pathspec {/* parse_pathspec flags */#define PATHSPEC_PREFER_CWD (1<<0) /* No args means match cwd */#define PATHSPEC_PREFER_FULL (1<<1) /* No args means match everything */+#define PATHSPEC_MAXDEPTH_VALID (1<<2) /* max_depth field is valid */externintinit_pathspec(structpathspec*,constchar**);externvoidparse_pathspec(structpathspec*pathspec,
This flag is equivalent to builtin/ls-files.c:strip_trailing_slashes()
and is intended to replace that function when ls-files is converted to
use parse_pathspec.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
pathspec.c | 9 +++++++++
pathspec.h | 1 +
2 files changed, 10 insertions(+)
@@ -31,6 +31,7 @@ struct pathspec {#define PATHSPEC_PREFER_CWD (1<<0) /* No args means match cwd */#define PATHSPEC_PREFER_FULL (1<<1) /* No args means match everything */#define PATHSPEC_MAXDEPTH_VALID (1<<2) /* max_depth field is valid */+#define PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP (1<<3)externintinit_pathspec(structpathspec*,constchar**);externvoidparse_pathspec(structpathspec*pathspec,
PATHSPEC_SYMLINK_LEADING_PATH and _STRIP_SUBMODULE_SLASH_EXPENSIVE are
respectively the alternate implementation of
pathspec.c:die_if_path_beyond_symlink() and
pathspec.c:check_path_for_gitlink(). They are intended to replace
those functions when builtin/add.c and builtin/check-ignore.c are
converted to use parse_pathspec.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
pathspec.c | 26 ++++++++++++++++++++++++++
pathspec.h | 2 ++
2 files changed, 28 insertions(+)
This makes 'original' suitable for passing to an external command
because all pathspec magic is left in place, provided that the
external command understands pathspec. The prefixing is needed because
we usually launch a subcommand at worktree's top directory and the
subcommand can no longer calculate the prefix itself.
This slightly affects the original purpose of 'original'
(i.e. reporting). We should report without prefixing. So only turn
this flag on when you know you are about to pass the result straight
away to an external command.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
pathspec.c | 12 +++++++++++-
pathspec.h | 1 +
2 files changed, 12 insertions(+), 1 deletion(-)
GUARD_PATHSPEC() marks pathspec-sensitive code (basically anything in
'struct pathspec' except fields "nr" and "original"). GUARD_PATHSPEC()
is not supposed to fail. The steps for a new pathspec magic or
optimization would be:
- update parse_pathspec, add extra information to struct pathspec
- grep GUARD_PATHSPEC() and update all relevant code (or note those
that won't work with your new stuff). Update GUARD_PATHSPEC mask
accordingly.
- update parse_pathspec calls to disable new magic early at command
parsing level. Make sure parse_pathspec() catches unsupported
syntax, not until GUARD_PATHSPEC catches it.
- add tests to verify supported/unsupported commands both work as
expected.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
builtin/diff.c | 2 ++
dir.c | 2 ++
pathspec.h | 7 +++++++
tree-diff.c | 19 +++++++++++++++++++
tree-walk.c | 2 ++
5 files changed, 32 insertions(+)
@@ -198,6 +198,25 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, coconstchar*paths[1];inti;+/*+*follow-renamecodeisveryspecific,weneedexactlyone+*path.Magicthatmatchesmorethanonepathisnot+*supported.+*/+GUARD_PATHSPEC(&opt->pathspec,PATHSPEC_FROMTOP);+#if 0+/*+*Weshouldrejectwildcardsaswell.Unfortunatelywe+*haven'tgotareliablewaytodetectthat'foo\*bar'in+*facthasnowildcards.nowildcard_lenismerelyahintfor+*optimization.Letitslipfornowuntilwildmatchistaught+*aboutdry-runmodeandreturnswildcardinfo.+*/+if(opt->pathspec.has_wildcard)+die("BUG:%s:%d: wildcards are not supported",+__FILE__,__LINE__);+#endif+/* Remove the file creation entry from the diff queue, and remember it */choice=q->queue[0];q->nr=0;
@@ -256,39 +256,37 @@ static int checkout_paths(const struct checkout_opts *opts,if(opts->patch_mode)returnrun_add_interactive(revision,"--patch=checkout",-opts->pathspec);+opts->pathspec.raw);lock_file=xcalloc(1,sizeof(structlock_file));newfd=hold_locked_index(lock_file,1);-if(read_cache_preload(opts->pathspec)<0)+if(read_cache_preload(opts->pathspec.raw)<0)returnerror(_("corrupt index file"));if(opts->source_tree)-read_tree_some(opts->source_tree,opts->pathspec);+read_tree_some(opts->source_tree,opts->pathspec.raw);-for(pos=0;opts->pathspec[pos];pos++)-;-ps_matched=xcalloc(1,pos);+ps_matched=xcalloc(1,opts->pathspec.nr);for(pos=0;pos<active_nr;pos++){structcache_entry*ce=active_cache[pos];if(opts->source_tree&&!(ce->ce_flags&CE_UPDATE))continue;-match_pathspec(opts->pathspec,ce->name,ce_namelen(ce),0,ps_matched);+match_pathspec_depth(&opts->pathspec,ce->name,ce_namelen(ce),0,ps_matched);}-if(report_path_error(ps_matched,opts->pathspec,opts->prefix))+if(report_path_error(ps_matched,opts->pathspec.raw,opts->prefix))return1;/* "checkout -m path" to recreate conflicted state */if(opts->merge)-unmerge_cache(opts->pathspec);+unmerge_cache(opts->pathspec.raw);/* Any unmerged paths? */for(pos=0;pos<active_nr;pos++){structcache_entry*ce=active_cache[pos];-if(match_pathspec(opts->pathspec,ce->name,ce_namelen(ce),0,NULL)){+if(match_pathspec_depth(&opts->pathspec,ce->name,ce_namelen(ce),0,NULL)){if(!ce_stage(ce))continue;if(opts->force){
@@ -315,7 +313,7 @@ static int checkout_paths(const struct checkout_opts *opts,structcache_entry*ce=active_cache[pos];if(opts->source_tree&&!(ce->ce_flags&CE_UPDATE))continue;-if(match_pathspec(opts->pathspec,ce->name,ce_namelen(ce),0,NULL)){+if(match_pathspec_depth(&opts->pathspec,ce->name,ce_namelen(ce),0,NULL)){if(!ce_stage(ce)){errs|=checkout_entry(ce,&state,NULL);continue;
@@ -960,7 +958,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)staticintcheckout_branch(structcheckout_opts*opts,structbranch_info*new){-if(opts->pathspec)+if(opts->pathspec.nr)die(_("paths cannot be used with switching branches"));if(opts->patch_mode)
@@ -249,31 +250,30 @@ int cmd_rm(int argc, const char **argv, const char *prefix)}}-pathspec=get_pathspec(prefix,argv);-refresh_index(&the_index,REFRESH_QUIET,pathspec,NULL,NULL);+parse_pathspec(&pathspec,0,PATHSPEC_PREFER_CWD,prefix,argv);+refresh_index(&the_index,REFRESH_QUIET,pathspec.raw,NULL,NULL);seen=NULL;-for(i=0;pathspec[i];i++)-/* nothing */;-seen=xcalloc(i,1);+seen=xcalloc(pathspec.nr,1);for(i=0;i<active_nr;i++){structcache_entry*ce=active_cache[i];-if(!match_pathspec(pathspec,ce->name,ce_namelen(ce),0,seen))+if(!match_pathspec_depth(&pathspec,ce->name,ce_namelen(ce),0,seen))continue;ALLOC_GROW(list.entry,list.nr+1,list.alloc);list.entry[list.nr].name=ce->name;list.entry[list.nr++].is_submodule=S_ISGITLINK(ce->ce_mode);}-if(pathspec){-constchar*match;+if(pathspec.nr){+constchar*original;intseen_any=0;-for(i=0;(match=pathspec[i])!=NULL;i++){+for(i=0;i<pathspec.nr;i++){+original=pathspec.items[i].original;if(!seen[i]){if(!ignore_unmatch){die(_("pathspec '%s' did not match any files"),-match);+original);}}else{
@@ -281,7 +281,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)}if(!recursive&&seen[i]==MATCHED_RECURSIVELY)die(_("not removing '%s' recursively without -r"),-*match?match:".");+*original?original:".");}if(!seen_any)
@@ -189,7 +189,7 @@ static void show_ru_info(void)len=strlen(path);if(len<max_prefix_len)continue;/* outside of the prefix */-if(!match_pathspec(pathspec,path,len,max_prefix_len,ps_matched))+if(!match_pathspec_depth(&pathspec,path,len,max_prefix_len,ps_matched))continue;/* uninterested */for(i=0;i<3;i++){if(!ui->mode[i])
@@ -218,7 +218,7 @@ static void show_files(struct dir_struct *dir)/* For cached/deleted files we don't need to even do the readdir */if(show_others||show_killed){-fill_directory(dir,pathspec);+fill_directory(dir,pathspec.raw);if(show_others)show_other_files(dir);if(show_killed)
@@ -557,23 +542,18 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)if(require_work_tree&&!is_inside_work_tree())setup_work_tree();-pathspec=get_pathspec(prefix,argv);--/* be nice with submodule paths ending in a slash */-if(pathspec)-strip_trailing_slash_from_submodules();+parse_pathspec(&pathspec,0,+PATHSPEC_PREFER_CWD|+PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,+prefix,argv);/* Find common prefix for all pathspec's */-max_prefix=common_prefix(pathspec);+max_prefix=common_prefix(pathspec.raw);max_prefix_len=max_prefix?strlen(max_prefix):0;/* Treat unmatching pathspec elements as errors */-if(pathspec&&error_unmatch){-intnum;-for(num=0;pathspec[num];num++)-;-ps_matched=xcalloc(1,num);-}+if(pathspec.nr&&error_unmatch)+ps_matched=xcalloc(1,pathspec.nr);if((dir.flags&DIR_SHOW_IGNORED)&&!exc_given)die("ls-files --ignored needs some exclude pattern");
@@ -600,7 +580,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)if(ps_matched){intbad;-bad=report_path_error(ps_matched,pathspec,prefix);+bad=report_path_error(ps_matched,pathspec.raw,prefix);if(bad)fprintf(stderr,"Did you forget to 'git add'?\n");
@@ -152,7 +151,6 @@ int write_archive_entries(struct archiver_args *args,structarchiver_contextcontext;structunpack_trees_optionsopts;structtree_desct;-structpathspecpathspec;interr;if(args->baselen>0&&args->base[args->baselen-1]=='/'){
@@ -187,10 +185,8 @@ int write_archive_entries(struct archiver_args *args,git_attr_set_direction(GIT_ATTR_INDEX,&the_index);}-init_pathspec(&pathspec,args->pathspec);-err=read_tree_recursive(args->tree,"",0,0,&pathspec,+err=read_tree_recursive(args->tree,"",0,0,&args->pathspec,write_archive_entry,&context);-free_pathspec(&pathspec);if(err==READ_TREE_RECURSIVE)err=0;returnerr;
@@ -223,7 +219,7 @@ static int path_exists(struct tree *tree, const char *path)structpathspecpathspec;intret;-init_pathspec(&pathspec,paths);+parse_pathspec(&pathspec,0,0,"",paths);ret=read_tree_recursive(tree,"",0,0,&pathspec,reject_entry,NULL);free_pathspec(&pathspec);returnret!=0;
@@ -232,11 +228,18 @@ static int path_exists(struct tree *tree, const char *path)staticvoidparse_pathspec_arg(constchar**pathspec,structarchiver_args*ar_args){-ar_args->pathspec=pathspec=get_pathspec("",pathspec);+/*+*mustbeconsistentwithparse_pathspecinpath_exists()+*Alsoifpathspecpatternsaredependent,we'reinbig+*troubleaswetesteachoneseparately+*/+parse_pathspec(&ar_args->pathspec,0,+PATHSPEC_PREFER_FULL,+"",pathspec);if(pathspec){while(*pathspec){if(!path_exists(ar_args->tree,*pathspec))-die("path not found: %s",*pathspec);+die(_("pathspec '%s' did not match any files"),*pathspec);pathspec++;}}
check-ignore (at least the test suite) seems to rely on the pattern
order. PATHSPEC_KEEP_ORDER is introduced to explictly express this.
The lack of PATHSPEC_MAXDEPTH_VALID is sufficient because it's the
only flag that reorders pathspecs, but it's less obvious that way.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
builtin/check-ignore.c | 34 +++++++++++++++++++++-------------
pathspec.c | 6 +++++-
pathspec.h | 1 +
t/t0008-ignores.sh | 8 ++++----
4 files changed, 31 insertions(+), 18 deletions(-)
@@ -53,14 +53,14 @@ static void output_exclude(const char *path, struct exclude *exclude)}}-staticintcheck_ignore(constchar*prefix,constchar**pathspec)+staticintcheck_ignore(intargc,constchar**argv,constchar*prefix){structdir_structdir;-constchar*path,*full_path;char*seen;intnum_ignored=0,dtype=DT_UNKNOWN,i;structpath_exclude_checkcheck;structexclude*exclude;+structpathspecpathspec;/* read_cache() is only necessary so we can watch out for submodules. */if(read_cache()<0)
@@ -397,7 +397,7 @@ test_expect_success_multi SYMLINKS 'symlink' '' ' test_expect_success_multiSYMLINKS'beyond a symlink''''test_check_ignore"a/symlink/foo"128&&-test_stderr"fatal: '\''a/symlink/foo'\'' is beyond a symbolic link"+test_stderr"fatal: pathspec '\''a/symlink/foo'\'' is beyond a symbolic link"' test_expect_success_multiSYMLINKS'beyond a symlink from subdirectory''''
@@ -405,7 +405,7 @@ test_expect_success_multi SYMLINKS 'beyond a symlink from subdirectory' '' 'cda&&test_check_ignore"symlink/foo"128)&&-test_stderr"fatal: '\''symlink/foo'\'' is beyond a symbolic link"+test_stderr"fatal: pathspec '\''symlink/foo'\'' is beyond a symbolic link"'############################################################################
@@ -414,7 +414,7 @@ test_expect_success_multi SYMLINKS 'beyond a symlink from subdirectory' '' ' test_expect_success_multi'submodule''''test_check_ignore"a/submodule/one"128&&-test_stderr"fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"+test_stderr"fatal: Pathspec '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"' test_expect_success_multi'submodule from subdirectory''''
@@ -422,7 +422,7 @@ test_expect_success_multi 'submodule from subdirectory' '' 'cda&&test_check_ignore"submodule/one"128)&&-test_stderr"fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"+test_stderr"fatal: Pathspec '\''submodule/one'\'' is in submodule '\''a/submodule'\''"'############################################################################
@@ -243,7 +249,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)intpatch_mode=0,unborn;constchar*rev;unsignedcharsha1[20];-constchar**pathspec=NULL;+structpathspecpathspec;conststructoptionoptions[]={OPT__QUIET(&quiet,N_("be quiet, only report errors")),OPT_SET_INT(0,"mixed",&reset_type,
@@ -263,13 +269,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix)argc=parse_options(argc,argv,prefix,options,git_reset_usage,PARSE_OPT_KEEP_DASHDASH);-pathspec=parse_args(argv,prefix,&rev);+parse_args(&pathspec,argv,prefix,patch_mode,&rev);unborn=!strcmp(rev,"HEAD")&&get_sha1("HEAD",sha1);if(unborn){/* reset on unborn branch: treat as reset to empty tree */hashcpy(sha1,EMPTY_TREE_SHA1_BIN);-}elseif(!pathspec){+}elseif(!pathspec.nr){structcommit*commit;if(get_sha1_committish(rev,sha1))die(_("Failed to resolve '%s' as a valid revision."),rev);
@@ -290,13 +296,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix)if(patch_mode){if(reset_type!=NONE)die(_("--patch is incompatible with --{hard,mixed,soft}"));-returnrun_add_interactive(sha1_to_hex(sha1),"--patch=reset",pathspec);+returnrun_add_interactive(sha1_to_hex(sha1),"--patch=reset",pathspec.raw);}/* git reset tree [--] paths... can be used to*loadchosenpathsfromthetreeintotheindexwithout*affectingtheworkingtreenorHEAD.*/-if(pathspec){+if(pathspec.nr){if(reset_type==MIXED)warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."));elseif(reset_type!=NONE)
@@ -344,7 +350,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)die(_("Could not write new index file."));}-if(!pathspec&&!unborn){+if(!pathspec.nr&&!unborn){/* Any resets without paths update HEAD to the head being*switchedto,savingthepreviousheadinORIG_HEADbefore.*/update_ref_status=update_refs(rev,sha1);
@@ -415,11 +387,19 @@ int cmd_add(int argc, const char **argv, const char *prefix)fprintf(stderr,_("Maybe you wanted to say 'git add .'?\n"));return0;}-pathspec=validate_pathspec(argv,prefix);if(read_cache()<0)die(_("index file corrupt"));-treat_gitlinks(pathspec);++/*+*Checkthe"pathspec '%s' did not match any files"block+*belowbeforeenablingnewmagic.+*/+parse_pathspec(&pathspec,0,+PATHSPEC_PREFER_FULL|+PATHSPEC_SYMLINK_LEADING_PATH|+PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE,+prefix,argv);if(add_new_files){intbaselen;
@@ -432,33 +412,39 @@ int cmd_add(int argc, const char **argv, const char *prefix)}/* This picks up the paths that are not tracked */-baselen=fill_directory(&dir,pathspec);-if(pathspec)-seen=prune_directory(&dir,pathspec,baselen);+baselen=fill_directory(&dir,pathspec.raw);+if(pathspec.nr)+seen=prune_directory(&dir,pathspec.raw,baselen);}if(refresh_only){-refresh(verbose,pathspec);+refresh(verbose,pathspec.raw);gotofinish;}-if(pathspec){+if(pathspec.nr){inti;structpath_exclude_checkcheck;path_exclude_check_init(&check,&dir);if(!seen)-seen=find_pathspecs_matching_against_index(pathspec);-for(i=0;pathspec[i];i++){-if(!seen[i]&&pathspec[i][0]-&&!file_exists(pathspec[i])){+seen=find_pathspecs_matching_against_index(pathspec.raw);++/*+*file_exists()assumesexactmatch+*/+GUARD_PATHSPEC(&pathspec,PATHSPEC_FROMTOP);++for(i=0;pathspec.raw[i];i++){+if(!seen[i]&&pathspec.raw[i][0]+&&!file_exists(pathspec.raw[i])){if(ignore_missing){intdtype=DT_UNKNOWN;-if(is_path_excluded(&check,pathspec[i],-1,&dtype))-dir_add_ignored(&dir,pathspec[i],strlen(pathspec[i]));+if(is_path_excluded(&check,pathspec.raw[i],-1,&dtype))+dir_add_ignored(&dir,pathspec.raw[i],strlen(pathspec.raw[i]));}elsedie(_("pathspec '%s' did not match any files"),-pathspec[i]);+pathspec.raw[i]);}}free(seen);
@@ -58,49 +58,6 @@ char *find_pathspecs_matching_against_index(const char **pathspec)}/*-*Checktheindextoseewhetherpathreferstoasubmodule,or-*somethinginsideasubmodule.Iftheformer,returnsthepathwith-*anytrailingslashstripped.Ifthelatter,dieswithanerror-*message.-*/-constchar*check_path_for_gitlink(constchar*path)-{-inti,path_len=strlen(path);-for(i=0;i<active_nr;i++){-structcache_entry*ce=active_cache[i];-if(S_ISGITLINK(ce->ce_mode)){-intce_len=ce_namelen(ce);-if(path_len<=ce_len||path[ce_len]!='/'||-memcmp(ce->name,path,ce_len))-/* path does not refer to this-*submoduleoranythinginsideit*/-continue;-if(path_len==ce_len+1){-/* path refers to submodule;-*striptrailingslash*/-returnxstrndup(ce->name,ce_len);-}else{-die(_("Path '%s' is in submodule '%.*s'"),-path,ce_len,ce->name);-}-}-}-returnpath;-}--/*-*Diesifthegivenpathreferstoafileinsideasymlinked-*directoryintheindex.-*/-voiddie_if_path_beyond_symlink(constchar*path,constchar*prefix)-{-if(has_symlink_leading_path(path,strlen(path))){-intlen=prefix?strlen(prefix):0;-die(_("'%s' is beyond a symbolic link"),path+len);-}-}--/**Magicpathspec**Possiblefuturemagicsemanticsincludestufflike:
@@ -436,7 +436,7 @@ extern int init_db(const char *template_dir, unsigned int flags);/* Initialize and use the cache information */externintread_index(structindex_state*);-externintread_index_preload(structindex_state*,constchar**pathspec);+externintread_index_preload(structindex_state*,conststructpathspec*pathspec);externintread_index_from(structindex_state*,constchar*path);externintis_index_unborn(structindex_state*);externintread_index_unmerged(structindex_state*);
This passes the pathspec, more or less unmodified, to
git-add--interactive. The command itself does not process pathspec. It
simply passes the pathspec to other builtin commands. So if all those
commands support pathspec, we're good.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
builtin/add.c | 26 ++++++++++----------------
builtin/checkout.c | 9 ++++-----
builtin/reset.c | 8 ++++----
commit.h | 2 +-
4 files changed, 19 insertions(+), 26 deletions(-)
@@ -156,11 +152,9 @@ int run_add_interactive(const char *revision, const char *patch_mode,if(revision)args[ac++]=revision;args[ac++]="--";-if(pc){-memcpy(&(args[ac]),pathspec,sizeof(constchar*)*pc);-ac+=pc;-}-args[ac]=NULL;+for(i=0;i<pathspec->nr;i++)+/* pass original pathspec, to be re-parsed */+args[ac++]=pathspec->items[i].original;status=run_command_v_opt(args,RUN_GIT_CMD);free(args);
@@ -175,17 +169,17 @@ int interactive_add(int argc, const char **argv, const char *prefix, int patch)*git-add--interactiveitselfdoesnotparsepathspec.It*simplypassesthepathspectootherbuiltincommands.Let's*hopeallofthemsupportallmagic,orwe'llneedtolimit-*themagichere.Thereisstillaproblemwithprefix.But-*that'llbeworkedonlateron.+*themagichere.*/parse_pathspec(&pathspec,PATHSPEC_ALL_MAGIC&~PATHSPEC_FROMTOP,PATHSPEC_PREFER_FULL|-PATHSPEC_SYMLINK_LEADING_PATH,+PATHSPEC_SYMLINK_LEADING_PATH|+PATHSPEC_PREFIX_ORIGIN,prefix,argv);returnrun_add_interactive(NULL,patch?"--patch":NULL,-pathspec.raw);+&pathspec);}staticintedit_patch(intargc,constchar**argv,constchar*prefix)
@@ -296,7 +296,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)if(patch_mode){if(reset_type!=NONE)die(_("--patch is incompatible with --{hard,mixed,soft}"));-returnrun_add_interactive(sha1_to_hex(sha1),"--patch=reset",pathspec.raw);+returnrun_add_interactive(sha1_to_hex(sha1),"--patch=reset",&pathspec);}/* git reset tree [--] paths... can be used to
@@ -156,7 +156,7 @@ int unmerge_index_entry_at(struct index_state *istate, int pos)returnunmerge_index_entry_at(istate,pos);}-voidunmerge_index(structindex_state*istate,constchar**pathspec)+voidunmerge_index(structindex_state*istate,conststructpathspec*pathspec){inti;
@@ -82,12 +82,9 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen,return0;}-staticintread_tree_some(structtree*tree,constchar**pathspec)+staticintread_tree_some(structtree*tree,conststructpathspec*pathspec){-structpathspecps;-init_pathspec(&ps,pathspec);-read_tree_recursive(tree,"",0,0,&ps,update_some,NULL);-free_pathspec(&ps);+read_tree_recursive(tree,"",0,0,pathspec,update_some,NULL);/* update the index with the given tree's info*forallargs,expandingwildcards,andexit
@@ -265,7 +262,7 @@ static int checkout_paths(const struct checkout_opts *opts,returnerror(_("corrupt index file"));if(opts->source_tree)-read_tree_some(opts->source_tree,opts->pathspec.raw);+read_tree_some(opts->source_tree,&opts->pathspec);ps_matched=xcalloc(1,opts->pathspec.nr);
@@ -382,9 +386,8 @@ 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);error("pathspec '%s' did not match any file(s) known to git.",-name);+pathspec->items[num].original);errors++;}strbuf_release(&sb);
@@ -580,7 +583,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)if(ps_matched){intbad;-bad=report_path_error(ps_matched,pathspec.raw,prefix);+bad=report_path_error(ps_matched,&pathspec,prefix);if(bad)fprintf(stderr,"Did you forget to 'git add'?\n");
@@ -121,19 +121,18 @@ static char *prune_directory(struct dir_struct *dir, const char **pathspec, intreturnseen;}-staticvoidrefresh(intverbose,constchar**pathspec)+staticvoidrefresh(intverbose,conststructpathspec*pathspec){char*seen;-inti,specs;+inti;-for(specs=0;pathspec[specs];specs++)-/* nothing */;-seen=xcalloc(specs,1);+seen=xcalloc(pathspec->nr,1);refresh_index(&the_index,verbose?REFRESH_IN_PORCELAIN:REFRESH_QUIET,pathspec,seen,_("Unstaged changes after refreshing the index:"));-for(i=0;i<specs;i++){+for(i=0;i<pathspec->nr;i++){if(!seen[i])-die(_("pathspec '%s' did not match any files"),pathspec[i]);+die(_("pathspec '%s' did not match any files"),+pathspec->items[i].match);}free(seen);}
@@ -405,7 +405,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)}/* This picks up the paths that are not tracked */-baselen=fill_directory(&dir,pathspec.raw);+baselen=fill_directory(&dir,&pathspec);if(pathspec.nr)seen=prune_directory(&dir,pathspec.raw,baselen);}
@@ -218,7 +218,7 @@ static void show_files(struct dir_struct *dir)/* For cached/deleted files we don't need to even do the readdir */if(show_others||show_killed){-fill_directory(dir,pathspec.raw);+fill_directory(dir,&pathspec);if(show_others)show_other_files(dir);if(show_killed)
@@ -107,10 +107,10 @@ int fill_directory(struct dir_struct *dir, const char **pathspec)*Calculatecommonprefixforthepathspec,and*usethattooptimizethedirectorywalk*/-len=common_prefix_len(pathspec);+len=common_prefix_len(pathspec->raw);/* Read the directory and prune it */-read_directory(dir,pathspec?*pathspec:"",len,pathspec);+read_directory(dir,pathspec->nr?pathspec->raw[0]:"",len,pathspec);returnlen;}
@@ -1438,14 +1438,20 @@ static int treat_leading_path(struct dir_struct *dir,returnrc;}-intread_directory(structdir_struct*dir,constchar*path,intlen,constchar**pathspec)+intread_directory(structdir_struct*dir,constchar*path,intlen,conststructpathspec*pathspec){structpath_simplify*simplify;+/*+*Checkoutcreate_simplify()+*/+if(pathspec)+GUARD_PATHSPEC(pathspec,PATHSPEC_FROMTOP|PATHSPEC_MAXDEPTH);+if(has_symlink_leading_path(path,len))returndir->nr;-simplify=create_simplify(pathspec);+simplify=create_simplify(pathspec?pathspec->raw:NULL);if(!len||treat_leading_path(dir,path,len,simplify))read_directory_recursive(dir,path,len,0,simplify);free_simplify(simplify);
@@ -551,7 +551,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)prefix,argv);/* Find common prefix for all pathspec's */-max_prefix=common_prefix(pathspec.raw);+max_prefix=common_prefix(&pathspec);max_prefix_len=max_prefix?strlen(max_prefix):0;/* Treat unmatching pathspec elements as errors */
@@ -107,7 +106,7 @@ int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec)*Calculatecommonprefixforthepathspec,and*usethattooptimizethedirectorywalk*/-len=common_prefix_len(pathspec->raw);+len=common_prefix_len(pathspec);/* Read the directory and prune it */read_directory(dir,pathspec->nr?pathspec->raw[0]:"",len,pathspec);
@@ -1073,7 +1073,7 @@ static int find_copy_in_parent(struct scoreboard *sb,diff_opts.output_format=DIFF_FORMAT_NO_OUTPUT;paths[0]=NULL;-diff_tree_setup_paths(paths,&diff_opts);+init_pathspec(&diff_opts.pathspec,paths);diff_setup_done(&diff_opts);/* Try "find copies harder" on new path if requested;
@@ -1156,7 +1156,7 @@ static int find_copy_in_parent(struct scoreboard *sb,}reset_scanned_flag(sb);diff_flush(&diff_opts);-diff_tree_release_paths(&diff_opts);+free_pathspec(&diff_opts.pathspec);returnretval;}
@@ -229,11 +229,11 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, codiff_opts.break_opt=opt->break_opt;diff_opts.rename_score=opt->rename_score;paths[0]=NULL;-diff_tree_setup_paths(paths,&diff_opts);+init_pathspec(&diff_opts.pathspec,paths);diff_setup_done(&diff_opts);diff_tree(t1,t2,base,&diff_opts);diffcore_std(&diff_opts);-diff_tree_release_paths(&diff_opts);+free_pathspec(&diff_opts.pathspec);/* Go through the new set of filepairing, and see if we find a more interesting one */opt->found_follow=0;
@@ -252,9 +252,9 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, cochoice=p;/* Update the path we use from now on.. */-diff_tree_release_paths(opt);+free_pathspec(&opt->pathspec);opt->pathspec.raw[0]=xstrdup(p->one->path);-diff_tree_setup_paths(opt->pathspec.raw,opt);+init_pathspec(&opt->pathspec,opt->pathspec.raw);/**Thecallerexpectsustoreturnasetofvanilla
@@ -1058,7 +1055,6 @@ static int find_copy_in_parent(struct scoreboard *sb,intopt){structdiff_optionsdiff_opts;-constchar*paths[1];inti,j;intretval;structblame_list*blame_list;
@@ -1072,8 +1068,6 @@ static int find_copy_in_parent(struct scoreboard *sb,DIFF_OPT_SET(&diff_opts,RECURSIVE);diff_opts.output_format=DIFF_FORMAT_NO_OUTPUT;-paths[0]=NULL;-init_pathspec(&diff_opts.pathspec,paths);diff_setup_done(&diff_opts);/* Try "find copies harder" on new path if requested;
@@ -247,14 +244,17 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co*/if((p->status=='R'||p->status=='C')&&!strcmp(p->two->path,opt->pathspec.raw[0])){+constchar*path[2];+/* Switch the file-pairs around */q->queue[i]=choice;choice=p;/* Update the path we use from now on.. */+path[0]=p->one->path;+path[1]=NULL;free_pathspec(&opt->pathspec);-opt->pathspec.raw[0]=xstrdup(p->one->path);-init_pathspec(&opt->pathspec,opt->pathspec.raw);+parse_pathspec(&opt->pathspec,PATHSPEC_ALL_MAGIC,0,"",path);/**Thecallerexpectsustoreturnasetofvanilla
@@ -100,26 +100,24 @@ int add_files_to_cache(const char *prefix,return!!data.add_errors;}-staticchar*prune_directory(structdir_struct*dir,constchar**pathspec,intprefix)+staticchar*prune_directory(structdir_struct*dir,structpathspec*pathspec,intprefix){char*seen;-inti,specs;+inti;structdir_entry**src,**dst;-for(specs=0;pathspec[specs];specs++)-/* nothing */;-seen=xcalloc(specs,1);+seen=xcalloc(pathspec->nr,1);src=dst=dir->entries;i=dir->nr;while(--i>=0){structdir_entry*entry=*src++;-if(match_pathspec(pathspec,entry->name,entry->len,-prefix,seen))+if(match_pathspec_depth(pathspec,entry->name,entry->len,+prefix,seen))*dst++=entry;}dir->nr=dst-dir->entries;-add_pathspec_matches_against_index(pathspec,seen,specs);+add_pathspec_matches_against_index(pathspec,seen);returnseen;}
@@ -409,7 +407,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)/* This picks up the paths that are not tracked */baselen=fill_directory(&dir,&pathspec);if(pathspec.nr)-seen=prune_directory(&dir,pathspec.raw,baselen);+seen=prune_directory(&dir,&pathspec,baselen);}if(refresh_only){
@@ -423,23 +421,23 @@ int cmd_add(int argc, const char **argv, const char *prefix)path_exclude_check_init(&check,&dir);if(!seen)-seen=find_pathspecs_matching_against_index(pathspec.raw);+seen=find_pathspecs_matching_against_index(&pathspec);/**file_exists()assumesexactmatch*/GUARD_PATHSPEC(&pathspec,PATHSPEC_FROMTOP);-for(i=0;pathspec.raw[i];i++){-if(!seen[i]&&pathspec.raw[i][0]-&&!file_exists(pathspec.raw[i])){+for(i=0;i<pathspec.nr;i++){+constchar*path=pathspec.items[i].match;+if(!seen[i]&&!file_exists(path)){if(ignore_missing){intdtype=DT_UNKNOWN;-if(is_path_excluded(&check,pathspec.raw[i],-1,&dtype))-dir_add_ignored(&dir,pathspec.raw[i],strlen(pathspec.raw[i]));+if(is_path_excluded(&check,path,-1,&dtype))+dir_add_ignored(&dir,path,pathspec.items[i].len);}elsedie(_("pathspec '%s' did not match any files"),-pathspec.raw[i]);+pathspec.items[i].original);}}free(seen);
@@ -140,113 +140,6 @@ int within_depth(const char *name, int namelen,**Itreturns0whenthereisnomatch.*/-staticintmatch_one(constchar*match,constchar*name,intnamelen)-{-intmatchlen;-intliteral=limit_pathspec_to_literal();--/* If the match was just the prefix, we matched */-if(!*match)-returnMATCHED_RECURSIVELY;--if(ignore_case){-for(;;){-unsignedcharc1=tolower(*match);-unsignedcharc2=tolower(*name);-if(c1=='\0'||(!literal&&is_glob_special(c1)))-break;-if(c1!=c2)-return0;-match++;-name++;-namelen--;-}-}else{-for(;;){-unsignedcharc1=*match;-unsignedcharc2=*name;-if(c1=='\0'||(!literal&&is_glob_special(c1)))-break;-if(c1!=c2)-return0;-match++;-name++;-namelen--;-}-}--/*-*Ifwedon'tmatchthematchstringexactly,-*weneedtomatchbyfnmatch-*/-matchlen=strlen(match);-if(strncmp_icase(match,name,matchlen)){-if(literal)-return0;-return!fnmatch_icase(match,name,0)?MATCHED_FNMATCH:0;-}--if(namelen==matchlen)-returnMATCHED_EXACTLY;-if(match[matchlen-1]=='/'||name[matchlen]=='/')-returnMATCHED_RECURSIVELY;-return0;-}--/*-*Givenanameandalistofpathspecs,returnsthenatureofthe-*closest(i.e.mostspecific)matchofthenametoanyofthe-*pathspecs.-*-*Thecallertypicallycallsthismultipletimeswiththesame-*pathspecandseen[]arraybutwithdifferentname/namelen-*(e.g.entriesfromtheindex)andisinterestedinseeingifand-*howeachpathspecmatchesallthenamesitcallsthisfunction-*with.Amarkisleftintheseen[]arrayforeachpathspecelement-*indicatingtheclosesttypeofmatchthatelementachieved,soif-*seen[n]remainszeroaftermultipleinvocations,thatmeansthenth-*pathspecdidnotmatchanynames,whichcouldindicatethatthe-*usermistypedthenthpathspec.-*/-intmatch_pathspec(constchar**pathspec,constchar*name,intnamelen,-intprefix,char*seen)-{-inti,retval=0;--if(!pathspec)-return1;--name+=prefix;-namelen-=prefix;--for(i=0;pathspec[i]!=NULL;i++){-inthow;-constchar*match=pathspec[i]+prefix;-if(seen&&seen[i]==MATCHED_EXACTLY)-continue;-how=match_one(match,name,namelen);-if(how){-if(retval<how)-retval=how;-if(seen&&seen[i]<how)-seen[i]=how;-}-}-returnretval;-}--/*-*Does'match'matchthegivenname?-*Amatchisfoundif-*-*(1)the'match'stringisleadingdirectoryof'name',or-*(2)the'match'stringisawildcardandmatches'name',or-*(3)the'match'stringisexactlythesameas'name'.-*-*andthereturnvaluetellswhichcaseitwas.-*-*Itreturns0whenthereisnomatch.-*/staticintmatch_pathspec_item(conststructpathspec_item*item,intprefix,constchar*name,intnamelen){
Prepending prefix to pathspec is a trick to workaround the fact that
commands can be executed in a subdirectory, but all git commands run
at worktree's root. The prefix part should always be treated as
literal string. Make it so.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
cache.h | 2 ++
path.c | 15 ++++++++++++++-
pathspec.c | 16 ++++++++++++----
pathspec.h | 2 +-
setup.c | 9 +++++----
5 files changed, 34 insertions(+), 10 deletions(-)
The prefix length is passed from one command to another via the new
magic 'prefix'. The magic is for parse_pathspec's internal use only,
not visible to parse_pathspec's callers.
Prefix length is not preserved across commands when --literal-pathspecs
is specified (no magic is allowed, including 'prefix'). That's OK
because we all paths are literal. No magic, no special treatment
regarding prefix. (This may be no longer true if we make :(glob)
default)
Other options to preserve the prefix include saving it to env variable
or quoting. Env var way (at least _one_ env var) is not suitable
because the prefix is not the same for all pathspecs. Pathspecs
starting with "../" will eat into the prefix part.
We could also preserve 'prefix' across commands is quote the prefix
part, then dequote on receiving. But it may not be 100% accurate, we
may dequote longer than the original prefix part, for example. That
may be good or not, but it's not the purpose.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
pathspec.c | 41 ++++++++++++++++++++++++++++++++++++-----
1 file changed, 36 insertions(+), 5 deletions(-)
@@ -92,9 +92,9 @@ static unsigned prefix_pathspec(struct pathspec_item *item,constchar*elt){unsignedmagic=0,short_magic=0;-constchar*copyfrom=elt;+constchar*copyfrom=elt,*long_magic_end=NULL;char*match;-inti;+inti,pathspec_prefix=-1;if(elt[0]!=':'){;/* nothing to do */
@@ -111,18 +111,29 @@ static unsigned prefix_pathspec(struct pathspec_item *item,nextat=copyfrom+len+1;if(!len)continue;-for(i=0;i<ARRAY_SIZE(pathspec_magic);i++)+for(i=0;i<ARRAY_SIZE(pathspec_magic);i++){if(strlen(pathspec_magic[i].name)==len&&!strncmp(pathspec_magic[i].name,copyfrom,len)){magic|=pathspec_magic[i].bit;break;}+if(!prefixcmp(copyfrom,"prefix:")){+char*endptr;+pathspec_prefix=strtol(copyfrom+7,+&endptr,10);+if(endptr-copyfrom!=len)+die("invalid parameter for pathspec magic 'prefix'");+/* "i" would be wrong, but it does not matter */+break;+}+}if(ARRAY_SIZE(pathspec_magic)<=i)die(_("Invalid pathspec magic '%.*s' in '%s'"),(int)len,copyfrom,elt);}if(*copyfrom!=')')die(_("Missing ')' at the end of pathspec magic in '%s'"),elt);+long_magic_end=copyfrom;copyfrom++;}else{/* shorthand */
@@ -149,7 +160,14 @@ static unsigned prefix_pathspec(struct pathspec_item *item,magic|=short_magic;*p_short_magic=short_magic;-if(magic&PATHSPEC_FROMTOP){+if(pathspec_prefix>=0&&+(prefixlen||(prefix&&*prefix)))+die("BUG: 'prefix' magic is supposed to be used at worktree's root");++if(pathspec_prefix>=0){+match=xstrdup(copyfrom);+prefixlen=pathspec_prefix;+}elseif(magic&PATHSPEC_FROMTOP){match=xstrdup(copyfrom);prefixlen=0;}else{
@@ -164,7 +182,20 @@ static unsigned prefix_pathspec(struct pathspec_item *item,*/if(flags&PATHSPEC_PREFIX_ORIGIN){structstrbufsb=STRBUF_INIT;-strbuf_add(&sb,elt,copyfrom-elt);+constchar*start=elt;+if(prefixlen&&!limit_pathspec_to_literal()){+/* Preserve the actual prefix length of each pattern */+if(long_magic_end){+strbuf_add(&sb,start,long_magic_end-start);+strbuf_addf(&sb,",prefix:%d",prefixlen);+start=long_magic_end;+}else{+if(*start==':')+start++;+strbuf_addf(&sb,":(prefix:%d)",prefixlen);+}+}+strbuf_add(&sb,start,copyfrom-start);strbuf_addstr(&sb,match);item->original=strbuf_detach(&sb,NULL);}else
@@ -91,11 +91,15 @@ static unsigned prefix_pathspec(struct pathspec_item *item,constchar*prefix,intprefixlen,constchar*elt){+staticintliteral_global=-1;unsignedmagic=0,short_magic=0;constchar*copyfrom=elt,*long_magic_end=NULL;char*match;inti,pathspec_prefix=-1;+if(literal_global<0)+literal_global=git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT,0);+if(elt[0]!=':'){;/* nothing to do */}elseif(elt[1]=='('){
@@ -183,7 +187,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item,if(flags&PATHSPEC_PREFIX_ORIGIN){structstrbufsb=STRBUF_INIT;constchar*start=elt;-if(prefixlen&&!limit_pathspec_to_literal()){+if(prefixlen&&!literal_global){/* Preserve the actual prefix length of each pattern */if(long_magic_end){strbuf_add(&sb,start,long_magic_end-start);
@@ -323,6 +323,10 @@ top `/`;; The magic word `top` (mnemonic: `/`) makes the pattern match from the root of the working tree, even when you are running the command from inside a subdirectory.++literal;;+ Wildcards in the pattern such as `*` or `?` are treated+ as literal characters. -- + Currently only the slash `/` is recognized as the "magic signature",
--literal-pathspecs and its equivalent environment variable are
probably used for scripting. In that setting, pathspec magic may be
unwanted. Disabling globbing in individual pathspec can be done via
:(literal) magic.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
Documentation/git.txt | 4 ++--
pathspec.c | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
@@ -439,8 +439,8 @@ help ...`. linkgit:git-replace[1] for more information. --literal-pathspecs::- Treat pathspecs literally, rather than as glob patterns. This is- equivalent to setting the `GIT_LITERAL_PATHSPECS` environment+ Treat pathspecs literally (i.e. no globbing, no pathspec magic).+ This is equivalent to setting the `GIT_LITERAL_PATHSPECS` environment variable to `1`.
:(glob)path differs from plain pathspec that it uses wildmatch with
WM_PATHNAME while the other uses fnmatch without FNM_PATHNAME. The
difference lies in how '*' (and '**') is processed.
With the introduction of :(glob) and :(literal) and their global
options --[no]glob-pathspecs, the user can:
- make everything literal by default via --noglob-pathspecs
--literal-pathspecs cannot be used for this purpose as it
disables _all_ pathspec magic.
- individually turn on globbing with :(glob)
- make everything globbing by default via --glob-pathspecs
- individually turn off globbing with :(literal)
The implication behind this is, there is no way to gain the default
matching behavior (i.e. fnmatch without FNM_PATHNAME). You either get
new globbing or literal. The old fnmatch behavior is considered
deprecated and discouraged to use.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
Documentation/git.txt | 19 +++++++++++++++++
Documentation/glossary-content.txt | 29 +++++++++++++++++++++++++
builtin/add.c | 9 ++++++--
builtin/ls-tree.c | 2 +-
cache.h | 2 ++
dir.c | 28 ++++++++++++++-----------
dir.h | 9 ++++----
git.c | 8 +++++++
pathspec.c | 43 +++++++++++++++++++++++++++++++++-----
pathspec.h | 4 +++-
tree-walk.c | 9 ++++----
11 files changed, 131 insertions(+), 31 deletions(-)
@@ -443,6 +443,17 @@ help ...`. This is equivalent to setting the `GIT_LITERAL_PATHSPECS` environment variable to `1`.+--glob-pathspecs:+ Add "glob" magic to all pathspec. This is equivalent to setting+ the `GIT_GLOB_PATHSPECS` environment variable to `1`. Disabling+ globbing on individual pathspecs can be done using pathspec+ magic ":(literal)"++--noglob-pathspecs:+ Add "literal" magic to all pathspec. This is equivalent to setting+ the `GIT_NOGLOB_PATHSPECS` environment variable to `1`. Enabling+ globbing on individual pathspecs can be done using pathspec+ magic ":(glob)" GIT COMMANDS ------------
@@ -834,6 +845,14 @@ GIT_LITERAL_PATHSPECS:: literal paths to Git (e.g., paths previously given to you by `git ls-tree`, `--raw` diff output, etc).+GIT_GLOB_PATHSPECS::+ Setting this variable to `1` will cause git to treat all+ pathspecs as glob patterns (aka "glob" magic).++GIT_NOGLOB_PATHSPECS::+ Setting this variable to `1` will cause git to treat all+ pathspecs as literal (aka "literal" magic).+ Discussion[[Discussion]] ------------------------
@@ -327,6 +327,35 @@ top `/`;; literal;; Wildcards in the pattern such as `*` or `?` are treated as literal characters.++glob;;+ Git treats the pattern as a shell glob suitable for+ consumption by fnmatch(3) with the FNM_PATHNAME flag:+ wildcards in the pattern will not match a / in the pathname.+ For example, "Documentation/{asterisk}.html" matches+ "Documentation/git.html" but not "Documentation/ppc/ppc.html"+ or "tools/perf/Documentation/perf.html".+++Two consecutive asterisks ("`**`") in patterns matched against+full pathname may have special meaning:++ - A leading "`**`" followed by a slash means match in all+ directories. For example, "`**/foo`" matches file or directory+ "`foo`" anywhere, the same as pattern "`foo`". "**/foo/bar"+ matches file or directory "`bar`" anywhere that is directly+ under directory "`foo`".++ - A trailing "/**" matches everything inside. For example,+ "abc/**" matches all files inside directory "abc", relative+ to the location of the `.gitignore` file, with infinite depth.++ - A slash followed by two consecutive asterisks then a slash+ matches zero or more directories. For example, "`a/**/b`"+ matches "`a/b`", "`a/x/b`", "`a/x/y/b`" and so on.++ - Other consecutive asterisks are considered invalid.+++Glob magic is incompatible with literal magic. -- + Currently only the slash `/` is recognized as the "magic signature",
@@ -38,26 +38,28 @@ int fnmatch_icase(const char *pattern, const char *string, int flags)returnfnmatch(pattern,string,flags|(ignore_case?FNM_CASEFOLD:0));}-inlineintgit_fnmatch(constchar*pattern,constchar*string,-intflags,intprefix)+inlineintgit_fnmatch(conststructpathspec_item*item,+constchar*pattern,constchar*string,+intprefix){-intfnm_flags=0;-if(flags&GFNM_PATHNAME)-fnm_flags|=FNM_PATHNAME;if(prefix>0){if(strncmp(pattern,string,prefix))returnFNM_NOMATCH;pattern+=prefix;string+=prefix;}-if(flags&GFNM_ONESTAR){+if(item->flags&PATHSPEC_ONESTAR){intpattern_len=strlen(++pattern);intstring_len=strlen(string);returnstring_len<pattern_len||strcmp(pattern,string+string_len-pattern_len);}-returnfnmatch(pattern,string,fnm_flags);+if(item->magic&PATHSPEC_GLOB)+returnwildmatch(pattern,string,WM_PATHNAME,NULL);+else+/* wildmatch has not learned no FNM_PATHNAME mode yet */+returnfnmatch(pattern,string,0);}staticsize_tcommon_prefix_len(conststructpathspec*pathspec)
@@ -208,10 +208,9 @@ extern int fnmatch_icase(const char *pattern, const char *string, int flags);/**Theprefixpartofpatternmustnotcontainswildcards.*/-#define GFNM_PATHNAME 1 /* similar to FNM_PATHNAME */-#define GFNM_ONESTAR 2 /* there is only _one_ wildcard, a star */--externintgit_fnmatch(constchar*pattern,constchar*string,-intflags,intprefix);+structpathspec_item;+externintgit_fnmatch(conststructpathspec_item*item,+constchar*pattern,constchar*string,+intprefix);#endif
@@ -143,6 +143,14 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT,"0",1);if(envchanged)*envchanged=1;+}elseif(!strcmp(cmd,"--glob-pathspecs")){+setenv(GIT_GLOB_PATHSPECS_ENVIRONMENT,"1",1);+if(envchanged)+*envchanged=1;+}elseif(!strcmp(cmd,"--noglob-pathspecs")){+setenv(GIT_NOGLOB_PATHSPECS_ENVIRONMENT,"1",1);+if(envchanged)+*envchanged=1;}else{fprintf(stderr,"Unknown option: %s\n",cmd);usage(git_usage_string);
@@ -103,6 +105,22 @@ static unsigned prefix_pathspec(struct pathspec_item *item,if(literal_global)global_magic|=PATHSPEC_LITERAL;+if(glob_global<0)+glob_global=git_env_bool(GIT_GLOB_PATHSPECS_ENVIRONMENT,0);+if(glob_global)+global_magic|=PATHSPEC_GLOB;++if(noglob_global<0)+noglob_global=git_env_bool(GIT_NOGLOB_PATHSPECS_ENVIRONMENT,0);++if(glob_global&&noglob_global)+die(_("global 'glob' and 'noglob' pathspec settings are incompatible"));++if((global_magic&PATHSPEC_LITERAL)&&+(global_magic&~PATHSPEC_LITERAL))+die(_("global 'literal' pathspec setting is incompatiable "+"with all other global pathspec settings"));+if(elt[0]!=':'||literal_global){;/* nothing to do */}elseif(elt[1]=='('){
@@ -166,12 +184,20 @@ static unsigned prefix_pathspec(struct pathspec_item *item,magic|=short_magic;*p_short_magic=short_magic;++/* --noglob-pathspec adds :(literal) _unless_ :(glob) is specifed */+if(noglob_global&&!(magic&PATHSPEC_GLOB))+global_magic|=PATHSPEC_LITERAL;+magic|=global_magic;if(pathspec_prefix>=0&&(prefixlen||(prefix&&*prefix)))die("BUG: 'prefix' magic is supposed to be used at worktree's root");+if((magic&PATHSPEC_LITERAL)&&(magic&PATHSPEC_GLOB))+die("%s: 'literal' and 'glob' are incompatible",elt);+if(pathspec_prefix>=0){match=xstrdup(copyfrom);prefixlen=pathspec_prefix;
This patch is essentially no-op. It helps catching new use of this
field though. This field is introduced as an intermediate step for the
pathspec conversion and will be removed eventually. At this stage no
more access sites should be introduced.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
builtin/ls-tree.c | 2 +-
dir.c | 4 ++--
pathspec.c | 6 +++---
pathspec.h | 2 +-
4 files changed, 7 insertions(+), 7 deletions(-)
@@ -115,7 +115,7 @@ int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec)len=common_prefix_len(pathspec);/* Read the directory and prune it */-read_directory(dir,pathspec->nr?pathspec->raw[0]:"",len,pathspec);+read_directory(dir,pathspec->nr?pathspec->_raw[0]:"",len,pathspec);returnlen;}
@@ -1356,7 +1356,7 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const struif(has_symlink_leading_path(path,len))returndir->nr;-simplify=create_simplify(pathspec?pathspec->raw:NULL);+simplify=create_simplify(pathspec?pathspec->_raw:NULL);if(!len||treat_leading_path(dir,path,len,simplify))read_directory_recursive(dir,path,len,0,simplify);free_simplify(simplify);
From: Eric Sunshine <hidden> Date: 2016-06-15 22:56:24
On Fri, Mar 15, 2013 at 2:06 AM, Nguyễn Thái Ngọc Duy [off-list ref] wrote:
match_pathspec_depth() and tree_entry_interesting() check max_depth
field in order to support "git grep --max-depth". The feature
activation is tied to "recursive" field, which led to some unwated
s/unwated/unwanted/
activation, e.g. 5c8eeb8 (diff-index: enable recursive pathspec
matching in unpack_trees - 2012-01-15).
From: Eric Sunshine <hidden> Date: 2016-06-15 22:56:24
On Fri, Mar 15, 2013 at 2:06 AM, Nguyễn Thái Ngọc Duy [off-list ref] wrote:
Prefix length is not preserved across commands when --literal-pathspecs
is specified (no magic is allowed, including 'prefix'). That's OK
because we all paths are literal. No magic, no special treatment
s/we all/we know all/
...or...
s/we all/all/
We could also preserve 'prefix' across commands is quote the prefix
part, then dequote on receiving. But it may not be 100% accurate, we
s/is quote/by quoting/
s/dequote/dequoting/
may dequote longer than the original prefix part, for example. That
may be good or not, but it's not the purpose.
From: John Keeping <hidden> Date: 2016-06-15 22:56:26
On Fri, Mar 15, 2013 at 01:06:42PM +0700, Nguyễn Thái Ngọc Duy wrote:
This passes the pathspec, more or less unmodified, to
git-add--interactive. The command itself does not process pathspec. It
simply passes the pathspec to other builtin commands. So if all those
commands support pathspec, we're good.
This breaks "git reset --keep" in a subdirectory for me.
I ran "git reset --keep <branch>" in a subdirectory and got:
fatal: BUG: parse_pathspec cannot take no argument in this case
Bisecting points to this commit.
The simplest test case is:
( cd t && ../bin-wrappers/git reset --keep HEAD )
which works on master but not pu.
@@ -156,11 +152,9 @@ int run_add_interactive(const char *revision, const char *patch_mode,if(revision)args[ac++]=revision;args[ac++]="--";-if(pc){-memcpy(&(args[ac]),pathspec,sizeof(constchar*)*pc);-ac+=pc;-}-args[ac]=NULL;+for(i=0;i<pathspec->nr;i++)+/* pass original pathspec, to be re-parsed */+args[ac++]=pathspec->items[i].original;status=run_command_v_opt(args,RUN_GIT_CMD);free(args);
@@ -175,17 +169,17 @@ int interactive_add(int argc, const char **argv, const char *prefix, int patch)*git-add--interactiveitselfdoesnotparsepathspec.It*simplypassesthepathspectootherbuiltincommands.Let's*hopeallofthemsupportallmagic,orwe'llneedtolimit-*themagichere.Thereisstillaproblemwithprefix.But-*that'llbeworkedonlateron.+*themagichere.*/parse_pathspec(&pathspec,PATHSPEC_ALL_MAGIC&~PATHSPEC_FROMTOP,PATHSPEC_PREFER_FULL|-PATHSPEC_SYMLINK_LEADING_PATH,+PATHSPEC_SYMLINK_LEADING_PATH|+PATHSPEC_PREFIX_ORIGIN,prefix,argv);returnrun_add_interactive(NULL,patch?"--patch":NULL,-pathspec.raw);+&pathspec);}staticintedit_patch(intargc,constchar**argv,constchar*prefix)
@@ -296,7 +296,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)if(patch_mode){if(reset_type!=NONE)die(_("--patch is incompatible with --{hard,mixed,soft}"));-returnrun_add_interactive(sha1_to_hex(sha1),"--patch=reset",pathspec.raw);+returnrun_add_interactive(sha1_to_hex(sha1),"--patch=reset",&pathspec);}/* git reset tree [--] paths... can be used to
On Tue, Mar 19, 2013 at 1:26 AM, John Keeping [off-list ref] wrote:
On Fri, Mar 15, 2013 at 01:06:42PM +0700, Nguyễn Thái Ngọc Duy wrote:
quoted
This passes the pathspec, more or less unmodified, to
git-add--interactive. The command itself does not process pathspec. It
simply passes the pathspec to other builtin commands. So if all those
commands support pathspec, we're good.
This breaks "git reset --keep" in a subdirectory for me.
I ran "git reset --keep <branch>" in a subdirectory and got:
fatal: BUG: parse_pathspec cannot take no argument in this case
Bisecting points to this commit.
The simplest test case is:
( cd t && ../bin-wrappers/git reset --keep HEAD )
which works on master but not pu.
Beautiful. I got messed up with C operator precedence. This should fix
it. I'll check the rest of parse_pathspec calls later.
From: John Keeping <hidden> Date: 2016-06-15 22:56:26
On Tue, Mar 19, 2013 at 08:58:23AM +0700, Duy Nguyen wrote:
On Tue, Mar 19, 2013 at 1:26 AM, John Keeping [off-list ref] wrote:
quoted
On Fri, Mar 15, 2013 at 01:06:42PM +0700, Nguyễn Thái Ngọc Duy wrote:
quoted
This passes the pathspec, more or less unmodified, to
git-add--interactive. The command itself does not process pathspec. It
simply passes the pathspec to other builtin commands. So if all those
commands support pathspec, we're good.
This breaks "git reset --keep" in a subdirectory for me.
I ran "git reset --keep <branch>" in a subdirectory and got:
fatal: BUG: parse_pathspec cannot take no argument in this case
Bisecting points to this commit.
The simplest test case is:
( cd t && ../bin-wrappers/git reset --keep HEAD )
which works on master but not pu.
Beautiful. I got messed up with C operator precedence. This should fix
it. I'll check the rest of parse_pathspec calls later.
Junio please pull the series from github [1]. I don't want to spam the
list with 45 patches again so I only send patches that are substantially
different from v1:
- documentation for parse_pathspec
- fix wrong operator precendence in git-reset
- fix initialization that may lead to crashes in git-grep (and
probably the failure in t7300-clean.sh)
Typos found by Eric are also fixed, just not sent here.
[1] https://github.com/pclouds/git/commits/parse-pathspec
Nguyễn Thái Ngọc Duy (6):
Add parse_pathspec() that converts cmdline args to struct pathspec
parse_pathspec: support stripping submodule trailing slashes
parse_pathspec: support stripping/checking submodule paths
Guard against new pathspec magic in pathspec matching code
Convert run_add_interactive to use struct pathspec
parse_pathspec: make sure the prefix part is wildcard-free
--
1.8.0.rc0.19.g7bbb31d
Currently to fill a struct pathspec, we do:
const char **paths;
paths = get_pathspec(prefix, argv);
...
init_pathspec(&pathspec, paths);
"paths" can only carry bare strings, which loses information from
command line arguments such as pathspec magic or the prefix part's
length for each argument.
parse_pathspec() is introduced to combine the two calls into one. The
plan is gradually replace all get_pathspec() and init_pathspec() with
parse_pathspec(). get_pathspec() now becomes a thin wrapper of
parse_pathspec().
parse_pathspec() allows the caller to reject the pathspec magics that
it does not support. When a new pathspec magic is introduced, we can
enable it per command after making sure that all underlying code has no
problem with the new magic.
"flags" parameter is currently unused. But it would allow callers to
pass certain instructions to parse_pathspec, for example forcing
literal pathspec when no magic is used.
With the introduction of parse_pathspec, there are now two functions
that can initialize struct pathspec: init_pathspec and
parse_pathspec. Any semantic changes in struct pathspec must be
reflected in both functions. init_pathspec() will be phased out in
favor of parse_pathspec().
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
Documentation/technical/api-setup.txt | 19 +++-
dir.c | 4 +-
dir.h | 2 +
pathspec.c | 168 ++++++++++++++++++++++++++--------
pathspec.h | 11 +++
5 files changed, 163 insertions(+), 41 deletions(-)
@@ -8,6 +8,23 @@ Talk about * is_inside_git_dir() * is_inside_work_tree() * setup_work_tree()-* get_pathspec() (Dscho)++Pathspec+========++See glossary-context.txt for the syntax of pathspec. In memory, a+pathspec set is represented by "struct pathspec" and is prepared by+parse_pathspec(). This function takes several arguments:++- magic_mask specifies what features that are NOT supported by the+ following code. If a user attempts to use such a feature,+ parse_pathspec() can reject it early.++- flags specifies other things that the caller wants parse_pathspec to+ perform.++- prefix and args come from cmd_* functions++get_pathspec() is obsolete and should never be used in new code.
@@ -194,15 +194,128 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const charcopyfrom++;}+magic|=short_magic;+*p_short_magic=short_magic;+if(magic&PATHSPEC_FROMTOP)-returnxstrdup(copyfrom);+match=xstrdup(copyfrom);else-returnprefix_path(prefix,prefixlen,copyfrom);+match=prefix_path(prefix,prefixlen,copyfrom);+*raw=item->match=match;+item->len=strlen(item->match);+if(limit_pathspec_to_literal())+item->nowildcard_len=item->len;+else+item->nowildcard_len=simple_length(item->match);+item->flags=0;+if(item->nowildcard_len<item->len&&+item->match[item->nowildcard_len]=='*'&&+no_wildcard(item->match+item->nowildcard_len+1))+item->flags|=PATHSPEC_ONESTAR;+returnmagic;+}++staticintpathspec_item_cmp(constvoid*a_,constvoid*b_)+{+structpathspec_item*a,*b;++a=(structpathspec_item*)a_;+b=(structpathspec_item*)b_;+returnstrcmp(a->match,b->match);+}++staticvoidNORETURNunsupported_magic(constchar*pattern,+unsignedmagic,+unsignedshort_magic)+{+structstrbufsb=STRBUF_INIT;+inti,n;+for(n=i=0;i<ARRAY_SIZE(pathspec_magic);i++){+conststructpathspec_magic*m=pathspec_magic+i;+if(!(magic&m->bit))+continue;+if(sb.len)+strbuf_addstr(&sb," ");+if(short_magic&m->bit)+strbuf_addf(&sb,"'%c'",m->mnemonic);+else+strbuf_addf(&sb,"'%s'",m->name);+n++;+}+/*+*Wemaywanttosubstitue"this command"withacommand+*name.E.g.whenadd--interactivedieswhenrunning+*"checkout -p"+*/+die(_("%s: pathspec magic not supported by this command: %s"),+pattern,sb.buf);+}++/*+*Givencommandlineargumentsandaprefix,converttheinputto+*pathspec.die()anymagicinmagic_maskisused.+*/+voidparse_pathspec(structpathspec*pathspec,+unsignedmagic_mask,unsignedflags,+constchar*prefix,constchar**argv)+{+structpathspec_item*item;+constchar*entry=argv?*argv:NULL;+inti,n,prefixlen;++memset(pathspec,0,sizeof(*pathspec));++/* No arguments, no prefix -> no pathspec */+if(!entry&&!prefix)+return;++/* No arguments with prefix -> prefix pathspec */+if(!entry){+staticconstchar*raw[2];++pathspec->items=item=xmalloc(sizeof(*item));+memset(item,0,sizeof(*item));+item->match=prefix;+item->nowildcard_len=item->len=strlen(prefix);+raw[0]=prefix;+raw[1]=NULL;+pathspec->nr=1;+pathspec->raw=raw;+return;+}++n=0;+while(argv[n])+n++;++pathspec->nr=n;+pathspec->items=item=xmalloc(sizeof(*item)*n);+pathspec->raw=argv;+prefixlen=prefix?strlen(prefix):0;++for(i=0;i<n;i++){+unsignedshort_magic;+entry=argv[i];++item[i].magic=prefix_pathspec(item+i,&short_magic,+argv+i,flags,+prefix,prefixlen,entry);+if(item[i].magic&magic_mask)+unsupported_magic(entry,+item[i].magic&magic_mask,+short_magic);+if(item[i].nowildcard_len<item[i].len)+pathspec->has_wildcard=1;+pathspec->magic|=item[i].magic;+}++qsort(pathspec->items,pathspec->nr,+sizeof(structpathspec_item),pathspec_item_cmp);}/**N.B.get_pathspec()isdeprecatedinfavorofthe"struct pathspec"-*basedinterface-seepathspec_magicabove.+*basedinterface-seepathspec.c:parse_pathspec().**Arguments:*-prefix-apathrelativetotherootoftheworkingtree
@@ -221,32 +334,11 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const char*/constchar**get_pathspec(constchar*prefix,constchar**pathspec){-constchar*entry=*pathspec;-constchar**src,**dst;-intprefixlen;--if(!prefix&&!entry)-returnNULL;--if(!entry){-staticconstchar*spec[2];-spec[0]=prefix;-spec[1]=NULL;-returnspec;-}--/* Otherwise we have to re-write the entries.. */-src=pathspec;-dst=pathspec;-prefixlen=prefix?strlen(prefix):0;-while(*src){-*(dst++)=prefix_pathspec(prefix,prefixlen,*src);-src++;-}-*dst=NULL;-if(!*pathspec)-returnNULL;-returnpathspec;+structpathspecps;+parse_pathspec(&ps,+PATHSPEC_ALL_MAGIC&~PATHSPEC_FROMTOP,+0,prefix,pathspec);+returnps.raw;}voidcopy_pathspec(structpathspec*dst,conststructpathspec*src)
This flag is equivalent to builtin/ls-files.c:strip_trailing_slashes()
and is intended to replace that function when ls-files is converted to
use parse_pathspec.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
pathspec.c | 9 +++++++++
pathspec.h | 2 ++
2 files changed, 11 insertions(+)
@@ -31,6 +31,8 @@ struct pathspec {#define PATHSPEC_PREFER_CWD (1<<0) /* No args means match cwd */#define PATHSPEC_PREFER_FULL (1<<1) /* No args means match everything */#define PATHSPEC_MAXDEPTH_VALID (1<<2) /* max_depth field is valid */+/* stripping the trailing slash if the given path is a gitlink */+#define PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP (1<<3)externintinit_pathspec(structpathspec*,constchar**);externvoidparse_pathspec(structpathspec*pathspec,
PATHSPEC_SYMLINK_LEADING_PATH and _STRIP_SUBMODULE_SLASH_EXPENSIVE are
respectively the alternate implementation of
pathspec.c:die_if_path_beyond_symlink() and
pathspec.c:check_path_for_gitlink(). They are intended to replace
those functions when builtin/add.c and builtin/check-ignore.c are
converted to use parse_pathspec.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
pathspec.c | 26 ++++++++++++++++++++++++++
pathspec.h | 10 ++++++++++
2 files changed, 36 insertions(+)
@@ -33,6 +33,16 @@ struct pathspec {#define PATHSPEC_MAXDEPTH_VALID (1<<2) /* max_depth field is valid *//* stripping the trailing slash if the given path is a gitlink */#define PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP (1<<3)+/* die if a symlink is part of the given path's directory */+#define PATHSPEC_SYMLINK_LEADING_PATH (1<<4)+/*+*Thisislikeacombinationof..LEADING_PATHand.._SLASH_CHEAP+*(butnotthesame):itstripsthetrailingslashifthegivenpath+*isagitlinkbutalsochecksanddiesifgitlinkispartofthe+*leadingpath(i.e.thegivenpathgoesbeyondasubmodule).It's+*saferthan_SLASH_CHEAPandalsomoreexpensive.+*/+#define PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE (1<<5)externintinit_pathspec(structpathspec*,constchar**);externvoidparse_pathspec(structpathspec*pathspec,
@@ -28,3 +28,22 @@ parse_pathspec(). This function takes several arguments: - prefix and args come from cmd_* functions get_pathspec() is obsolete and should never be used in new code.++parse_pathspec() helps catch unsupported features and reject it+politely. At a lower level, different pathspec-related functions may+not support the same set of features. Such pathspec-sensitive+functions are guarded with GUARD_PATHSPEC(), which will die in an+unfriendly way when an unsupported feature is requested.++The command designers are supposed to make sure that GUARD_PATHSPEC()+never dies. They have to make sure all unsupported features are caught+by parse_pathspec(), not by GUARD_PATHSPEC. grepping GUARD_PATHSPEC()+should give the designers all pathspec-sensitive codepaths and what+features they support.++A similar process is applied when a new pathspec magic is added. The+designer lifts the GUARD_PATHSPEC restriction in the functions that+support the new magic. At the same time (s)he has to make sure this+new feature will be caught at parse_pathspec() in commands that cannot+handle the new magic in some cases. grepping parse_pathspec() should+help.
@@ -198,6 +198,25 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, coconstchar*paths[1];inti;+/*+*follow-renamecodeisveryspecific,weneedexactlyone+*path.Magicthatmatchesmorethanonepathisnot+*supported.+*/+GUARD_PATHSPEC(&opt->pathspec,PATHSPEC_FROMTOP);+#if 0+/*+*Weshouldrejectwildcardsaswell.Unfortunatelywe+*haven'tgotareliablewaytodetectthat'foo\*bar'in+*facthasnowildcards.nowildcard_lenismerelyahintfor+*optimization.Letitslipfornowuntilwildmatchistaught+*aboutdry-runmodeandreturnswildcardinfo.+*/+if(opt->pathspec.has_wildcard)+die("BUG:%s:%d: wildcards are not supported",+__FILE__,__LINE__);+#endif+/* Remove the file creation entry from the diff queue, and remember it */choice=q->queue[0];q->nr=0;
This passes the pathspec, more or less unmodified, to
git-add--interactive. The command itself does not process pathspec. It
simply passes the pathspec to other builtin commands. So if all those
commands support pathspec, we're good.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
builtin/add.c | 26 ++++++++++----------------
builtin/checkout.c | 9 ++++-----
builtin/reset.c | 8 ++++----
commit.h | 2 +-
4 files changed, 19 insertions(+), 26 deletions(-)
@@ -156,11 +152,9 @@ int run_add_interactive(const char *revision, const char *patch_mode,if(revision)args[ac++]=revision;args[ac++]="--";-if(pc){-memcpy(&(args[ac]),pathspec,sizeof(constchar*)*pc);-ac+=pc;-}-args[ac]=NULL;+for(i=0;i<pathspec->nr;i++)+/* pass original pathspec, to be re-parsed */+args[ac++]=pathspec->items[i].original;status=run_command_v_opt(args,RUN_GIT_CMD);free(args);
@@ -175,17 +169,17 @@ int interactive_add(int argc, const char **argv, const char *prefix, int patch)*git-add--interactiveitselfdoesnotparsepathspec.It*simplypassesthepathspectootherbuiltincommands.Let's*hopeallofthemsupportallmagic,orwe'llneedtolimit-*themagichere.Thereisstillaproblemwithprefix.But-*that'llbeworkedonlateron.+*themagichere.*/parse_pathspec(&pathspec,PATHSPEC_ALL_MAGIC&~PATHSPEC_FROMTOP,PATHSPEC_PREFER_FULL|-PATHSPEC_SYMLINK_LEADING_PATH,+PATHSPEC_SYMLINK_LEADING_PATH|+PATHSPEC_PREFIX_ORIGIN,prefix,argv);returnrun_add_interactive(NULL,patch?"--patch":NULL,-pathspec.raw);+&pathspec);}staticintedit_patch(intargc,constchar**argv,constchar*prefix)
@@ -296,7 +296,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)if(patch_mode){if(reset_type!=NONE)die(_("--patch is incompatible with --{hard,mixed,soft}"));-returnrun_add_interactive(sha1_to_hex(sha1),"--patch=reset",pathspec.raw);+returnrun_add_interactive(sha1_to_hex(sha1),"--patch=reset",&pathspec);}/* git reset tree [--] paths... can be used to
Prepending prefix to pathspec is a trick to workaround the fact that
commands can be executed in a subdirectory, but all git commands run
at worktree's root. The prefix part should always be treated as
literal string. Make it so.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
cache.h | 2 ++
path.c | 15 ++++++++++++++-
pathspec.c | 17 +++++++++++++----
pathspec.h | 2 +-
setup.c | 9 +++++----
5 files changed, 35 insertions(+), 10 deletions(-)
From: Eric Sunshine <hidden> Date: 2016-06-15 22:56:28
On Wed, Mar 20, 2013 at 8:16 AM, Nguyễn Thái Ngọc Duy [off-list ref] wrote:
+static void NORETURN unsupported_magic(const char *pattern,
+ unsigned magic,
+ unsigned short_magic)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int i, n;
+ for (n = i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
+ const struct pathspec_magic *m = pathspec_magic + i;
+ if (!(magic & m->bit))
+ continue;
+ if (sb.len)
+ strbuf_addstr(&sb, " ");
+ if (short_magic & m->bit)
+ strbuf_addf(&sb, "'%c'", m->mnemonic);
+ else
+ strbuf_addf(&sb, "'%s'", m->name);
+ n++;
+ }
+ /*
+ * We may want to substitue "this command" with a command
s/substitue/substitute/
+ * name. E.g. when add--interactive dies when running
+ * "checkout -p"
+ */
+ die(_("%s: pathspec magic not supported by this command: %s"),
+ pattern, sb.buf);
+}
@@ -28,3 +28,22 @@ parse_pathspec(). This function takes several arguments: - prefix and args come from cmd_* functions get_pathspec() is obsolete and should never be used in new code.++parse_pathspec() helps catch unsupported features and reject it
s/reject it/reject them/
+politely. At a lower level, different pathspec-related functions may
+not support the same set of features. Such pathspec-sensitive
+functions are guarded with GUARD_PATHSPEC(), which will die in an
+unfriendly way when an unsupported feature is requested.
From: Adam Spiers <hidden> Date: 2016-06-15 22:56:48
On Fri, Mar 15, 2013 at 01:06:38PM +0700, Nguyễn Thái Ngọc Duy wrote:
check-ignore (at least the test suite) seems to rely on the pattern
order. PATHSPEC_KEEP_ORDER is introduced to explictly express this.
The lack of PATHSPEC_MAXDEPTH_VALID is sufficient because it's the
only flag that reorders pathspecs, but it's less obvious that way.
Sorry for the slow response - I only just noticed this today. (It
would be useful if any future patches to check-ignore Cc: me
explicitly, to catch my mail filters.)
@@ -53,14 +53,14 @@ static void output_exclude(const char *path, struct exclude *exclude)}}-staticintcheck_ignore(constchar*prefix,constchar**pathspec)+staticintcheck_ignore(intargc,constchar**argv,constchar*prefix){structdir_structdir;-constchar*path,*full_path;char*seen;intnum_ignored=0,dtype=DT_UNKNOWN,i;structpath_exclude_checkcheck;structexclude*exclude;+structpathspecpathspec;/* read_cache() is only necessary so we can watch out for submodules. */if(read_cache()<0)
Is there a compelling reason for introducing argc as a new parameter
to check_ignore(), other than simplifying the above line? And why
rename the pathspec parameter to argv? Both these changes are
misleading AFAICS, since paths provided to check_ignore() can come
from sources other than CLI arguments (i.e. via --stdin).
The introduction of argc also makes it possible to invoke
check_ignore() with arguments which are not self-consistent.
I haven't been following your pathspec work, but FWIW the other
changes in this patch look reasonable at a glance.
Thanks,
Adam
On Sat, Apr 13, 2013 at 1:03 AM, Adam Spiers [off-list ref] wrote:
quoted
-static int check_ignore(const char *prefix, const char **pathspec)
+static int check_ignore(int argc, const char **argv, const char *prefix)
{
struct dir_struct dir;
- const char *path, *full_path;
char *seen;
int num_ignored = 0, dtype = DT_UNKNOWN, i;
struct path_exclude_check check;
struct exclude *exclude;
+ struct pathspec pathspec;
/* read_cache() is only necessary so we can watch out for submodules. */
if (read_cache() < 0)
@@ -70,31 +70,39 @@ static int check_ignore(const char *prefix, const char **pathspec) dir.flags |= DIR_COLLECT_IGNORED; setup_standard_excludes(&dir);- if (!pathspec || !*pathspec) {+ if (!argc) {
Is there a compelling reason for introducing argc as a new parameter
to check_ignore(), other than simplifying the above line? And why
rename the pathspec parameter to argv? Both these changes are
misleading AFAICS, since paths provided to check_ignore() can come
from sources other than CLI arguments (i.e. via --stdin).
Because I introduced "struct pathspec pathspec;" I need to rename the
argument "pathspec" to something else. Maybe we could rename the
argument to "paths"?
The introduction of argc also makes it possible to invoke
check_ignore() with arguments which are not self-consistent.
This is the same problem with main() and other places that follow this
convention. But I don't mind dropping argc either.
--
Duy
From: Adam Spiers <hidden> Date: 2016-06-15 22:56:49
On Sat, Apr 13, 2013 at 09:09:33AM +1000, Duy Nguyen wrote:
On Sat, Apr 13, 2013 at 1:03 AM, Adam Spiers [off-list ref] wrote:
quoted
quoted
-static int check_ignore(const char *prefix, const char **pathspec)
+static int check_ignore(int argc, const char **argv, const char *prefix)
{
struct dir_struct dir;
- const char *path, *full_path;
char *seen;
int num_ignored = 0, dtype = DT_UNKNOWN, i;
struct path_exclude_check check;
struct exclude *exclude;
+ struct pathspec pathspec;
/* read_cache() is only necessary so we can watch out for submodules. */
if (read_cache() < 0)
@@ -70,31 +70,39 @@ static int check_ignore(const char *prefix, const char **pathspec) dir.flags |= DIR_COLLECT_IGNORED; setup_standard_excludes(&dir);- if (!pathspec || !*pathspec) {+ if (!argc) {
Is there a compelling reason for introducing argc as a new parameter
to check_ignore(), other than simplifying the above line? And why
rename the pathspec parameter to argv? Both these changes are
misleading AFAICS, since paths provided to check_ignore() can come
from sources other than CLI arguments (i.e. via --stdin).
Because I introduced "struct pathspec pathspec;" I need to rename the
argument "pathspec" to something else.
Ah, I see - that makes sense :-)
Maybe we could rename the argument to "paths"?
Sounds fine to me.
quoted
The introduction of argc also makes it possible to invoke
check_ignore() with arguments which are not self-consistent.
This is the same problem with main()
How could main() be invoked with argc inconsistent with argv?
and other places that follow this convention. But I don't mind
dropping argc either.
What is the reason for that convention? I'm willing to be persuaded
either way.