From: Adam Spiers <hidden> Date: 2016-06-15 22:54:37
I was browsing stackoverflow the other day and came across this question:
http://stackoverflow.com/questions/12144633/which-gitignore-rule-is-ignoring-my-file/
A quick google revealed this thread from 2009:
http://thread.gmane.org/gmane.comp.version-control.git/108671/focus=108815
where Junio and Jeff discussed the possibility of adding a new `git
check-ignore' subcommand somewhat analogous to the existing `git
check-attr', and suggested the beginnings of an implementation. It
struck me that it might not be too hard to follow these ideas to their
natural conclusion, so I decided it would make a fun project :-)
The following series of patches is the outcome. I am completely new
to git hacking, so whilst I have tried very hard to follow all the
conventions and documented guidelines, please go easy on me if there
are any glaring errors ;-) However, the added test suite should cover
the new code paths thoroughly, and I also ran check-ignore through
valgrind and made some improvements accordingly, so hopefully it's
pretty near the mark.
I have a question and some comments about my current patch series.
Firstly, I re-used pathspec-handling code from builtin/add.c, so I
moved it to a new pathspec.c file. It looks like setup.c might have
been a better candidate, but that library is already a fairly large
collection of apparently loosely associated things, so I wasn't sure.
According to the comments, get_pathspec() is due to be superceded by
the "struct pathspec" interface, so perhaps it would make sense to
split setup.c up into pathspec.c and one or two other files so as to
move towards a clean demarcation of this new API?
Secondly, in the course of trying to understand the code base, my
little brain got confused and I noticed a few areas where I thought
there was potential to make things a bit clearer. So some of my
commits are janitorial in nature.
Thirdly, currently the new sub-command hardly looks at the cache.
This is partially because it doesn't need to in the most common use
case (i.e. user is confused about why files are/aren't being ignored).
It's also because this whole project took a lot longer than I
expected, so I'm running out of time :-) Perhaps someone can add this
in the future if it's needed. Right now the cache is only used to
prevent recursing into submodules.
Thanks,
Adam
Adam Spiers (9):
Update directory listing API doc to match code
Improve documentation and comments regarding directory traversal API
Rename cryptic 'which' variable to more consistent name
Refactor excluded_from_list
Refactor excluded and path_excluded
For each exclude pattern, store information about where it came from
Extract some useful pathspec handling code from builtin/add.c into a
library
Provide free_directory() for reclaiming dir_struct memory
Add git-check-ignores
.gitignore | 1 +
Documentation/git-check-ignore.txt | 58 +++++
Documentation/gitignore.txt | 6 +-
Documentation/technical/api-directory-listing.txt | 23 +-
Makefile | 3 +
builtin.h | 1 +
builtin/add.c | 84 +-----
builtin/check-ignore.c | 150 +++++++++++
builtin/clean.c | 2 +-
builtin/ls-files.c | 3 +-
command-list.txt | 1 +
contrib/completion/git-completion.bash | 1 +
dir.c | 183 ++++++++++---
dir.h | 37 ++-
git.c | 1 +
pathspec.c | 87 +++++++
pathspec.h | 6 +
t/t0007-ignores.sh | 301 ++++++++++++++++++++++
18 files changed, 811 insertions(+), 137 deletions(-)
create mode 100644 Documentation/git-check-ignore.txt
create mode 100644 builtin/check-ignore.c
create mode 100644 pathspec.c
create mode 100644 pathspec.h
create mode 100755 t/t0007-ignores.sh
--
1.7.12.155.ge5750d5.dirty
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:37
'el' is only *slightly* less cryptic, but is already used as the
variable name for a struct exclude_list pointer in numerous other
places, so this reduces the number of cryptic variable names in use by
one :-)
Signed-off-by: Adam Spiers <redacted>
---
dir.c | 10 +++++-----
dir.h | 4 ++--
2 files changed, 7 insertions(+), 7 deletions(-)
@@ -0,0 +1,87 @@+#include"cache.h"+#include"dir.h"++voidvalidate_path(constchar*prefix,constchar*path)+{+if(has_symlink_leading_path(path,strlen(path))){+intlen=prefix?strlen(prefix):0;+die(_("'%s' is beyond a symbolic link"),path+len);+}+}++constchar**validate_pathspec(constchar*prefix,constchar**files)+{+constchar**pathspec=get_pathspec(prefix,files);++if(pathspec){+constchar**p;+for(p=pathspec;*p;p++){+validate_path(prefix,*p);+}+}++returnpathspec;+}++voidfill_pathspec_matches(constchar**pathspec,char*seen,intspecs)+{+intnum_unmatched=0,i;++/*+*Sincewearewalkingtheindexasifwewerewalkingthedirectory,+*wehavetomarkthematchedpathspecasseen;otherwisewewill+*mistakenlythinkthattheusergaveapathspecthatdidnotmatch+*anything.+*/+for(i=0;i<specs;i++)+if(!seen[i])+num_unmatched++;+if(!num_unmatched)+return;+for(i=0;i<active_nr;i++){+structcache_entry*ce=active_cache[i];+match_pathspec(pathspec,ce->name,ce_namelen(ce),0,seen);+}+}++char*find_used_pathspec(constchar**pathspec)+{+char*seen;+inti;++for(i=0;pathspec[i];i++)+;/* just counting */+seen=xcalloc(i,1);+fill_pathspec_matches(pathspec,seen,i);+returnseen;+}++voidtreat_gitlink(constchar*path)+{+inti,len=strlen(path);+for(i=0;i<active_nr;i++){+structcache_entry*ce=active_cache[i];+if(S_ISGITLINK(ce->ce_mode)){+intlen2=ce_namelen(ce);+if(len<=len2||path[len2]!='/'||+memcmp(ce->name,path,len2))+continue;+if(len==len2+1)+/* strip trailing slash */+path=xstrndup(ce->name,len2);+else+die(_("Path '%s' is in submodule '%.*s'"),+path,len2,ce->name);+}+}+}++voidtreat_gitlinks(constchar**pathspec)+{+if(!pathspec||!*pathspec)+return;++inti;+for(i=0;pathspec[i];i++)+treat_gitlink(pathspec[i]);+}
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:37
For exclude patterns read in from files, the filename is stored together
with the corresponding line number (counting starting at 1).
For exclude patterns provided on the command line, the sequence number
is negative, with counting starting at -1, so for example the 2nd
pattern provided via --exclude would be numbered -2. This allows any
future consumers of that data to easily distinguish between exclude
patterns from files vs. from the CLI.
Signed-off-by: Adam Spiers <redacted>
---
builtin/clean.c | 2 +-
builtin/ls-files.c | 3 ++-
dir.c | 25 +++++++++++++++++++------
dir.h | 5 ++++-
4 files changed, 26 insertions(+), 9 deletions(-)
@@ -31,6 +31,9 @@ struct exclude_list {intbaselen;intto_exclude;intflags;+constchar*src;+intsrcpos;/* counting starts from 1 for line numbers in ignore files,+andfrom-1decrementingforpatternsfromCLI(--exclude)*/}**excludes;};
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:37
In a similar way to the previous commit, this extracts new helper
functions excluded_1() and path_excluded_1() which return the last
exclude_list element which matched, or NULL if no match was found.
excluded() and path_excluded() become wrappers around these, and just
return 0 or 1 depending on whether any matching exclude_list element
was found.
This allows callers to find out _why_ a given path was excluded,
rather than just whether it was or not, paving the way for a new git
sub-command which allows users to test their exclude lists from the
command line.
Signed-off-by: Adam Spiers <redacted>
---
dir.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
dir.h | 3 +++
2 files changed, 67 insertions(+), 17 deletions(-)
@@ -657,11 +679,18 @@ int path_excluded(struct path_exclude_check *check,if(namelen<0)namelen=strlen(name);+/*+*Ifpathisnon-empty,andnameisequaltopathora+*subdirectoryofpath,nameshouldbeexcluded,because+*it'sinsideadirectorywhichisalreadyknowntobe+*excludedandwaspreviouslyleftincheck->path.+*/if(path->len&&path->len<=namelen&&!memcmp(name,path->buf,path->len)&&-(!name[path->len]||name[path->len]=='/'))-return1;+(!name[path->len]||name[path->len]=='/')){+returncheck->exclude;+}strbuf_setlen(path,0);for(i=0;name[i];i++){
@@ -669,8 +698,11 @@ int path_excluded(struct path_exclude_check *check,if(ch=='/'){intdt=DT_DIR;-if(excluded(check->dir,path->buf,&dt))-return1;+exclude=excluded_1(check->dir,path->buf,&dt);+if(exclude){+check->exclude=exclude;+returnexclude;+}}strbuf_addch(path,ch);}
@@ -678,7 +710,22 @@ int path_excluded(struct path_exclude_check *check,/* An entry in the index; cannot be a directory with subentries */strbuf_setlen(path,0);-returnexcluded(check->dir,name,dtype);+returnexcluded_1(check->dir,name,dtype);+}++/*+*Isthisnameexcluded?Thisisforacallerlikeshow_files()that+*donothonordirectoryhierarchyanditeratethroughpathsthatare+*possiblyinanignoreddirectory.+*/+intpath_excluded(structpath_exclude_check*check,+constchar*name,intnamelen,int*dtype)+{+structexclude*exclude=path_excluded_1(check,name,namelen,dtype);+if(exclude){+returnexclude->to_exclude;+}+return0;}staticstructdir_entry*dir_entry_new(constchar*pathname,intlen)
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:37
This works in a similar manner to git-check-attr. Some code
was reused from add.c by refactoring out into pathspec.c.
Thanks to Jeff King and Junio C Hamano for the idea:
http://thread.gmane.org/gmane.comp.version-control.git/108671/focus=108815
Signed-off-by: Adam Spiers <redacted>
---
.gitignore | 1 +
Documentation/git-check-ignore.txt | 58 +++++++
Documentation/gitignore.txt | 6 +-
Makefile | 1 +
builtin.h | 1 +
builtin/add.c | 2 +-
builtin/check-ignore.c | 150 ++++++++++++++++
command-list.txt | 1 +
contrib/completion/git-completion.bash | 1 +
git.c | 1 +
t/t0007-ignores.sh | 301 +++++++++++++++++++++++++++++++++
11 files changed, 520 insertions(+), 3 deletions(-)
create mode 100644 Documentation/git-check-ignore.txt
create mode 100644 builtin/check-ignore.c
create mode 100755 t/t0007-ignores.sh
@@ -0,0 +1,58 @@+git-check-ignore(1)+=================++NAME+----+git-check-ignore - Debug gitignore / exclude files+++SYNOPSIS+--------+[verse]+'git check-ignore' pathname...+'git check-ignore' --stdin [-z] < <list-of-paths>++DESCRIPTION+-----------++For each pathname given via the command-line or from a file via+`--stdin`, this command will list the first exclude pattern found (if+any) which explicitly excludes or includes that pathname. Note that+within any given exclude file, later patterns take precedence over+earlier ones, so any matching pattern which this command outputs may+not be the one you would immediately expect.++OPTIONS+-------+--stdin::+ Read file names from stdin instead of from the command-line.++-z::+ Only meaningful with `--stdin`; paths are separated with a+ NUL character instead of a linefeed character.++OUTPUT+------++The output is a series of lines of the form:++<path> COLON SP <type> SP <pattern> SP <source> SP <position> LF++<path> is the path of a file being queried, <type> is either+'excluded' or 'included' (for patterns prefixed with '!'), <pattern>+is the matching pattern, <source> is the pattern's source file (either+as an absolute path or relative to the repository root), and+<position> is the position of the pattern within that source.++If no pattern matches a given path, nothing will be output for that+path.++SEE ALSO+--------+linkgit:gitignore[5]+linkgit:gitconfig[5]+linkgit:git-ls-files[5]++GIT+---+Part of the linkgit:git[1] suite
@@ -155,8 +155,10 @@ The second .gitignore prevents git from ignoring SEE ALSO ---------linkgit:git-rm[1], linkgit:git-update-index[1],-linkgit:gitrepository-layout[5]+linkgit:git-rm[1],+linkgit:git-update-index[1],+linkgit:gitrepository-layout[5],+linkgit:git-check-ignore[1] GIT ---
@@ -273,7 +273,7 @@ static int add_files(struct dir_struct *dir, int flags)fprintf(stderr,_(ignore_error));for(i=0;i<dir->ignored_nr;i++)fprintf(stderr,"%s\n",dir->ignored[i]->name);-fprintf(stderr,_("Use -f if you really want to add them.\n"));+fprintf(stderr,_("Use -f if you really want to add them, or git check-ignore to see\nwhy they're ignored.\n"));die(_("no files added"));}
@@ -0,0 +1,150 @@+#include"builtin.h"+#include"cache.h"+#include"dir.h"+#include"quote.h"+#include"pathspec.h"+#include"parse-options.h"++staticintstdin_paths;+staticconstchar*constcheck_ignore_usage[]={+"git check-ignore pathname...",+"git check-ignore --stdin [-z] < <list-of-paths>",+NULL+};++staticintnull_term_line;++staticconststructoptioncheck_ignore_options[]={+OPT_BOOLEAN(0,"stdin",&stdin_paths,"read file names from stdin"),+OPT_BOOLEAN('z',NULL,&null_term_line,+"input paths are terminated by a null character"),+OPT_END()+};++staticvoidoutput_exclude(constchar*path,structexclude*exclude)+{+char*type=exclude->to_exclude?"excluded":"included";+char*bang=exclude->to_exclude?"":"!";+char*dir=(exclude->flags&EXC_FLAG_MUSTBEDIR)?"/":"";+printf(_("%s: %s %s%s%s "),path,type,bang,exclude->pattern,dir);+if(exclude->srcpos>0){+printf("%s %d",exclude->src,exclude->srcpos);+}+else{+/* Exclude was from CLI parameter. This code path is+*currentlyimpossibletohit,butlateronwemight+*wanttoaddignoretracingtoothercommandssuch+*asgitclean,whichdoesaccept--exclude.+*/+/* printf("%s %d", exclude->src, -exclude->srcpos); */+}+printf("\n");+}++staticvoidcheck_ignore(constchar*prefix,constchar**pathspec)+{+structdir_structdir;+constchar*path;+char*seen=NULL;++/* read_cache() is only necessary so we can watch out for submodules. */+if(read_cache()<0)+die(_("index file corrupt"));++memset(&dir,0,sizeof(dir));+dir.flags|=DIR_COLLECT_IGNORED;+setup_standard_excludes(&dir);++if(pathspec){+inti;+structpath_exclude_checkcheck;+structexclude*exclude;++path_exclude_check_init(&check,&dir);+if(!seen)+seen=find_used_pathspec(pathspec);+for(i=0;pathspec[i];i++){+path=pathspec[i];+char*full_path=+prefix_path(prefix,prefix?strlen(prefix):0,path);+treat_gitlink(full_path);+validate_path(prefix,full_path);+if(!seen[i]&&path[0]){+intdtype=DT_UNKNOWN;+exclude=path_excluded_1(&check,full_path,-1,&dtype);+if(exclude){+output_exclude(path,exclude);+}+}+}+free(seen);+free_directory(&dir);+path_exclude_check_clear(&check);+}+else{+printf("no pathspec\n");+}+}++staticvoidcheck_ignore_stdin_paths(constchar*prefix)+{+structstrbufbuf,nbuf;+char**pathspec=NULL;+size_tnr=0,alloc=0;+intline_termination=null_term_line?0:'\n';++strbuf_init(&buf,0);+strbuf_init(&nbuf,0);+while(strbuf_getline(&buf,stdin,line_termination)!=EOF){+if(line_termination&&buf.buf[0]=='"'){+strbuf_reset(&nbuf);+if(unquote_c_style(&nbuf,buf.buf,NULL))+die("line is badly quoted");+strbuf_swap(&buf,&nbuf);+}+ALLOC_GROW(pathspec,nr+1,alloc);+pathspec[nr]=xcalloc(strlen(buf.buf)+1,sizeof(*buf.buf));+strcpy(pathspec[nr++],buf.buf);+}+ALLOC_GROW(pathspec,nr+1,alloc);+pathspec[nr]=NULL;+check_ignore(prefix,(constchar**)pathspec);+maybe_flush_or_die(stdout,"attribute to stdout");+strbuf_release(&buf);+strbuf_release(&nbuf);+free(pathspec);+}++staticNORETURNvoiderror_with_usage(constchar*msg)+{+error("%s",msg);+usage_with_options(check_ignore_usage,check_ignore_options);+}++intcmd_check_ignore(intargc,constchar**argv,constchar*prefix)+{+git_config(git_default_config,NULL);++argc=parse_options(argc,argv,prefix,check_ignore_options,+check_ignore_usage,0);++if(stdin_paths){+if(0<argc)+error_with_usage("Can't specify files with --stdin");+}else{+if(null_term_line)+error_with_usage("-z only makes sense with --stdin");++if(argc==0)+error_with_usage("No path specified");+}++if(stdin_paths)+check_ignore_stdin_paths(prefix);+else{+check_ignore(prefix,argv);+maybe_flush_or_die(stdout,"ignore to stdout");+}++return0;+}
@@ -0,0 +1,301 @@+#!/bin/sh++test_description=gitignores++../test-lib.sh++init_vars(){+global_excludes="$HOME/global-excludes"+}++enable_global_excludes(){+init_vars+gitconfigcore.excludesfile"$global_excludes"+}++ignore_check(){+paths="$1"expected="$2"global_args="$3"++iftest-z"$expected";then+>"$HOME/expected"# avoid newline+else+echo"$expected">"$HOME/expected"+fi&&+run_check_ignore"$paths""$global_args"+}++expect(){+echo"$*">"$HOME/expected"+}++run_check_ignore(){+args="$1"global_args="$2"++init_vars&&+rm-f"$HOME/stdout""$HOME/stderr""$HOME/cmd"&&+echo`whichgit`$global_argscheck-ignore$args>"$HOME/cmd"&&+pwd>"$HOME/pwd"&&+git$global_argscheck-ignore$args>"$HOME/stdout"2>"$HOME/stderr"&&+test_cmp"$HOME/expected""$HOME/stdout"&&+test_line_count=0"$HOME/stderr"+}++test_expect_success'setup''+init_vars+mkdir-pa/b/ignored-dira/submoduleb&&+ln-sba/symlink&&+(+cda/submodule&&+gitinit&&+echoa>a&&+gitadda&&+gitcommit-m"commit in submodule"+)&&+gitadda/submodule&&+cat<<-EOF>.gitignore&&+one+EOF+cat<<-EOF>a/.gitignore&&+two*+*three+EOF+cat<<-EOF>a/b/.gitignore&&+four+five+# this comment should affect the line numbers+six+ignored-dir/+# and so should this blank line:++!on*+!two+EOF+echo"seven">a/b/ignored-dir/.gitignore&&+test-n"$HOME"&&+cat<<-EOF>"$global_excludes"+globalone+!globaltwo+globalthree+EOF+'++test_expect_success'empty command line''+test_must_failgitcheck-ignore2>"$HOME/stderr"&&+grep-q"error: No path specified""$HOME/stderr"+'++test_expect_success'erroneous use of --''+test_must_failgitcheck-ignore--2>"$HOME/stderr"&&+grep-q"error: No path specified""$HOME/stderr"+'++test_expect_success'--stdin with superfluous arg''+test_must_failgitcheck-ignore--stdinfoo2>"$HOME/stderr"&&+grep-q"Can'\''t specify files with --stdin""$HOME/stderr"+'++test_expect_success'--stdin -z with superfluous arg''+test_must_failgitcheck-ignore--stdin-zfoo2>"$HOME/stderr"&&+grep-q"Can'\''t specify files with --stdin""$HOME/stderr"+'++test_expect_success'-z without --stdin''+test_must_failgitcheck-ignore-z2>"$HOME/stderr"&&+grep-q"error: -z only makes sense with --stdin""$HOME/stderr"+'++test_expect_success'-z without --stdin and superfluous arg''+test_must_failgitcheck-ignore-zfoo2>"$HOME/stderr"&&+grep-q"error: -z only makes sense with --stdin""$HOME/stderr"+'++test_expect_success'needs work tree''+(+cd.git&&+test_must_failgitcheck-ignorefoo2>"$HOME/stderr"+)&&+grep-q"fatal: This operation must be run in a work tree""$HOME/stderr"++'+test_expect_success'top-level not ignored''+ignore_checkfoo""+'++test_expect_success'top-level ignored''+ignore_checkone"one: excluded one .gitignore 1"+'++test_expect_success'sub-directory ignore from top''+expect"a/one: excluded one .gitignore 1"&&+run_check_ignorea/one+'++test_expect_success'sub-directory local ignore''+expect"a/3-three: excluded *three a/.gitignore 2"&&+run_check_ignore"a/3-three a/three-not-this-one"+'++test_expect_success'sub-directory local ignore inside a''+expect"3-three: excluded *three a/.gitignore 2"&&+(+cda&&+run_check_ignore"3-three three-not-this-one"+)+'++test_expect_success'nested include''+expect"a/b/one: included !on* a/b/.gitignore 8"&&+run_check_ignore"a/b/one"+'++test_expect_success'ignored sub-directory''+expect"a/b/ignored-dir: excluded ignored-dir/ a/b/.gitignore 5"&&+run_check_ignore"a/b/ignored-dir"+'++test_expect_success'multiple files inside ignored sub-directory''+cat<<-EOF >"$HOME/expected" &&+a/b/ignored-dir/foo:excludedignored-dir/a/b/.gitignore5+a/b/ignored-dir/twoooo:excludedignored-dir/a/b/.gitignore5+a/b/ignored-dir/seven:excludedignored-dir/a/b/.gitignore5+EOF+run_check_ignore"a/b/ignored-dir/foo a/b/ignored-dir/twoooo a/b/ignored-dir/seven"+'++test_expect_success'cd to ignored sub-directory''+cat<<-EOF >"$HOME/expected" &&+foo:excludedignored-dir/a/b/.gitignore5+twoooo:excludedignored-dir/a/b/.gitignore5+../one:included!on*a/b/.gitignore8+seven:excludedignored-dir/a/b/.gitignore5+../../one:excludedone.gitignore1+EOF+(+cda/b/ignored-dir&&+run_check_ignore"foo twoooo ../one seven ../../one"+)+'++test_expect_success'symlink''+ignore_check"a/symlink"""+'++test_expect_success'beyond a symlink''+test_must_failgitcheck-ignore"a/symlink/foo"+'++test_expect_success'beyond a symlink from subdirectory''+(+cda&&+test_must_failgitcheck-ignore"symlink/foo"+)+'++test_expect_success'submodule''+test_must_failgitcheck-ignore"a/submodule/one"2>"$HOME/stderr"&&+expect"fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"&&+test_cmp"$HOME/expected""$HOME/stderr"+'++test_expect_success'submodule from subdirectory''+(+cda&&+test_must_failgitcheck-ignore"submodule/one"2>"$HOME/stderr"+)&&+expect"fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"&&+test_cmp"$HOME/expected""$HOME/stderr"+'++test_expect_success'global ignore not yet enabled''+expect"a/globalthree: excluded *three a/.gitignore 2"&&+run_check_ignore"globalone a/globalthree a/globaltwo"+'++test_expect_success'global ignore''+enable_global_excludes&&+cat<<-EOF >"$HOME/expected" &&+globalone:excludedglobalone$global_excludes1+globalthree:excludedglobalthree$global_excludes3+a/globalthree:excluded*threea/.gitignore2+globaltwo:included!globaltwo$global_excludes2+EOF+run_check_ignore"globalone globalthree a/globalthree globaltwo"+'++test_expect_success'--stdin''+cat<<-EOF>in.txt&&+one+a/one+a/b/on+a/b/one+a/b/two+a/b/twooo+globaltwo+a/globaltwo+a/b/globaltwo+b/globaltwo+EOF+cat<<-EOF >"$HOME/expected" &&+one:excludedone.gitignore1+a/one:excludedone.gitignore1+a/b/on:included!on*a/b/.gitignore8+a/b/one:included!on*a/b/.gitignore8+a/b/two:included!twoa/b/.gitignore9+a/b/twooo:excludedtwo*a/.gitignore1+globaltwo:included!globaltwo$global_excludes2+a/globaltwo:included!globaltwo$global_excludes2+a/b/globaltwo:included!globaltwo$global_excludes2+b/globaltwo:included!globaltwo$global_excludes2+EOF+run_check_ignore--stdin<in.txt+'++test_expect_success'--stdin -z''+tr"\n""\0"<in.txt|run_check_ignore"--stdin -z"+'++test_expect_success'-z --stdin''+tr"\n""\0"<in.txt|run_check_ignore"-z --stdin"+'++test_expect_success'--stdin from subdirectory''+cat<<-EOF>in.txt&&+../one+one+b/on+b/one+b/two+b/twooo+../globaltwo+globaltwo+b/globaltwo+../b/globaltwo+EOF+cat<<-EOF >"$HOME/expected" &&+../one:excludedone.gitignore1+one:excludedone.gitignore1+b/on:included!on*a/b/.gitignore8+b/one:included!on*a/b/.gitignore8+b/two:included!twoa/b/.gitignore9+b/twooo:excludedtwo*a/.gitignore1+../globaltwo:included!globaltwo$global_excludes2+globaltwo:included!globaltwo$global_excludes2+b/globaltwo:included!globaltwo$global_excludes2+../b/globaltwo:included!globaltwo$global_excludes2+EOF+(+cda&&+run_check_ignore--stdin<../in.txt+)+'++test_expect_success'--stdin -z from subdirectory''+tr"\n""\0"<in.txt|(cda&&run_check_ignore"--stdin -z")+'++test_expect_success'-z --stdin from subdirectory''+tr"\n""\0"<in.txt|(cda&&run_check_ignore"-z --stdin")+'+++test_done
@@ -9,8 +9,11 @@ Data structure -------------- `struct dir_struct` structure is used to pass directory traversal-options to the library and to record the paths discovered. The notable-options are:+options to the library and to record the paths discovered. A single+`struct dir_struct` is used regardless of whether or not the traversal+recursively descends into subdirectories.++The notable options are: `exclude_per_dir`::
@@ -39,7 +42,7 @@ options are: If set, recurse into a directory that looks like a git directory. Otherwise it is shown as a directory.-The result of the enumeration is left in these fields::+The result of the enumeration is left in these fields: `entries[]`::
@@ -449,6 +451,10 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)die("cannot use %s as an exclude file",fname);}+/*+*Loadstheper-directoryexcludelistforthesubstringofbase+*whichhasacharlengthofbaselen.+*/staticvoidprep_exclude(structdir_struct*dir,constchar*base,intbaselen){structexclude_list*el;
@@ -459,7 +465,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)(baselen+strlen(dir->exclude_per_dir)>=PATH_MAX))return;/* too long a path -- ignore */-/* Pop the ones that are not the prefix of the path being checked. */+/* Pop the directories that are not the prefix of the path being checked. */el=&dir->exclude_list[EXC_DIRS];while((stk=dir->exclude_stack)!=NULL){if(stk->baselen<=baselen&&
@@ -26,9 +34,15 @@ struct exclude_list {}**excludes;};+/*+*Thecontentsoftheper-directoryexcludefilesarelazilyreadon+*demandandthencachedinmemory,oneperexclude_stackstruct,in+*ordertoavoidopeningandparsingeachoneeverytimethat+*directoryistraversed.+*/structexclude_stack{-structexclude_stack*prev;-char*filebuf;+structexclude_stack*prev;/* the struct exclude_stack for the parent directory */+char*filebuf;/* remember pointer to per-directory exclude file contents so we can free() */intbaselen;intexclude_ix;};
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:37
7c4c97c0ac turned the flags in struct dir_struct into a single bitfield
variable, but forgot to update this document.
Signed-off-by: Adam Spiers <redacted>
---
Documentation/technical/api-directory-listing.txt | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
@@ -17,24 +17,24 @@ options are: The name of the file to be read in each directory for excluded files (typically `.gitignore`).-`collect_ignored`::+`flags`::- Include paths that are to be excluded in the result.+ A bit-field of options:-`show_ignored`::+`DIR_SHOW_IGNORED`::: The traversal is for finding just ignored files, not unignored files.-`show_other_directories`::+`DIR_SHOW_OTHER_DIRECTORIES`::: Include a directory that is not tracked.-`hide_empty_directories`::+`DIR_HIDE_EMPTY_DIRECTORIES`::: Do not include a directory that is not tracked and is empty.-`no_gitlinks`::+`DIR_NO_GITLINKS`::: If set, recurse into a directory that looks like a git directory. Otherwise it is shown as a directory.
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:37
The excluded function uses a new helper function called
exclude_from_list_1() to perform the inner loop over all of the
exclude patterns. The helper just tells us whether the path is
included, excluded, or undecided.
However, it may be useful to know _which_ pattern was
triggered. So let's pass out the entire exclude match,
which contains the status information we were already
passing out.
Further patches can make use of this.
This is a modified forward port of a patch from 2009 by Jeff King:
http://article.gmane.org/gmane.comp.version-control.git/108815
Signed-off-by: Adam Spiers <redacted>
---
dir.c | 40 +++++++++++++++++++++++++++++-----------
1 file changed, 29 insertions(+), 11 deletions(-)
@@ -509,22 +509,24 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)dir->basebuf[baselen]='\0';}-/* Scan the list and let the last match determine the fate.-*Return1forexclude,0forincludeand-1forundecided.+/*+*Scanthegivenexcludelistinreversetoseewhetherpathname+*shouldbeignored.Thefirstmatch(i.e.thelastonthelist),if+*any,determinesthefate.Returnstheexclude_listelementwhich+*matched,orNULLforundecided.*/-intexcluded_from_list(constchar*pathname,-intpathlen,constchar*basename,int*dtype,-structexclude_list*el)+structexclude*excluded_from_list_1(constchar*pathname,intpathlen,+constchar*basename,int*dtype,+structexclude_list*el){inti;if(!el->nr)-return-1;/* undefined */+returnNULL;/* undefined */for(i=el->nr-1;0<=i;i--){structexclude*x=el->excludes[i];constchar*name,*exclude=x->pattern;-intto_exclude=x->to_exclude;intnamelen,prefix=x->nowildcardlen;if(x->flags&EXC_FLAG_MUSTBEDIR){
@@ -538,14 +540,14 @@ int excluded_from_list(const char *pathname,/* match basename */if(prefix==x->patternlen){if(!strcmp_icase(exclude,basename))-returnto_exclude;+returnx;}elseif(x->flags&EXC_FLAG_ENDSWITH){if(x->patternlen-1<=pathlen&&!strcmp_icase(exclude+1,pathname+pathlen-x->patternlen+1))-returnto_exclude;+returnx;}else{if(fnmatch_icase(exclude,basename,0)==0)-returnto_exclude;+returnx;}continue;}
@@ -79,4 +79,6 @@ marked. If you to exclude files, make sure you have loaded index first. * Use `dir.entries[]`.+* Call `free_directory()` when none of the contained elements are no longer in use.+ (JC)
@@ -454,6 +454,12 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)die("cannot use %s as an exclude file",fname);}+staticvoidfree_exclude_stack(structexclude_stack*stk)+{+free(stk->filebuf);+free(stk);+}+/**Loadstheper-directoryexcludelistforthesubstringofbase*whichhasacharlengthofbaselen.
@@ -479,8 +485,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)structexclude*exclude=el->excludes[--el->nr];free(exclude);}-free(stk->filebuf);-free(stk);+free_exclude_stack(stk);}/* Read from the parent directories and push them down. */
On Sun, Sep 2, 2012 at 7:12 AM, Adam Spiers [off-list ref] wrote:
This works in a similar manner to git-check-attr. Some code
was reused from add.c by refactoring out into pathspec.c.
Thanks, comments from a quick glance. First of all, can we make it
work (or share code) with .gitattributes? We may need to debug
.gitattributes as well as .gitignore. A common command would be nice.
Also --quiet option, where check-ignore returns 0 if the given path is
ignored, 1 otherwise?
+OUTPUT
+------
+
+The output is a series of lines of the form:
+
+<path> COLON SP <type> SP <pattern> SP <source> SP <position> LF
+
+<path> is the path of a file being queried, <type> is either
+'excluded' or 'included' (for patterns prefixed with '!'), <pattern>
+is the matching pattern, <source> is the pattern's source file (either
+as an absolute path or relative to the repository root), and
+<position> is the position of the pattern within that source.
I think we should have a few levels of verbosity.
- The --quiet I already mention above.
- If many paths are given, then perhaps we could print ignored paths
(no extra info).
- Going to the next level, we could print path and the the location
of the final exclude/include rule (file and line number).
- For debugging, given one path, we print all the rules that are
applied to it, which may help understand how/why it goes wrong.
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:37
Hi there,
Firstly, thanks for the quick feedback!
On Sun, Sep 2, 2012 at 11:41 AM, Nguyen Thai Ngoc Duy [off-list ref] wrote:
On Sun, Sep 2, 2012 at 7:12 AM, Adam Spiers [off-list ref] wrote:
quoted
This works in a similar manner to git-check-attr. Some code
was reused from add.c by refactoring out into pathspec.c.
Thanks, comments from a quick glance. First of all, can we make it
work (or share code) with .gitattributes? We may need to debug
.gitattributes as well as .gitignore. A common command would be nice.
I'm no expert on .gitattributes and check-attr, but AFAICS, all the
opportunities to share code in the plumbing and front-end seem to be
taken already, e.g. the directory traversal and path handling. The
CLI argument parsing is necessarily different because check-attr
requires a list of attributes as well as a list of files, and of
course the output routines have to be different too.
The only opportunity for code reuse which I saw but /didn't/ take was
around the --stdin line parsing code which is duplicated between:
check_attr_stdin_paths
check_ignore_stdin_paths
cmd_checkout_index
cmd_update_index
hash_stdin_paths
I attempted to refactor these, but quickly realised that due to the
lack of proper closures in C, the overheads and complexity incurred by
performing such a refactoring probably outweighed the benefits, so I
gave up on the idea.
Having said that, I'm totally open to suggestions if you can spot
other places where code could be reused :)
Also --quiet option, where check-ignore returns 0 if the given path is
ignored, 1 otherwise?
I considered that, but couldn't think of appropriate behaviour when
multiple paths are given, so in the end I decided to remain consistent
with check-attr, which always returns 0. But I'm happy to change it
if you can think of a more useful behaviour. For example we could
have a --count option which produces no output but has an exit status
corresponding to the number of ignored files.
- If many paths are given, then perhaps we could print ignored paths
(no extra info).
How is this different to git ls-files -i -o ?
- Going to the next level, we could print path and the the location
of the final exclude/include rule (file and line number).
That's the current behaviour, and I believe it covers the most common
use case.
- For debugging, given one path, we print all the rules that are
applied to it, which may help understand how/why it goes wrong.
That would be nice, but I'm not sure it's a tremendously common use
case. Could you think of a scenario in which it would be useful? I
guess it could be done by adding a new DIR_DEBUG_IGNORED flag to
dir_struct which would make the exclude matcher functions collect all
matching patterns, rather than just returning the first one. This in
turn would require another field for collecting all matched patterns.
I don't think we really need NEED_WORK_TREE here. .gitignore can be
read from index only.
I thought about that, but in the end I decided it probably didn't make
sense, because none of the exclude matching routines match against the
index - they all match against the working tree and core.excludesfile.
This would also require changing the matching logic to honor the index,
but I didn't see the benefit in doing that, since all operations which
involve excludes (add, status, etc.) relate to a work tree.
But as with all of the above, please don't hesitate to point out if
I've missed something. You guys are the experts, not me ;-)
Thanks again,
Adam
From: Philip Oakley <hidden> Date: 2016-06-15 22:54:37
From: "Adam Spiers" <redacted>
Sent: Sunday, September 02, 2012 1:12 AM
Subject: [PATCH 6/9] For each exclude pattern, store information about
where it came from
For exclude patterns read in from files, the filename is stored
together
with the corresponding line number (counting starting at 1).
For exclude patterns provided on the command line, the sequence number
is negative, with counting starting at -1, so for example the 2nd
pattern provided via --exclude would be numbered -2. This allows any
future consumers of that data to easily distinguish between exclude
patterns from files vs. from the CLI.
Signed-off-by: Adam Spiers <redacted>
---
builtin/clean.c | 2 +-
builtin/ls-files.c | 3 ++-
dir.c | 25 +++++++++++++++++++------
dir.h | 5 ++++-
4 files changed, 26 insertions(+), 9 deletions(-)
const char *base, int baselen)
memcpy(dir->basebuf + current, base + current,
stk->baselen - current);
strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
- add_excludes_from_file_to_list(dir->basebuf,
+
+ /* dir->basebuf gets reused by the traversal, but we
+ * need fname to remain unchanged to ensure the src
+ * member of each struct exclude correctly back-references
+ * its source file.
+ */
+ char *fname = strdup(dir->basebuf);
+
+ add_excludes_from_file_to_list(fname,
dir->basebuf, stk->baselen,
&stk->filebuf, el, 1);
dir->exclude_stack = stk;
@@ -31,6 +31,9 @@ struct exclude_list {intbaselen;intto_exclude;intflags;+constchar*src;+intsrcpos;/* counting starts from 1 for line numbers in ignore
files,
+ and from -1 decrementing for patterns from CLI (--exclude) */
} **excludes;
};
@@ -123,7 +126,7 @@ extern int add_excludes_from_file_to_list(const
char *fname, const char *base, i
char **buf_p, struct exclude_list *el, int check_index);
extern void add_excludes_from_file(struct dir_struct *, const char
*fname);
extern void add_exclude(const char *string, const char *base,
- int baselen, struct exclude_list *el);
+ int baselen, struct exclude_list *el, const char *src, int srcpos);
extern void free_excludes(struct exclude_list *el);
extern int file_exists(const char *);
--
1.7.12.155.ge5750d5.dirty
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
-----
No virus found in this message.
Checked by AVG - www.avg.com
Version: 2012.0.2197 / Virus Database: 2437/5240 - Release Date:
09/01/12
Also --quiet option, where check-ignore returns 0 if the given path is
ignored, 1 otherwise?
I agree that multiple paths are problematic.
We could error out if multiple paths are given with --quiet until we
figure out what the useful result would be in such a case, and still
give a useful answer to callers that feed a single path, though.
That may encourage suboptimal coding to casual Porcelain writers,
i.e. it would allow
for path in $paths
do
if git check-ignore -q "$path"
then
do something to "$path"
fi
done
even though we would rather want to encourage
git check-ignore --name-only $paths |
while read path
do
do something to "$path"
done
But from lay-scriptors' point of view, being able to easily write a
script (even though it may be inefficient) to do the job at hand is
far better than having to give up writing one because the tool does
not allow easy-and-stupid scripting, so it is not exactly a huge
downside.
quoted
- If many paths are given, then perhaps we could print ignored paths
(no extra info).
How is this different to git ls-files -i -o ?
I personally think the parts of ls-files that deal with paths not in
the index outlived its usefulness ;-) and users deserve to be given
a better UI.
quoted
- Going to the next level, we could print path and the the location
of the final exclude/include rule (file and line number).
That's the current behaviour, and I believe it covers the most common
use case.
Yes; I have a reservation on your output format, though.
quoted
- For debugging, given one path, we print all the rules that are
applied to it, which may help understand how/why it goes wrong.
I do not think that would be terribly useful. Maybe for people who
are learning how dir.c internally works, but not for people who are
trying to improve the set of .gitignore files in their project.
I thought about that, but in the end I decided it probably didn't make
sense, because none of the exclude matching routines match against the
index - they all match against the working tree and core.excludesfile.
This would also require changing the matching logic to honor the index,
but I didn't see the benefit in doing that, since all operations which
involve excludes (add, status, etc.) relate to a work tree.
The mechanism primarily is to see if a path in the working tree is a
cruft or a valuable still to be added; I am OK with NEED_WORK_TREE;
when we have a useful case to run this in a bare repository, we can
lift it.
As with the "what to do with multiple paths and -q", it is better to
start with feature set to cover only the known or easily anticipated
use cases, rejecting the cases for which good semantics are not
thought out.
An alternative would be a code that operates sanely only for known
or anticipated cases and do random things with irrational semantics
in other cases, and people start relying on the irrational behaviour
without realizing their input and the behaviour they are seeing are
not something that the feature is designed to for, but whatever the
code with loose precondition checking happens to do. We do not want
to repeat that kind of mistake, which is hard to fix in future
versions.
From: Junio C Hamano <hidden> Date: 2016-06-15 22:54:37
Adam Spiers [off-list ref] writes:
+OPTIONS
+-------
+--stdin::
+ Read file names from stdin instead of from the command-line.
+
+-z::
+ Only meaningful with `--stdin`; paths are separated with a
+ NUL character instead of a linefeed character.
On input, or on output, or both?
The answer should be "both", otherwise you cannot safely handle
paths with funny character in your script, even if you wanted to.
Which means that this cannot only be meaningful with "--stdin", I
think.
+OUTPUT
+------
+
+The output is a series of lines of the form:
+
+<path> COLON SP <type> SP <pattern> SP <source> SP <position> LF
+
+<path> is the path of a file being queried, <type> is either
+'excluded' or 'included' (for patterns prefixed with '!'), <pattern>
+is the matching pattern, <source> is the pattern's source file (either
+as an absolute path or relative to the repository root), and
+<position> is the position of the pattern within that source.
Let's step back a bit and think what this command is about. What is
the reason why the user wants to run "check-ignore $path" in the
first place? I think there are two (or three, depending on how you
count).
(1) You have one (or more) paths at hand. You want to know if it
is (or some of them are) ignored, but you do not particularly
care how they are ignored. Think of implementing your own "git
add" as a script.
(2) You have one or more paths that are ignored but do not want
them to be, and want to find out why they are.
For the former, your script may want to see the paths sifted into
"ignored" bin and "not-ignored" bin, so
git check-ignore [-z] --name-only $paths
that gives you only the paths without any reason is more useful.
You also may want the opposite (show only paths not ignored), but
that can be computed easily by the script, so it is of lessor
importance.
For the latter, you are debugging the set of exclude sources and
want to learn where the decision to exclude it comes from. For that
kind of use, it would be more useful if the output mimicked error
messages from the compilers and output from "grep -n" to show the
source, e.g.
.gitignore:13:/git-am git-am
Emacs users can use "M-x grep<RET>git check-ignore -v git-am<RET>",
see the output, and find the hit in its output (I would imagine vim
would have a similar feature). The output format would be something
like:
<source> <COLON> <linenum> <COLON> <pattern> <HT> <pathname>
I do not think you need excluded/included <type> as a separate item
in the non "-z" output, as it should be clear from the <pattern>.
Substitute "<cmdline>" (literally) as source for patterns obtained
from the command line.
I would also suggest to
(1) make --name-only the default (i.e. no need to have the
"--name-only" option);
(2) give the version that mimicks "grep -n" when "-v|--verbose" is
given; and
(3) support "--quiet"; the command would exit with status 0 if
_any_ of the paths given is ignored, or status 1 if none of the
paths is ignored (or error out with die() when --quiet and
multiple paths are given).
Regarding "-z" (for script consumption), I do not object to the
broken down format, e.g., "check-ignore -z -v" may give a sequence
of
<pathname> NUL <type> NUL <pattern> NUL <source> NUL <position> NUL
while "check-ignore -z" would give a sequence of
<pathname> NUL
From: Junio C Hamano <hidden> Date: 2016-06-15 22:54:37
Adam Spiers [off-list ref] writes:
'el' is only *slightly* less cryptic, but is already used as the
variable name for a struct exclude_list pointer in numerous other
places, so this reduces the number of cryptic variable names in use by
one :-)
The name originally meant to mean "to which element of the array
dir_struct.exclude_list[] are we adding this entry?" but I agree
"el" that stands for ExcludeList would be a better name.
Often we use "el" (or "elem") for elements of an iterable we are
iterating on in a loop, and the name of the iterable does not have
to be EsomethingLsomething, by the way. Because no existing use of
"el" in this file is of that kind, I do not think this change
introduces new confusion to the code.
Thanks. I wish there are more people like you ;-)
As to styles, I spotted only three kinds of "Huh?":
* do not initialise statics to 0 or NULL, e.g.
-static int exclude_args = 0;
+static int exclude_args;
* avoid unnnecessary braces {} around single statement blocks, e.g.
-if (exclude) {
+if (exclude)
return exclude;
-}
* else should follow close brace '}' of if clause, e.g.
if (...) {
...
-}
-else {
+} else {
...
For reviews on substance, please see other messages from me.
From: Philip Oakley <hidden> Date: 2016-06-15 22:54:37
From: "Junio C Hamano" <redacted>
Sent: Sunday, September 02, 2012 8:02 PM
"Philip Oakley" [off-list ref] writes:
quoted
Is there a way to identify the config core.excludesfile if present?
i.e. that it is from that config variable, rather than directory
traversal.
If the code handles $GIT_DIR/info/exclude then that configuration
would also be handled the same way, no?
Probably not. The $GIT_DIR/info/exclude is directly a path, while the
core.excludesfile could point anywhere. This assumes the path to the
relevant ignore file is shown.
Given the suggested report format in the Documentation, this path could
be reported as 'coreexclude', not just an 'exclude'.
If I've understood the regular code correctly, the core.excludesfile is
always at one end of the exclude struct so should be easy to check at
that position.
On Sun, Sep 2, 2012 at 9:50 PM, Adam Spiers [off-list ref] wrote:
I'm no expert on .gitattributes and check-attr, but AFAICS, all the
opportunities to share code in the plumbing and front-end seem to be
taken already, e.g. the directory traversal and path handling. The
CLI argument parsing is necessarily different because check-attr
requires a list of attributes as well as a list of files, and of
course the output routines have to be different too.
The only opportunity for code reuse which I saw but /didn't/ take was
around the --stdin line parsing code which is duplicated between:
check_attr_stdin_paths
check_ignore_stdin_paths
cmd_checkout_index
cmd_update_index
hash_stdin_paths
I attempted to refactor these, but quickly realised that due to the
lack of proper closures in C, the overheads and complexity incurred by
performing such a refactoring probably outweighed the benefits, so I
gave up on the idea.
Having said that, I'm totally open to suggestions if you can spot
other places where code could be reused :)
Yeah. That was my impression too. I just hoped a new set of eyes might
discover something ;) At lease we could prepare the output format that
can be reused (maybe with little changes) for check-attr debugging if
it comes later. Or make this command part of check-attr..
quoted
Also --quiet option, where check-ignore returns 0 if the given path is
ignored, 1 otherwise?
I considered that, but couldn't think of appropriate behaviour when
multiple paths are given, so in the end I decided to remain consistent
with check-attr, which always returns 0. But I'm happy to change it
if you can think of a more useful behaviour. For example we could
have a --count option which produces no output but has an exit status
corresponding to the number of ignored files.
We could take this opportunity to kill "add --ignore-missing", which
is basically .gitignore checking and it accepts multiple paths, I
think.
quoted
- If many paths are given, then perhaps we could print ignored paths
(no extra info).
How is this different to git ls-files -i -o ?
I think ls-files requires real files on working directory, but
check-ignore can deal with just non-existing paths.
quoted
- For debugging, given one path, we print all the rules that are
applied to it, which may help understand how/why it goes wrong.
That would be nice, but I'm not sure it's a tremendously common use
case. Could you think of a scenario in which it would be useful? I
guess it could be done by adding a new DIR_DEBUG_IGNORED flag to
dir_struct which would make the exclude matcher functions collect all
matching patterns, rather than just returning the first one. This in
turn would require another field for collecting all matched patterns.
Mixing include/exclude ignore rules multiple times could be hard to
figure out what goes wrong. But as we haven't seen an actual use case
yet, just leave it out.
quoted
I don't think we really need NEED_WORK_TREE here. .gitignore can be
read from index only.
I thought about that, but in the end I decided it probably didn't make
sense, because none of the exclude matching routines match against the
index - they all match against the working tree and core.excludesfile.
This would also require changing the matching logic to honor the index,
but I didn't see the benefit in doing that, since all operations which
involve excludes (add, status, etc.) relate to a work tree.
But as with all of the above, please don't hesitate to point out if
I've missed something. You guys are the experts, not me ;-)
Again I was thinking that check-ignore could work with imaginary paths
and could be used by scripts. If a script just wants to check certain
paths are excluded, it should not need to move to working directory
(though it probably is in working directory most of the time).
--
Duy
@@ -509,22 +509,24 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)dir->basebuf[baselen]='\0';}-/* Scan the list and let the last match determine the fate.-*Return1forexclude,0forincludeand-1forundecided.+/*+*Scanthegivenexcludelistinreversetoseewhetherpathname+*shouldbeignored.Thefirstmatch(i.e.thelastonthelist),if+*any,determinesthefate.Returnstheexclude_listelementwhich+*matched,orNULLforundecided.*/-intexcluded_from_list(constchar*pathname,-intpathlen,constchar*basename,int*dtype,-structexclude_list*el)+structexclude*excluded_from_list_1(constchar*pathname,intpathlen,+constchar*basename,int*dtype,+structexclude_list*el){inti;
On Sun, Sep 2, 2012 at 7:12 AM, Adam Spiers [off-list ref] wrote:
quoted hunk
--- a/builtin/add.c+++ b/builtin/add.c
@@ -273,7 +273,7 @@ static int add_files(struct dir_struct *dir, int flags)fprintf(stderr,_(ignore_error));for(i=0;i<dir->ignored_nr;i++)fprintf(stderr,"%s\n",dir->ignored[i]->name);-fprintf(stderr,_("Use -f if you really want to add them.\n"));+fprintf(stderr,_("Use -f if you really want to add them, or git check-ignore to see\nwhy they're ignored.\n"));die(_("no files added"));}
You may want to mark help strings ("read file names from stdin" and
"input paths... null character") and check_ignore_usage[] for
translation. Just wrap those strings with N_() and you'll be fine. For
similar changes, check out nd/i18n-parseopt-help on branch 'pu'.
These English words "excluded" and "included" make the translator me
want to translate them. But they could be the markers for scripts, so
they may not be translated. How about using non alphanumeric letters
instead?
+static void check_ignore(const char *prefix, const char **pathspec)
+{
+ struct dir_struct dir;
+ const char *path;
+ char *seen = NULL;
+
+ /* read_cache() is only necessary so we can watch out for submodules. */
+ if (read_cache() < 0)
+ die(_("index file corrupt"));
+
+ memset(&dir, 0, sizeof(dir));
+ dir.flags |= DIR_COLLECT_IGNORED;
+ setup_standard_excludes(&dir);
You should support ignore rules from files and command line arguments
too, like ls-files. For quick testing.
Interesting. We have usage_msg_opt() in parse-options.c, but it's more
verbose. Perhaps this function should be moved to parse-options.c
because it may be useful to other commands as well?
--
Duy
As to styles, I spotted only three kinds of "Huh?":
* do not initialise statics to 0 or NULL, e.g.
-static int exclude_args = 0;
+static int exclude_args;
* avoid unnnecessary braces {} around single statement blocks, e.g.
-if (exclude) {
+if (exclude)
return exclude;
-}
* else should follow close brace '}' of if clause, e.g.
if (...) {
...
-}
-else {
+} else {
...
OK thanks, I will fix these and also submit a patch for CodingGuidelines.
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:38
On Sun, Sep 2, 2012 at 11:36 PM, Philip Oakley [off-list ref] wrote:
From: "Junio C Hamano" <redacted>
Sent: Sunday, September 02, 2012 8:02 PM
quoted
"Philip Oakley" [off-list ref] writes:
quoted
Is there a way to identify the config core.excludesfile if present?
i.e. that it is from that config variable, rather than directory
traversal.
Yes, the output of git check-ignore includes the source file, so you
can easily see whether the ignore originated from a per-directory
exclude or from core.excludesfile. One giveaway is that the former
is an absolute path, and the latter are all relative.
quoted
If the code handles $GIT_DIR/info/exclude then that configuration
would also be handled the same way, no?
Yes, they are both handled via setup_standard_excludes().
If I've understood the regular code correctly, the core.excludesfile is
always at one end of the exclude struct so should be easy to check at that
position.
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:39
On Sun, Sep 2, 2012 at 9:35 PM, Junio C Hamano [off-list ref] wrote:
* avoid unnnecessary braces {} around single statement blocks, e.g.
-if (exclude) {
+if (exclude)
return exclude;
-}
* else should follow close brace '}' of if clause, e.g.
if (...) {
...
-}
-else {
+} else {
...
What about when the if clause requires braces but the else clause
doesn't? Should it be
if (...) {
...;
...;
} else
...;
or
if (...) {
...;
...;
}
else
...;
?
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:42
On Tue, Sep 04, 2012 at 08:06:12PM +0700, Nguyen Thai Ngoc Duy wrote:
On Sun, Sep 2, 2012 at 7:12 AM, Adam Spiers [off-list ref] wrote:
quoted
--- a/builtin/add.c+++ b/builtin/add.c
@@ -273,7 +273,7 @@ static int add_files(struct dir_struct *dir, int flags)fprintf(stderr,_(ignore_error));for(i=0;i<dir->ignored_nr;i++)fprintf(stderr,"%s\n",dir->ignored[i]->name);-fprintf(stderr,_("Use -f if you really want to add them.\n"));+fprintf(stderr,_("Use -f if you really want to add them, or git check-ignore to see\nwhy they're ignored.\n"));die(_("no files added"));}
String too long (> 80 chars).
You mean the line of code is too long, or the argument to _(), or
both? I didn't like this either, but I saw that builtin/checkout.c
already did something similar twice, and I wasn't sure how else to do
it. Suggestions gratefully received.
You may want to mark help strings ("read file names from stdin" and
"input paths... null character") and check_ignore_usage[] for
translation. Just wrap those strings with N_() and you'll be fine. For
similar changes, check out nd/i18n-parseopt-help on branch 'pu'.
Thanks, I'll do that.
[snipped discussion of "include" / "exclude" which already continued elsewhere]
quoted
+static void check_ignore(const char *prefix, const char **pathspec)
+{
+ struct dir_struct dir;
+ const char *path;
+ char *seen = NULL;
+
+ /* read_cache() is only necessary so we can watch out for submodules. */
+ if (read_cache() < 0)
+ die(_("index file corrupt"));
+
+ memset(&dir, 0, sizeof(dir));
+ dir.flags |= DIR_COLLECT_IGNORED;
+ setup_standard_excludes(&dir);
You should support ignore rules from files and command line arguments
too, like ls-files. For quick testing.
You mean --exclude, --exclude-from, and --exclude-per-directory?
Sure, although I have limited time right now, so maybe these could be
added in a later iteration?
Interesting. We have usage_msg_opt() in parse-options.c, but it's more
verbose. Perhaps this function should be moved to parse-options.c
because it may be useful to other commands as well?
On Mon, Sep 10, 2012 at 6:09 PM, Adam Spiers [off-list ref] wrote:
quoted
quoted
fprintf(stderr, "%s\n", dir->ignored[i]->name);
- fprintf(stderr, _("Use -f if you really want to add them.\n"));
+ fprintf(stderr, _("Use -f if you really want to add them, or git check-ignore to see\nwhy they're ignored.\n"));
die(_("no files added"));
}
String too long (> 80 chars).
You mean the line of code is too long, or the argument to _(), or
both? I didn't like this either, but I saw that builtin/checkout.c
already did something similar twice, and I wasn't sure how else to do
it. Suggestions gratefully received.
I don't rememeber :( I might mean the output because I missed "\n" in
the middle. At least you can split the string in to at "\n" to make it
resemble output.
quoted
You should support ignore rules from files and command line arguments
too, like ls-files. For quick testing.
You mean --exclude, --exclude-from, and --exclude-per-directory?
Sure, although I have limited time right now, so maybe these could be
added in a later iteration?
Sure, no problem. It's not hard to add them anyway (I think).
--
Duy
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:49
7c4c97c0ac turned the flags in struct dir_struct into a single bitfield
variable, but forgot to update this document.
Signed-off-by: Adam Spiers <redacted>
---
Documentation/technical/api-directory-listing.txt | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
@@ -17,24 +17,24 @@ options are: The name of the file to be read in each directory for excluded files (typically `.gitignore`).-`collect_ignored`::+`flags`::- Include paths that are to be excluded in the result.+ A bit-field of options:-`show_ignored`::+`DIR_SHOW_IGNORED`::: The traversal is for finding just ignored files, not unignored files.-`show_other_directories`::+`DIR_SHOW_OTHER_DIRECTORIES`::: Include a directory that is not tracked.-`hide_empty_directories`::+`DIR_HIDE_EMPTY_DIRECTORIES`::: Do not include a directory that is not tracked and is empty.-`no_gitlinks`::+`DIR_NO_GITLINKS`::: If set, recurse into a directory that looks like a git directory. Otherwise it is shown as a directory.
@@ -514,9 +514,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)/* Scan the list and let the last match determine the fate.*Return1forexclude,0forincludeand-1forundecided.*/-intexcluded_from_list(constchar*pathname,-intpathlen,constchar*basename,int*dtype,-structexclude_list*el)+intis_excluded_from_list(constchar*pathname,+intpathlen,constchar*basename,int*dtype,+structexclude_list*el){inti;
@@ -596,8 +596,9 @@ static int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)prep_exclude(dir,pathname,basename-pathname);for(st=EXC_CMDL;st<=EXC_FILE;st++){-switch(excluded_from_list(pathname,pathlen,basename,-dtype_p,&dir->exclude_list[st])){+switch(is_excluded_from_list(pathname,pathlen,+basename,dtype_p,+&dir->exclude_list[st])){case0:return0;case1:
@@ -98,8 +98,8 @@ extern int within_depth(const char *name, int namelen, int depth, int max_depth)externintfill_directory(structdir_struct*dir,constchar**pathspec);externintread_directory(structdir_struct*,constchar*path,intlen,constchar**pathspec);-externintexcluded_from_list(constchar*pathname,intpathlen,constchar*basename,-int*dtype,structexclude_list*el);+externintis_excluded_from_list(constchar*pathname,intpathlen,constchar*basename,+int*dtype,structexclude_list*el);structdir_entry*dir_add_ignored(structdir_struct*dir,constchar*pathname,intlen);/*
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:49
This is a re-vamp of my original check-ignore series, which aims to
address all the feedback which was raised in the first round of
reviews. The most notable changes are the CLI options and output
formats as suggested by Junio and Nguyễn; now there are three levels
of verbosity: --quiet, default, and --verbose. -z also now affects
the output and so is now compatible with the --stdin optin.
Some commits have been broken into smaller pieces to facilitate easier
reviews, and based on an earlier discussion, three exclude functions
have been given an 'is_' prefix to clarify their boolean nature. The
helper functions extracted from these three now have more meaningful
names rather than just a '_1' suffix.
Other minor issues, such as inconsistent coding style, have been
fixed, and the modification to the output text in add.c has been
scrapped.
It has been rebased on the latest master, and passed a full test run.
Adam Spiers (14):
Update directory listing API doc to match code
Improve documentation and comments regarding directory traversal API
Rename cryptic 'which' variable to more consistent name
Rename path_excluded() to is_path_excluded()
Rename excluded_from_list() to is_excluded_from_list()
Rename excluded() to is_excluded()
Refactor is_excluded_from_list()
Refactor is_excluded()
Refactor is_path_excluded()
For each exclude pattern, store information about where it came from
Refactor treat_gitlinks()
Extract some useful pathspec handling code from builtin/add.c into a
library
Provide free_directory() for reclaiming dir_struct memory
Add git-check-ignore sub-command
.gitignore | 1 +
Documentation/git-check-ignore.txt | 85 ++++
Documentation/gitignore.txt | 6 +-
Documentation/technical/api-directory-listing.txt | 23 +-
Makefile | 3 +
attr.c | 2 +-
builtin.h | 1 +
builtin/add.c | 84 +---
builtin/check-ignore.c | 167 ++++++
builtin/clean.c | 2 +-
builtin/ls-files.c | 5 +-
command-list.txt | 1 +
contrib/completion/git-completion.bash | 1 +
dir.c | 191 +++++--
dir.h | 47 +-
git.c | 1 +
pathspec.c | 97 ++++
pathspec.h | 6 +
t/t0007-ignores.sh | 587 ++++++++++++++++++++++
t/t9902-completion.sh | 24 +-
unpack-trees.c | 10 +-
21 files changed, 1182 insertions(+), 162 deletions(-)
create mode 100644 Documentation/git-check-ignore.txt
create mode 100644 builtin/check-ignore.c
create mode 100644 pathspec.c
create mode 100644 pathspec.h
create mode 100755 t/t0007-ignores.sh
--
1.7.12.147.g6d168f4
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:49
The excluded function uses a new helper function called
last_exclude_matching_from_list() to perform the inner loop over all of
the exclude patterns. The helper just tells us whether the path is
included, excluded, or undecided.
However, it may be useful to know _which_ pattern was triggered. So
let's pass out the entire exclude match, which contains the status
information we were already passing out.
Further patches can make use of this.
This is a modified forward port of a patch from 2009 by Jeff King:
http://article.gmane.org/gmane.comp.version-control.git/108815
Signed-off-by: Adam Spiers <redacted>
---
dir.c | 41 ++++++++++++++++++++++++++++++-----------
1 file changed, 30 insertions(+), 11 deletions(-)
@@ -511,22 +511,26 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)dir->basebuf[baselen]='\0';}-/* Scan the list and let the last match determine the fate.-*Return1forexclude,0forincludeand-1forundecided.+/*+*Scanthegivenexcludelistinreversetoseewhetherpathname+*shouldbeignored.Thefirstmatch(i.e.thelastonthelist),if+*any,determinesthefate.Returnstheexclude_listelementwhich+*matched,orNULLforundecided.*/-intis_excluded_from_list(constchar*pathname,-intpathlen,constchar*basename,int*dtype,-structexclude_list*el)+staticstructexclude*last_exclude_matching_from_list(constchar*pathname,+intpathlen,+constchar*basename,+int*dtype,+structexclude_list*el){inti;if(!el->nr)-return-1;/* undefined */+returnNULL;/* undefined */for(i=el->nr-1;0<=i;i--){structexclude*x=el->excludes[i];constchar*name,*exclude=x->pattern;-intto_exclude=x->to_exclude;intnamelen,prefix=x->nowildcardlen;if(x->flags&EXC_FLAG_MUSTBEDIR){
@@ -540,14 +544,14 @@ int is_excluded_from_list(const char *pathname,/* match basename */if(prefix==x->patternlen){if(!strcmp_icase(exclude,basename))-returnto_exclude;+returnx;}elseif(x->flags&EXC_FLAG_ENDSWITH){if(x->patternlen-1<=pathlen&&!strcmp_icase(exclude+1,pathname+pathlen-x->patternlen+1))-returnto_exclude;+returnx;}else{if(fnmatch_icase(exclude,basename,0)==0)-returnto_exclude;+returnx;}continue;}
@@ -79,4 +79,6 @@ marked. If you to exclude files, make sure you have loaded index first. * Use `dir.entries[]`.+* Call `free_directory()` when none of the contained elements are no longer in use.+ (JC)
@@ -456,6 +456,12 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)die("cannot use %s as an exclude file",fname);}+staticvoidfree_exclude_stack(structexclude_stack*stk)+{+free(stk->filebuf);+free(stk);+}+/**Loadstheper-directoryexcludelistforthesubstringofbase*whichhasacharlengthofbaselen.
@@ -481,8 +487,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)structexclude*exclude=el->excludes[--el->nr];free(exclude);}-free(stk->filebuf);-free(stk);+free_exclude_stack(stk);}/* Read from the parent directories and push them down. */
@@ -587,7 +587,7 @@ int is_excluded_from_list(const char *pathname,return-1;/* undecided */}-staticintexcluded(structdir_struct*dir,constchar*pathname,int*dtype_p)+staticintis_excluded(structdir_struct*dir,constchar*pathname,int*dtype_p){intpathlen=strlen(pathname);intst;
@@ -637,7 +637,7 @@ int is_path_excluded(struct path_exclude_check *check,/**weallowthecallertopassnamelenasanoptimization;it*mustmatchthelengthofthename,asweeventuallycall-*excluded()onthewholenamestring.+*is_excluded()onthewholenamestring.*/if(namelen<0)namelen=strlen(name);
@@ -654,7 +654,7 @@ int is_path_excluded(struct path_exclude_check *check,if(ch=='/'){intdt=DT_DIR;-if(excluded(check->dir,path->buf,&dt))+if(is_excluded(check->dir,path->buf,&dt))return1;}strbuf_addch(path,ch);
@@ -663,7 +663,7 @@ int is_path_excluded(struct path_exclude_check *check,/* An entry in the index; cannot be a directory with subentries */strbuf_setlen(path,0);-returnexcluded(check->dir,name,dtype);+returnis_excluded(check->dir,name,dtype);}staticstructdir_entry*dir_entry_new(constchar*pathname,intlen)
@@ -97,39 +98,6 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags)return!!data.add_errors;}-staticvoidfill_pathspec_matches(constchar**pathspec,char*seen,intspecs)-{-intnum_unmatched=0,i;--/*-*Sincewearewalkingtheindexasifwewerewalkingthedirectory,-*wehavetomarkthematchedpathspecasseen;otherwisewewill-*mistakenlythinkthattheusergaveapathspecthatdidnotmatch-*anything.-*/-for(i=0;i<specs;i++)-if(!seen[i])-num_unmatched++;-if(!num_unmatched)-return;-for(i=0;i<active_nr;i++){-structcache_entry*ce=active_cache[i];-match_pathspec(pathspec,ce->name,ce_namelen(ce),0,seen);-}-}--staticchar*find_used_pathspec(constchar**pathspec)-{-char*seen;-inti;--for(i=0;pathspec[i];i++)-;/* just counting */-seen=xcalloc(i,1);-fill_pathspec_matches(pathspec,seen,i);-returnseen;-}-staticchar*prune_directory(structdir_struct*dir,constchar**pathspec,intprefix){char*seen;
@@ -153,46 +121,6 @@ static char *prune_directory(struct dir_struct *dir, const char **pathspec, intreturnseen;}-/*-*Checkwhetherpathreferstoasubmodule,orsomethinginsidea-*submodule.Iftheformer,returnsthepathwithanytrailingslash-*stripped.Ifthelatter,dieswithanerrormessage.-*/-constchar*treat_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;-}--voidtreat_gitlinks(constchar**pathspec)-{-if(!pathspec||!*pathspec)-return;--inti;-for(i=0;pathspec[i];i++)-pathspec[i]=treat_gitlink(pathspec[i]);-}-staticvoidrefresh(intverbose,constchar**pathspec){char*seen;
@@ -210,23 +138,6 @@ static void refresh(int verbose, const char **pathspec)free(seen);}-staticconstchar**validate_pathspec(intargc,constchar**argv,constchar*prefix)-{-constchar**pathspec=get_pathspec(prefix,argv);--if(pathspec){-constchar**p;-for(p=pathspec;*p;p++){-if(has_symlink_leading_path(*p,strlen(*p))){-intlen=prefix?strlen(prefix):0;-die(_("'%s' is beyond a symbolic link"),*p+len);-}-}-}--returnpathspec;-}-intrun_add_interactive(constchar*revision,constchar*patch_mode,constchar**pathspec){
@@ -261,7 +172,7 @@ int interactive_add(int argc, const char **argv, const char *prefix, int patch)constchar**pathspec=NULL;if(argc){-pathspec=validate_pathspec(argc,argv,prefix);+pathspec=validate_pathspec(prefix,argv);if(!pathspec)return-1;}
@@ -428,7 +339,7 @@ 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(argc,argv,prefix);+pathspec=validate_pathspec(prefix,argv);if(read_cache()<0)die(_("index file corrupt"));
@@ -0,0 +1,97 @@+#include"cache.h"+#include"dir.h"++voidvalidate_path(constchar*prefix,constchar*path)+{+if(has_symlink_leading_path(path,strlen(path))){+intlen=prefix?strlen(prefix):0;+die(_("'%s' is beyond a symbolic link"),path+len);+}+}++constchar**validate_pathspec(constchar*prefix,constchar**files)+{+constchar**pathspec=get_pathspec(prefix,files);++if(pathspec){+constchar**p;+for(p=pathspec;*p;p++){+validate_path(prefix,*p);+}+}++returnpathspec;+}++voidfill_pathspec_matches(constchar**pathspec,char*seen,intspecs)+{+intnum_unmatched=0,i;++/*+*Sincewearewalkingtheindexasifwewerewalkingthedirectory,+*wehavetomarkthematchedpathspecasseen;otherwisewewill+*mistakenlythinkthattheusergaveapathspecthatdidnotmatch+*anything.+*/+for(i=0;i<specs;i++)+if(!seen[i])+num_unmatched++;+if(!num_unmatched)+return;+for(i=0;i<active_nr;i++){+structcache_entry*ce=active_cache[i];+match_pathspec(pathspec,ce->name,ce_namelen(ce),0,seen);+}+}++char*find_used_pathspec(constchar**pathspec)+{+char*seen;+inti;++for(i=0;pathspec[i];i++)+;/* just counting */+seen=xcalloc(i,1);+fill_pathspec_matches(pathspec,seen,i);+returnseen;+}++/*+*Checkwhetherpathreferstoasubmodule,orsomethinginsidea+*submodule.Iftheformer,returnsthepathwithanytrailingslash+*stripped.Ifthelatter,dieswithanerrormessage.+*/+constchar*treat_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;+}++voidtreat_gitlinks(constchar**pathspec)+{+if(!pathspec||!*pathspec)+return;++inti;+for(i=0;pathspec[i];i++)+pathspec[i]=treat_gitlink(pathspec[i]);+}
@@ -454,7 +454,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)&&!file_exists(pathspec[i])){if(ignore_missing){intdtype=DT_UNKNOWN;-if(path_excluded(&check,pathspec[i],-1,&dtype))+if(is_path_excluded(&check,pathspec[i],-1,&dtype))dir_add_ignored(&dir,pathspec[i],strlen(pathspec[i]));}elsedie(_("pathspec '%s' did not match any files"),
@@ -1373,7 +1373,7 @@ static int check_ok_to_remove(const char *name, int len, int dtype,return0;if(o->dir&&-path_excluded(o->path_exclude_check,name,-1,&dtype))+is_path_excluded(o->path_exclude_check,name,-1,&dtype))/**ce->nameisexplicitlyexcluded,soitisOkto*overwriteit.
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:49
'el' is only *slightly* less cryptic, but is already used as the
variable name for a struct exclude_list pointer in numerous other
places, so this reduces the number of cryptic variable names in use by
one :-)
Signed-off-by: Adam Spiers <redacted>
---
dir.c | 10 +++++-----
dir.h | 4 ++--
2 files changed, 7 insertions(+), 7 deletions(-)
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:49
Extract the body of the for loop in treat_gitlinks() into a separate
treat_gitlink() function so that it can be reused elsewhere. This
paves the way for a new check-ignore sub-command.
Signed-off-by: Adam Spiers <redacted>
---
builtin/add.c | 49 +++++++++++++++++++++++++++++++------------------
1 file changed, 31 insertions(+), 18 deletions(-)
@@ -153,31 +153,44 @@ static char *prune_directory(struct dir_struct *dir, const char **pathspec, intreturnseen;}-staticvoidtreat_gitlinks(constchar**pathspec)+/*+*Checkwhetherpathreferstoasubmodule,orsomethinginsidea+*submodule.Iftheformer,returnsthepathwithanytrailingslash+*stripped.Ifthelatter,dieswithanerrormessage.+*/+constchar*treat_gitlink(constchar*path){-inti;--if(!pathspec||!*pathspec)-return;-+inti,path_len=strlen(path);for(i=0;i<active_nr;i++){structcache_entry*ce=active_cache[i];if(S_ISGITLINK(ce->ce_mode)){-intlen=ce_namelen(ce),j;-for(j=0;pathspec[j];j++){-intlen2=strlen(pathspec[j]);-if(len2<=len||pathspec[j][len]!='/'||-memcmp(ce->name,pathspec[j],len))-continue;-if(len2==len+1)-/* strip trailing slash */-pathspec[j]=xstrndup(ce->name,len);-else-die(_("Path '%s' is in submodule '%.*s'"),-pathspec[j],len,ce->name);+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;+}++voidtreat_gitlinks(constchar**pathspec)+{+if(!pathspec||!*pathspec)+return;++inti;+for(i=0;pathspec[i];i++)+pathspec[i]=treat_gitlink(pathspec[i]);}staticvoidrefresh(intverbose,constchar**pathspec)
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:49
For exclude patterns read in from files, the filename is stored together
with the corresponding line number (counting starting at 1).
For exclude patterns provided on the command line, the sequence number
is negative, with counting starting at -1, so for example the 2nd
pattern provided via --exclude would be numbered -2. This allows any
future consumers of that data to easily distinguish between exclude
patterns from files vs. from the CLI.
Signed-off-by: Adam Spiers <redacted>
---
builtin/clean.c | 2 +-
builtin/ls-files.c | 3 ++-
dir.c | 25 +++++++++++++++++++------
dir.h | 5 ++++-
4 files changed, 26 insertions(+), 9 deletions(-)
@@ -31,6 +31,9 @@ struct exclude_list {intbaselen;intto_exclude;intflags;+constchar*src;+intsrcpos;/* counting starts from 1 for line numbers in ignore files,+andfrom-1decrementingforpatternsfromCLI(--exclude)*/}**excludes;};
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:49
In a similar way to the previous commit, this extracts a new helper
function last_exclude_matching_path() which return the last
exclude_list element which matched, or NULL if no match was found.
is_path_excluded() becomes a wrapper around this, and just returns 0
or 1 depending on whether any matching exclude_list element was found.
This allows callers to find out _why_ a given path was excluded,
rather than just whether it was or not, paving the way for a new git
sub-command which allows users to test their exclude lists from the
command line.
Signed-off-by: Adam Spiers <redacted>
---
dir.c | 47 ++++++++++++++++++++++++++++++++++++++---------
dir.h | 3 +++
2 files changed, 41 insertions(+), 9 deletions(-)
@@ -681,11 +685,17 @@ int is_path_excluded(struct path_exclude_check *check,if(namelen<0)namelen=strlen(name);+/*+*Ifpathisnon-empty,andnameisequaltopathora+*subdirectoryofpath,nameshouldbeexcluded,because+*it'sinsideadirectorywhichisalreadyknowntobe+*excludedandwaspreviouslyleftincheck->path.+*/if(path->len&&path->len<=namelen&&!memcmp(name,path->buf,path->len)&&(!name[path->len]||name[path->len]=='/'))-return1;+returncheck->exclude;strbuf_setlen(path,0);for(i=0;name[i];i++){
@@ -693,8 +703,12 @@ int is_path_excluded(struct path_exclude_check *check,if(ch=='/'){intdt=DT_DIR;-if(is_excluded(check->dir,path->buf,&dt))-return1;+exclude=last_exclude_matching(check->dir,+path->buf,&dt);+if(exclude){+check->exclude=exclude;+returnexclude;+}}strbuf_addch(path,ch);}
@@ -702,7 +716,22 @@ int is_path_excluded(struct path_exclude_check *check,/* An entry in the index; cannot be a directory with subentries */strbuf_setlen(path,0);-returnis_excluded(check->dir,name,dtype);+returnlast_exclude_matching(check->dir,name,dtype);+}++/*+*Isthisnameexcluded?Thisisforacallerlikeshow_files()that+*donothonordirectoryhierarchyanditeratethroughpathsthatare+*possiblyinanignoreddirectory.+*/+intis_path_excluded(structpath_exclude_check*check,+constchar*name,intnamelen,int*dtype)+{+structexclude*exclude=+last_exclude_matching_path(check,name,namelen,dtype);+if(exclude)+returnexclude->to_exclude;+return0;}staticstructdir_entry*dir_entry_new(constchar*pathname,intlen)
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:49
In a similar way to the previous commit, this extracts a new helper
function last_exclude_matching() which returns the last exclude_list
element which matched, or NULL if no match was found. is_excluded()
becomes a wrapper around this, and just returns 0 or 1 depending on
whether any matching exclude_list element was found.
This allows callers to find out _why_ a given path was excluded,
rather than just whether it was or not, paving the way for a new git
sub-command which allows users to test their exclude lists from the
command line.
Signed-off-by: Adam Spiers <redacted>
---
dir.c | 38 +++++++++++++++++++++++++++++---------
1 file changed, 29 insertions(+), 9 deletions(-)
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:49
This works in a similar manner to git-check-attr. Some code
was reused from add.c by refactoring out into pathspec.c.
Thanks to Jeff King and Junio C Hamano for the idea:
http://thread.gmane.org/gmane.comp.version-control.git/108671/focus=108815
Signed-off-by: Adam Spiers <redacted>
---
.gitignore | 1 +
Documentation/git-check-ignore.txt | 85 +++++
Documentation/gitignore.txt | 6 +-
Makefile | 1 +
builtin.h | 1 +
builtin/check-ignore.c | 167 ++++++++++
command-list.txt | 1 +
contrib/completion/git-completion.bash | 1 +
git.c | 1 +
t/t0007-ignores.sh | 587 +++++++++++++++++++++++++++++++++
t/t9902-completion.sh | 24 +-
11 files changed, 861 insertions(+), 14 deletions(-)
create mode 100644 Documentation/git-check-ignore.txt
create mode 100644 builtin/check-ignore.c
create mode 100755 t/t0007-ignores.sh
@@ -0,0 +1,85 @@+git-check-ignore(1)+=================++NAME+----+git-check-ignore - Debug gitignore / exclude files+++SYNOPSIS+--------+[verse]+'git check-ignore' [options] pathname...+'git check-ignore' [options] --stdin < <list-of-paths>++DESCRIPTION+-----------++For each pathname given via the command-line or from a file via+`--stdin`, this command will list the first exclude pattern found (if+any) which explicitly excludes or includes that pathname. Note that+within any given exclude file, later patterns take precedence over+earlier ones, so any matching pattern which this command outputs may+not be the one you would immediately expect.++OPTIONS+-------+-q, --quiet::+ Don't output anything, just set exit status. This is only+ valid with a single pathname.++-v, --verbose::+ Also output details about the matching pattern (if any)+ for each given pathname.++--stdin::+ Read file names from stdin instead of from the command-line.++-z::+ The output format is modified to be machine-parseable (see+ below). If `--stdin` is also given, input paths are separated+ with a NUL character instead of a linefeed character.++OUTPUT+------++By default, any of the given pathnames which match an ignore pattern+will be output, one per line. If no pattern matches a given path,+nothing will be output for that path; this means that path will not be+ignored.++If `--verbose` is specified, the output is a series of lines of the form:++<source> <COLON> <linenum> <COLON> <pattern> <HT> <pathname>++<pathname> is the path of a file being queried, <pattern> is the+matching pattern, <source> is the pattern's source file, and <linenum>+is the line number of the pattern within that source. If the pattern+contained a `!` prefix or `/` suffix, it will be preserved in the+output. <source> will be an absolute path when referring to the file+configured by `core.excludesfile`, or relative to the repository root+when referring to `.git/info/exclude` or a per-directory exclude file.++If `-z` is specified, the output is a series of lines of the form:++EXIT STATUS+-----------++0::+ One or more of the provided paths is ignored.++1::+ None of the provided paths are ignored.++128::+ A fatal error was encountered.++SEE ALSO+--------+linkgit:gitignore[5]+linkgit:gitconfig[5]+linkgit:git-ls-files[5]++GIT+---+Part of the linkgit:git[1] suite
@@ -155,8 +155,10 @@ The second .gitignore prevents git from ignoring SEE ALSO ---------linkgit:git-rm[1], linkgit:git-update-index[1],-linkgit:gitrepository-layout[5]+linkgit:git-rm[1],+linkgit:git-update-index[1],+linkgit:gitrepository-layout[5],+linkgit:git-check-ignore[1] GIT ---
@@ -0,0 +1,167 @@+#include"builtin.h"+#include"cache.h"+#include"dir.h"+#include"quote.h"+#include"pathspec.h"+#include"parse-options.h"++staticintquiet,verbose,stdin_paths;+staticconstchar*constcheck_ignore_usage[]={+"git check-ignore [options] pathname...",+"git check-ignore [options] --stdin < <list-of-paths>",+NULL+};++staticintnull_term_line;++staticconststructoptioncheck_ignore_options[]={+OPT__QUIET(&quiet,N_("suppress progress reporting")),+OPT__VERBOSE(&verbose,N_("be verbose")),+OPT_GROUP(""),+OPT_BOOLEAN(0,"stdin",&stdin_paths,+N_("read file names from stdin")),+OPT_BOOLEAN('z',NULL,&null_term_line,+N_("input paths are terminated by a null character")),+OPT_END()+};++staticvoidoutput_exclude(constchar*path,structexclude*exclude)+{+char*bang=exclude->to_exclude?"":"!";+char*dir=(exclude->flags&EXC_FLAG_MUSTBEDIR)?"/":"";+if(!null_term_line){+if(!verbose){+write_name_quoted(path,stdout,'\n');+}else{+quote_c_style(exclude->src,NULL,stdout,0);+printf(":%d:%s%s%s\t",+exclude->srcpos,+bang,exclude->pattern,dir);+quote_c_style(path,NULL,stdout,0);+fputc('\n',stdout);+}+}else{+if(!verbose){+printf("%s%c",path,'\0');+}else{+printf("%s%c%d%c%s%s%s%c%s%c",+exclude->src,'\0',+exclude->srcpos,'\0',+bang,exclude->pattern,dir,'\0',+path,'\0');+}+}+}++staticintcheck_ignore(constchar*prefix,constchar**pathspec)+{+structdir_structdir;+constchar*path;+char*seen=NULL;+intnum_ignored=0;++/* read_cache() is only necessary so we can watch out for submodules. */+if(read_cache()<0)+die(_("index file corrupt"));++memset(&dir,0,sizeof(dir));+dir.flags|=DIR_COLLECT_IGNORED;+setup_standard_excludes(&dir);++if(pathspec){+inti;+structpath_exclude_checkcheck;+structexclude*exclude;++path_exclude_check_init(&check,&dir);+if(!seen)+seen=find_used_pathspec(pathspec);+for(i=0;pathspec[i];i++){+path=pathspec[i];+char*full_path=+prefix_path(prefix,prefix?strlen(prefix):0,path);+full_path=treat_gitlink(full_path);+validate_path(prefix,full_path);+if(!seen[i]&&path[0]){+intdtype=DT_UNKNOWN;+exclude=last_exclude_matching_path(&check,full_path,+-1,&dtype);+if(exclude){+if(!quiet)+output_exclude(path,exclude);+num_ignored++;+}+}+}+free(seen);+free_directory(&dir);+path_exclude_check_clear(&check);+}else{+printf("no pathspec\n");+}+returnnum_ignored;+}++staticintcheck_ignore_stdin_paths(constchar*prefix)+{+structstrbufbuf,nbuf;+char**pathspec=NULL;+size_tnr=0,alloc=0;+intline_termination=null_term_line?0:'\n';++strbuf_init(&buf,0);+strbuf_init(&nbuf,0);+while(strbuf_getline(&buf,stdin,line_termination)!=EOF){+if(line_termination&&buf.buf[0]=='"'){+strbuf_reset(&nbuf);+if(unquote_c_style(&nbuf,buf.buf,NULL))+die("line is badly quoted");+strbuf_swap(&buf,&nbuf);+}+ALLOC_GROW(pathspec,nr+1,alloc);+pathspec[nr]=xcalloc(strlen(buf.buf)+1,sizeof(*buf.buf));+strcpy(pathspec[nr++],buf.buf);+}+ALLOC_GROW(pathspec,nr+1,alloc);+pathspec[nr]=NULL;+intnum_ignored=check_ignore(prefix,(constchar**)pathspec);+maybe_flush_or_die(stdout,"attribute to stdout");+strbuf_release(&buf);+strbuf_release(&nbuf);+free(pathspec);+returnnum_ignored;+}++intcmd_check_ignore(intargc,constchar**argv,constchar*prefix)+{+git_config(git_default_config,NULL);++argc=parse_options(argc,argv,prefix,check_ignore_options,+check_ignore_usage,0);++if(stdin_paths){+if(0<argc)+die(_("cannot specify pathnames with --stdin"));+}else{+if(null_term_line)+die(_("-z only makes sense with --stdin"));+if(argc==0)+die(_("no path specified"));+}+if(quiet){+if(argc>1)+die(_("--quiet is only valid with a single pathname"));+if(verbose)+die(_("cannot have both --quiet and --verbose"));+}++intnum_ignored=0;+if(stdin_paths){+num_ignored=check_ignore_stdin_paths(prefix);+}else{+num_ignored=check_ignore(prefix,argv);+maybe_flush_or_die(stdout,"ignore to stdout");+}++returnnum_ignored>0?0:1;+}
@@ -0,0 +1,587 @@+#!/bin/sh++test_description=check-ignore++../test-lib.sh++init_vars(){+global_excludes="$HOME/global-excludes"+}++enable_global_excludes(){+init_vars+gitconfigcore.excludesfile"$global_excludes"+}++expect_in(){+dest="$HOME/expected-$1"text="$2"+iftest-z"$text"+then+>"$dest"# avoid newline+else+echo-e"$text">"$dest"+fi+}++expect(){+expect_instdout"$1"+}++expect_from_stdin(){+cat>"$HOME/expected-stdout"+}++test_stderr(){+expected="$1"+expect_instderr"$1"&&+test_cmp"$HOME/expected-stderr""$HOME/stderr"+}++stderr_contains(){+regexp="$1"+ifgrep-q"$regexp""$HOME/stderr"+then+return0+else+echo"didn't find /$regexp/ in $HOME/stderr"+cat"$HOME/stderr"+return1+fi+}++stderr_empty_on_success(){+expect_code="$1"+iftest$expect_code=0+then+test_stderr""+else+# If we expect failure then stderr might or might not be empty+# due to --quiet - the caller can check its contents+return0+fi+}++test_check_ignore(){+args="$1"expect_code="${2:-0}"global_args="$3"++init_vars&&+rm-f"$HOME/stdout""$HOME/stderr""$HOME/cmd"&&+echo$(whichgit)$global_argscheck-ignore$quiet_opt$verbose_opt$args\+>"$HOME/cmd"&&+pwd>"$HOME/pwd"&&+test_expect_code"$expect_code"\+git$global_argscheck-ignore$quiet_opt$verbose_opt$args\+>"$HOME/stdout"2>"$HOME/stderr"&&+test_cmp"$HOME/expected-stdout""$HOME/stdout"&&+stderr_empty_on_success"$expect_code"+}++test_expect_success_multi(){+testname="$1"expect_verbose="$2"code="$3"++expect=$(echo"$expect_verbose"|sed-e's/.* //')++test_expect_success"$testname""+expect'$expect'&&+$code+"++forquiet_optin'-q''--quiet'+do+test_expect_success"$testname${quiet_opt:+ with $quiet_opt}""+expect''&&+$code+"+done+quiet_opt=++forverbose_optin'-v''--verbose'+do+test_expect_success"$testname${verbose_opt:+ with $verbose_opt}""+expect'$expect_verbose'&&+$code+"+done+verbose_opt=+}++test_expect_success'setup''+init_vars+mkdir-pa/b/ignored-dira/submoduleb&&+ln-sba/symlink&&+(+cda/submodule&&+gitinit&&+echoa>a&&+gitadda&&+gitcommit-m"commit in submodule"+)&&+gitadda/submodule&&+cat<<-\EOF>.gitignore&&+one+EOF+cat<<-\EOF>a/.gitignore&&+two*+*three+EOF+cat<<-\EOF>a/b/.gitignore&&+four+five+# this comment should affect the line numbers+six+ignored-dir/+# and so should this blank line:++!on*+!two+EOF+echo"seven">a/b/ignored-dir/.gitignore&&+test-n"$HOME"&&+cat<<-\EOF>"$global_excludes"&&+globalone+!globaltwo+globalthree+EOF+cat<<-\EOF>>.git/info/exclude+per-repo+EOF+'++############################################################################+#+# test invalid inputs++test_expect_success_multi'empty command line''''+test_check_ignore""128&&+stderr_contains"fatal: no path specified"+'++test_expect_success'-q with multiple args''+expect""&&+test_check_ignore"-q one two"128&&+stderr_contains"fatal: --quiet is only valid with a single pathname"+'++test_expect_success'--quiet with multiple args''+expect""&&+test_check_ignore"--quiet one two"128&&+stderr_contains"fatal: --quiet is only valid with a single pathname"+'++forverbose_optin'-v''--verbose'+do+forquiet_optin'-q''--quiet'+do+test_expect_success"$quiet_opt$verbose_opt""+expect''&&+test_check_ignore'$quiet_opt $verbose_opt foo'128&&+stderr_contains'fatal: cannot have both --quiet and --verbose'+"+done+done++test_expect_success'--quiet with multiple args''+expect""&&+test_check_ignore"--quiet one two"128&&+stderr_contains"fatal: --quiet is only valid with a single pathname"+'++test_expect_success_multi'erroneous use of --''''+test_check_ignore"--"128&&+stderr_contains"fatal: no path specified"+'++test_expect_success_multi'--stdin with superfluous arg''''+test_check_ignore"--stdin foo"128&&+stderr_contains"fatal: cannot specify pathnames with --stdin"+'++test_expect_success_multi'--stdin -z with superfluous arg''''+test_check_ignore"--stdin -z foo"128&&+stderr_contains"fatal: cannot specify pathnames with --stdin"+'++test_expect_success_multi'-z without --stdin''''+test_check_ignore"-z"128&&+stderr_contains"fatal: -z only makes sense with --stdin"+'++test_expect_success_multi'-z without --stdin and superfluous arg''''+test_check_ignore"-z foo"128&&+stderr_contains"fatal: -z only makes sense with --stdin"+'++test_expect_success_multi'needs work tree''''+(+cd.git&&+test_check_ignore"foo"128+)&&+stderr_contains"fatal: This operation must be run in a work tree"+'++############################################################################+#+# test standard ignores++test_expect_success_multi"top-level not ignored"'''+test_check_ignore"foo"1+'++test_expect_success_multi"top-level ignored"\+'.gitignore:1:one one''+test_check_ignore"one"+'++test_expect_success_multi'sub-directory ignore from top'\+'.gitignore:1:one a/one''+test_check_ignore"a/one"+'++test_expect_success'sub-directory local ignore''+expect"a/3-three"&&+test_check_ignore"a/3-three a/three-not-this-one"+'++test_expect_success'sub-directory local ignore with --verbose''+expect"a/.gitignore:2:*three a/3-three"&&+test_check_ignore"--verbose a/3-three a/three-not-this-one"+'++test_expect_success'local ignore inside a sub-directory''+expect"3-three"&&+(+cda&&+test_check_ignore"3-three three-not-this-one"+)+'+test_expect_success'local ignore inside a sub-directory with --verbose''+expect"a/.gitignore:2:*three 3-three"&&+(+cda&&+test_check_ignore"--verbose 3-three three-not-this-one"+)+'++test_expect_success_multi'nested include'\+'a/b/.gitignore:8:!on* a/b/one''+test_check_ignore"a/b/one"+'++############################################################################+#+# test ignored sub-directories++test_expect_success_multi'ignored sub-directory'\+'a/b/.gitignore:5:ignored-dir/ a/b/ignored-dir''+test_check_ignore"a/b/ignored-dir"+'++test_expect_success'multiple files inside ignored sub-directory''+expect_from_stdin<<-\EOF&&+a/b/ignored-dir/foo+a/b/ignored-dir/twoooo+a/b/ignored-dir/seven+EOF+test_check_ignore"a/b/ignored-dir/foo a/b/ignored-dir/twoooo a/b/ignored-dir/seven"+'++test_expect_success'multiple files inside ignored sub-directory with -v''+expect_from_stdin<<-\EOF&&+a/b/.gitignore:5:ignored-dir/a/b/ignored-dir/foo+a/b/.gitignore:5:ignored-dir/a/b/ignored-dir/twoooo+a/b/.gitignore:5:ignored-dir/a/b/ignored-dir/seven+EOF+test_check_ignore"-v a/b/ignored-dir/foo a/b/ignored-dir/twoooo a/b/ignored-dir/seven"+'++test_expect_success'cd to ignored sub-directory''+expect_from_stdin<<-\EOF&&+foo+twoooo+../one+seven+../../one+EOF+(+cda/b/ignored-dir&&+test_check_ignore"foo twoooo ../one seven ../../one"+)+'++test_expect_success'cd to ignored sub-directory with -v''+expect_from_stdin<<-\EOF&&+a/b/.gitignore:5:ignored-dir/foo+a/b/.gitignore:5:ignored-dir/twoooo+a/b/.gitignore:8:!on*../one+a/b/.gitignore:5:ignored-dir/seven+.gitignore:1:one../../one+EOF+(+cda/b/ignored-dir&&+test_check_ignore"-v foo twoooo ../one seven ../../one"+)+'++############################################################################+#+# test handling of symlinks++test_expect_success_multi'symlink''''+test_check_ignore"a/symlink"1+'++test_expect_success_multi'beyond a symlink''''+test_check_ignore"a/symlink/foo"128&&+test_stderr"fatal: '\''a/symlink/foo'\'' is beyond a symbolic link"+'++test_expect_success_multi'beyond a symlink from subdirectory''''+(+cda&&+test_check_ignore"symlink/foo"128+)&&+test_stderr"fatal: '\''symlink/foo'\'' is beyond a symbolic link"+'++############################################################################+#+# test handling of submodules++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_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 handling of global ignore files++test_expect_success'global ignore not yet enabled''+expect_from_stdin<<-\EOF&&+.git/info/exclude:7:per-repoper-repo+a/.gitignore:2:*threea/globalthree+.git/info/exclude:7:per-repoa/per-repo+EOF+test_check_ignore"-v globalone per-repo a/globalthree a/per-repo not-ignored a/globaltwo"+'++test_expect_success'global ignore''+enable_global_excludes&&+expect_from_stdin<<-\EOF&&+globalone+per-repo+globalthree+a/globalthree+a/per-repo+globaltwo+EOF+test_check_ignore"globalone per-repo globalthree a/globalthree a/per-repo not-ignored globaltwo"+'++test_expect_success'global ignore with -v''+enable_global_excludes&&+expect_from_stdin<<-EOF&&+$global_excludes:1:globaloneglobalone+.git/info/exclude:7:per-repoper-repo+$global_excludes:3:globalthreeglobalthree+a/.gitignore:2:*threea/globalthree+.git/info/exclude:7:per-repoa/per-repo+$global_excludes:2:!globaltwoglobaltwo+EOF+test_check_ignore"-v globalone per-repo globalthree a/globalthree a/per-repo not-ignored globaltwo"+'++############################################################################+#+# test --stdin++cat<<-\EOF>stdin+one+not-ignored+a/one+a/not-ignored+a/b/on+a/b/one+a/b/oneone+"a/b/one two"+"a/b/one\"three"+a/b/not-ignored+a/b/two+a/b/twooo+globaltwo+a/globaltwo+a/b/globaltwo+b/globaltwo+EOF+cat<<-\EOF>expected-default+one+a/one+a/b/on+a/b/one+a/b/oneone+a/b/onetwo+"a/b/one\"three"+a/b/two+a/b/twooo+globaltwo+a/globaltwo+a/b/globaltwo+b/globaltwo+EOF+cat<<-EOF>expected-verbose+.gitignore:1:oneone+.gitignore:1:onea/one+a/b/.gitignore:8:!on*a/b/on+a/b/.gitignore:8:!on*a/b/one+a/b/.gitignore:8:!on*a/b/oneone+a/b/.gitignore:8:!on*a/b/onetwo+a/b/.gitignore:8:!on*"a/b/one\"three"+a/b/.gitignore:9:!twoa/b/two+a/.gitignore:1:two*a/b/twooo+$global_excludes:2:!globaltwoglobaltwo+$global_excludes:2:!globaltwoa/globaltwo+$global_excludes:2:!globaltwoa/b/globaltwo+$global_excludes:2:!globaltwob/globaltwo+EOF++sed-e's/^"//'-e's/\\//'-e's/"$//'stdin|\+tr"\n""\0">stdin0+sed-e's/^"//'-e's/\\//'-e's/"$//'expected-default|\+tr"\n""\0">expected-default0+sed-e's/ "/ /'-e's/\\//'-e's/"$//'expected-verbose|\+tr":\t\n""\0">expected-verbose0++test_expect_success'--stdin''+expect_from_stdin<expected-default&&+test_check_ignore"--stdin"<stdin+'++test_expect_success'--stdin -q''+expect""&&+test_check_ignore"-q --stdin"<stdin+'++test_expect_success'--stdin -v''+expect_from_stdin<expected-verbose&&+test_check_ignore"-v --stdin"<stdin+'++foroptsin'--stdin -z''-z --stdin'+do+test_expect_success"$opts""+expect_from_stdin<expected-default0&&+test_check_ignore'$opts'<stdin0+"++test_expect_success"$opts -q""+expect""&&+test_check_ignore'-q $opts'<stdin0+"++test_expect_success"$opts -v""+expect_from_stdin<expected-verbose0&&+test_check_ignore'-v $opts'<stdin0+"+done++cat<<-\EOF>stdin+../one+../not-ignored+one+not-ignored+b/on+b/one+b/oneone+"b/one two"+"b/one\"three"+b/two+b/not-ignored+b/twooo+../globaltwo+globaltwo+b/globaltwo+../b/globaltwo+EOF+cat<<-\EOF>expected-default+../one+one+b/on+b/one+b/oneone+b/onetwo+"b/one\"three"+b/two+b/twooo+../globaltwo+globaltwo+b/globaltwo+../b/globaltwo+EOF+cat<<-EOF>expected-verbose+.gitignore:1:one../one+.gitignore:1:oneone+a/b/.gitignore:8:!on*b/on+a/b/.gitignore:8:!on*b/one+a/b/.gitignore:8:!on*b/oneone+a/b/.gitignore:8:!on*b/onetwo+a/b/.gitignore:8:!on*"b/one\"three"+a/b/.gitignore:9:!twob/two+a/.gitignore:1:two*b/twooo+$global_excludes:2:!globaltwo../globaltwo+$global_excludes:2:!globaltwoglobaltwo+$global_excludes:2:!globaltwob/globaltwo+$global_excludes:2:!globaltwo../b/globaltwo+EOF++sed-e's/^"//'-e's/\\//'-e's/"$//'stdin|\+tr"\n""\0">stdin0+sed-e's/^"//'-e's/\\//'-e's/"$//'expected-default|\+tr"\n""\0">expected-default0+sed-e's/ "/ /'-e's/\\//'-e's/"$//'expected-verbose|\+tr":\t\n""\0">expected-verbose0++test_expect_success'--stdin from subdirectory''+expect_from_stdin<expected-default&&+(+cda&&+test_check_ignore"--stdin"<../stdin+)+'++test_expect_success'--stdin from subdirectory with -v''+expect_from_stdin<expected-verbose&&+(+cda&&+test_check_ignore"--stdin -v"<../stdin+)+'++foroptsin'--stdin -z''-z --stdin'+do+test_expect_success"$opts from subdirectory"'+expect_from_stdin<expected-default0&&+(+cda&&+test_check_ignore"'"$opts"'"<../stdin0+)+'++test_expect_success"$opts from subdirectory with -v"'+expect_from_stdin<expected-verbose0&&+(+cda&&+test_check_ignore"'"$opts"' -v"<../stdin0+)+'+done+++test_done
From: Adam Spiers <hidden> Date: 2016-06-15 22:54:49
From the perspective of a newcomer to the codebase, the directory
traversal API has a few potentially confusing properties. These
comments clarify a few key aspects and will hopefully make it easier
to understand for other newcomers in the future.
Signed-off-by: Adam Spiers <redacted>
---
Documentation/technical/api-directory-listing.txt | 9 +++++---
dir.c | 8 ++++++-
dir.h | 26 +++++++++++++++++++++--
3 files changed, 37 insertions(+), 6 deletions(-)
@@ -9,8 +9,11 @@ Data structure -------------- `struct dir_struct` structure is used to pass directory traversal-options to the library and to record the paths discovered. The notable-options are:+options to the library and to record the paths discovered. A single+`struct dir_struct` is used regardless of whether or not the traversal+recursively descends into subdirectories.++The notable options are: `exclude_per_dir`::
@@ -39,7 +42,7 @@ options are: If set, recurse into a directory that looks like a git directory. Otherwise it is shown as a directory.-The result of the enumeration is left in these fields::+The result of the enumeration is left in these fields: `entries[]`::
@@ -451,6 +453,10 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)die("cannot use %s as an exclude file",fname);}+/*+*Loadstheper-directoryexcludelistforthesubstringofbase+*whichhasacharlengthofbaselen.+*/staticvoidprep_exclude(structdir_struct*dir,constchar*base,intbaselen){structexclude_list*el;
@@ -461,7 +467,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)(baselen+strlen(dir->exclude_per_dir)>=PATH_MAX))return;/* too long a path -- ignore */-/* Pop the ones that are not the prefix of the path being checked. */+/* Pop the directories that are not the prefix of the path being checked. */el=&dir->exclude_list[EXC_DIRS];while((stk=dir->exclude_stack)!=NULL){if(stk->baselen<=baselen&&
@@ -26,9 +34,15 @@ struct exclude_list {}**excludes;};+/*+*Thecontentsoftheper-directoryexcludefilesarelazilyreadon+*demandandthencachedinmemory,oneperexclude_stackstruct,in+*ordertoavoidopeningandparsingeachoneeverytimethat+*directoryistraversed.+*/structexclude_stack{-structexclude_stack*prev;-char*filebuf;+structexclude_stack*prev;/* the struct exclude_stack for the parent directory */+char*filebuf;/* remember pointer to per-directory exclude file contents so we can free() */intbaselen;intexclude_ix;};
From: Michael Haggerty <hidden> Date: 2016-06-15 22:54:50
On 09/20/2012 09:46 PM, Adam Spiers wrote:
quoted hunk
This works in a similar manner to git-check-attr. Some code
was reused from add.c by refactoring out into pathspec.c.
Thanks to Jeff King and Junio C Hamano for the idea:
http://thread.gmane.org/gmane.comp.version-control.git/108671/focus=108815
Signed-off-by: Adam Spiers <redacted>
---
.gitignore | 1 +
Documentation/git-check-ignore.txt | 85 +++++
Documentation/gitignore.txt | 6 +-
Makefile | 1 +
builtin.h | 1 +
builtin/check-ignore.c | 167 ++++++++++
command-list.txt | 1 +
contrib/completion/git-completion.bash | 1 +
git.c | 1 +
t/t0007-ignores.sh | 587 +++++++++++++++++++++++++++++++++
t/t9902-completion.sh | 24 +-
11 files changed, 861 insertions(+), 14 deletions(-)
create mode 100644 Documentation/git-check-ignore.txt
create mode 100644 builtin/check-ignore.c
create mode 100755 t/t0007-ignores.sh
@@ -0,0 +1,85 @@+git-check-ignore(1)+=================++NAME+----+git-check-ignore - Debug gitignore / exclude files+++SYNOPSIS+--------+[verse]+'git check-ignore' [options] pathname...+'git check-ignore' [options] --stdin < <list-of-paths>++DESCRIPTION+-----------++For each pathname given via the command-line or from a file via+`--stdin`, this command will list the first exclude pattern found (if+any) which explicitly excludes or includes that pathname. Note that+within any given exclude file, later patterns take precedence over+earlier ones, so any matching pattern which this command outputs may+not be the one you would immediately expect.
Can I tell from the output of "git check-ignore" whether a file is
really ignored? The way I read the paragraph above, the output doesn't
necessarily show the pattern that determines whether a file is *really*
ignored. That makes it sound like the ignore status of the file might
be different than what I would infer from the output. If I am
misunderstanding the situation, then perhaps the explanation in the
above paragraph can be improved.
On the other hand, if my understanding is correct, then why did you
choose this (seemingly strange) policy? It would seem more useful
either to output the pattern that has the definitive effect on the
file's status, or to output all patterns that match the file.
+OPTIONS
+-------
+-q, --quiet::
+ Don't output anything, just set exit status. This is only
+ valid with a single pathname.
+
+-v, --verbose::
+ Also output details about the matching pattern (if any)
+ for each given pathname.
+
+--stdin::
+ Read file names from stdin instead of from the command-line.
+
+-z::
+ The output format is modified to be machine-parseable (see
+ below). If `--stdin` is also given, input paths are separated
+ with a NUL character instead of a linefeed character.
+
+OUTPUT
+------
+
+By default, any of the given pathnames which match an ignore pattern
+will be output, one per line. If no pattern matches a given path,
+nothing will be output for that path; this means that path will not be
+ignored.
+
+If `--verbose` is specified, the output is a series of lines of the form:
+
+<source> <COLON> <linenum> <COLON> <pattern> <HT> <pathname>
+
+<pathname> is the path of a file being queried, <pattern> is the
+matching pattern, <source> is the pattern's source file, and <linenum>
+is the line number of the pattern within that source. If the pattern
+contained a `!` prefix or `/` suffix, it will be preserved in the
+output. <source> will be an absolute path when referring to the file
+configured by `core.excludesfile`, or relative to the repository root
+when referring to `.git/info/exclude` or a per-directory exclude file.
+
+If `-z` is specified, the output is a series of lines of the form:
+
+EXIT STATUS
+-----------
[...]
I think you forgot to finish the thought about "If -z is specified".
Michael
--
Michael Haggerty
mhagger@alum.mit.edu
http://softwareswirl.blogspot.com/
It was unfortunate that these functions were not already documented.
But now that you are elevating them from static to global functions, it
would be great if you would add some comments for them.
(By the way, thanks for the docstrings that you added in an earlier patch.)
This patch is not only moving code around, but also:
* extracting a new function, validate_path()
* changing the signature of validate_pathspec()
* maybe other things? How is a reviewer to know without examining every
line of the patch?
Each self-contained change should be done in a separate patch. For
example, one patch should move the code while making only the minimal
changes logically connected to the move (e.g., removing "static"
qualifiers). The other changes should be made separate commits.
Michael
--
Michael Haggerty
mhagger@alum.mit.edu
http://softwareswirl.blogspot.com/
@@ -79,4 +79,6 @@ marked. If you to exclude files, make sure you have loaded index first. * Use `dir.entries[]`.+* Call `free_directory()` when none of the contained elements are no longer in use.+ (JC)
With I see a function like this, the first question in my head is always
"does it also free(dir), or does it only free the substructures, leaving
dir empty but allocated?" There should be a comment documenting the
behavior. I also find it helpful if a function that frees the top-level
structure has "free" in the name, while a function that only empties the
top-level structure without freeing it *not* have free in the name
(e.g., "clear_directory()"). But maybe that's just me.
Michael
--
Michael Haggerty
mhagger@alum.mit.edu
http://softwareswirl.blogspot.com/