"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Evan Silberman <evan@jklol.net>
Subject:
Re: leading separators in ignore patterns
To:
Stefan Sperling <stsp@stsp.name>
Cc:
gameoftrees@openbsd.org
Date:
Sat, 17 May 2025 13:38:23 -0700

Download raw body.

Thread
I'm back with a patch.

- Skip past leading slashes in ignore patterns. In gitignore(7), these
  trigger the behavior got has by default. Add regress test.
- Lift documentation of ignore patterns to a subsection, referenced by
  the `add` and `status` docs.
- Refine documentation of ignore patterns to discuss the extensions to
  glob(7) as a whole, and then list caveats relative to git(1) and
  cvs(1).
- Add a file to the test worktree in the main gitignore regress test in
  order to register a known delta to gitignore(7) that was ambiguously
  documented previously: for the pattern a/**/foo, git will ignore
  a/foo, but got will not.

The one change to worktree.c here originally happened in read_ignores()
but I moved it to match_path() after further consideration. Not sure
about my style here, is it unnecessary to use new locals for advancing
the pattern pointer/decreasing the length?

I note that `got import` references ignore patterns but the patterns
given as -I params are not matched the same way. Maybe `got import`
should respect .gitignore/.cvsignore?

Happy to split this up a bit if it makes review easier, most of the
diffstat is from the man page reworks.

diff /home/evan/src/got
path + /home/evan/src/got
commit - 26e67dac35191e72095a1e9a7216719d3abfbb7c
blob - 6652a0e142d22804174660f5bfac8d20073bdec6
file + cvg/cvg.1
--- cvg/cvg.1
+++ cvg/cvg.1
@@ -581,6 +581,8 @@ are as follows:
 .Bl -tag -width Ds
 .It Fl I
 Show unversioned files even if they match an ignore pattern.
+See
+.Sx Ignore Patterns .
 .It Fl S Ar status-codes
 Suppress the output of files with a modification status matching any of the
 single-character status codes contained in the
@@ -604,42 +606,6 @@ Cannot be used together with the
 .Fl S
 option.
 .El
-.Pp
-For compatibility with
-.Xr cvs 1
-and
-.Xr git 1 ,
-.Cm got status
-reads
-.Xr glob 7
-patterns from
-.Pa .cvsignore
-and
-.Pa .gitignore
-files in each traversed directory and will not display unversioned files
-which match these patterns.
-Ignore patterns which end with a slash,
-.Dq / ,
-will only match directories.
-As an extension to
-.Xr glob 7
-matching rules,
-.Cm got status
-supports consecutive asterisks,
-.Dq ** ,
-which will match an arbitrary amount of directories.
-Unlike
-.Xr cvs 1 ,
-.Cm got status
-only supports a single ignore pattern per line.
-Unlike
-.Xr git 1 ,
-.Cm got status
-does not support negated ignore patterns prefixed with
-.Dq \&! ,
-and gives no special significance to the location of path component separators,
-.Dq / ,
-in a pattern.
 .It Xo
 .Cm log
 .Op Fl bdPpRs
@@ -1045,9 +1011,9 @@ another repository.
 .Xc
 Schedule unversioned files in a work tree for addition to the
 repository in the next commit.
-By default, files which match a
-.Cm got status
-ignore pattern will not be added.
+By default, files which match an ignore pattern will not be added.
+See
+.Sx Ignore Patterns .
 .Pp
 If a
 .Ar path
@@ -1073,9 +1039,9 @@ The options for
 are as follows:
 .Bl -tag -width Ds
 .It Fl I
-Add files even if they match a
-.Cm got status
-ignore pattern.
+Add files even if they match an ignore pattern.
+See
+.Xs Ignore Patterns .
 .It Fl R
 Permit recursion into directories.
 If this option is not specified,
@@ -1761,6 +1727,61 @@ If a
 argument corresponds to the work tree's root directory, display information
 for all tracked files.
 .El
+.Tg ignore
+.Ss Ignore Patterns
+.Cm cvg status
+and
+.Cm  add
+read patterns from
+.Pa .cvsignore
+and
+.Pa .gitignore
+files in each traversed directory and will not display or add unversioned files
+which match these patterns.
+The patterns are matched according to
+.Xr glob 7
+rules, with extensions to improve compatibility with
+.Xr cvs 1
+and
+.Xr git 1 .
+Patterns from each
+.Pa .cvsignore
+or
+.Pa .gitignore
+file are matched relative to the directory containing that file.
+If a pattern begins with two asterisks followed by a slash,
+.Dq **/ ,
+the remainder of the pattern will match at any directory at
+or below the directory containing
+.Pa .cvsignore
+or
+.Pa gitignore
+file.
+Two asterisks surrounded by slashes,
+.Dq /**/ ,
+in the middle of a pattern will match one directory or more.
+Patterns which end with a slash,
+.Dq / ,
+will only match directories, and any paths below matching directories will
+be ignored.
+.Pp
+Unlike
+.Xr cvs 1 ,
+.Nm
+only supports a single ignore pattern per line.
+Unlike
+.Xr git 1 ,
+.Nm
+does not support negated ignore patterns prefixed with
+.Dq \&! ,
+and does not match patterns at arbitrary depth relative to the
+.Pa .gitignore 
+file unless they begin with
+.Dq **/ .
+For better
+.Xr git 1
+compatibility, patterns beginning with a slash are matched as if the slash
+were not present.
 .Sh ENVIRONMENT
 .Bl -tag -width GOT_IGNORE_GITCONFIG
 .It Ev GOT_AUTHOR
commit - 26e67dac35191e72095a1e9a7216719d3abfbb7c
blob - 72385745a21eb10a6eaf3ec6da928b7bbbaa0037
file + got/got.1
--- got/got.1
+++ got/got.1
@@ -964,6 +964,8 @@ are as follows:
 .Bl -tag -width Ds
 .It Fl I
 Show unversioned files even if they match an ignore pattern.
+See
+.Sx Ignore Patterns .
 .It Fl S Ar status-codes
 Suppress the output of files with a modification status matching any of the
 single-character status codes contained in the
@@ -987,42 +989,6 @@ Cannot be used together with the
 .Fl S
 option.
 .El
-.Pp
-For compatibility with
-.Xr cvs 1
-and
-.Xr git 1 ,
-.Cm got status
-reads
-.Xr glob 7
-patterns from
-.Pa .cvsignore
-and
-.Pa .gitignore
-files in each traversed directory and will not display unversioned files
-which match these patterns.
-Ignore patterns which end with a slash,
-.Dq / ,
-will only match directories.
-As an extension to
-.Xr glob 7
-matching rules,
-.Cm got status
-supports consecutive asterisks,
-.Dq ** ,
-which will match an arbitrary amount of directories.
-Unlike
-.Xr cvs 1 ,
-.Cm got status
-only supports a single ignore pattern per line.
-Unlike
-.Xr git 1 ,
-.Cm got status
-does not support negated ignore patterns prefixed with
-.Dq \&! ,
-and gives no special significance to the location of path component separators,
-.Dq / ,
-in a pattern.
 .It Xo
 .Cm log
 .Op Fl bdPpRst
@@ -1904,9 +1870,9 @@ another repository.
 .Xc
 Schedule unversioned files in a work tree for addition to the
 repository in the next commit.
-By default, files which match a
-.Cm got status
-ignore pattern will not be added.
+By default, files which match an ignore pattern will not be added.
+See
+.Sx Ignore Patterns .
 .Pp
 If a
 .Ar path
@@ -1932,9 +1898,9 @@ The options for
 are as follows:
 .Bl -tag -width Ds
 .It Fl I
-Add files even if they match a
-.Cm got status
-ignore pattern.
+Add files even if they match an ignore pattern.
+See
+.Xs Ignore Patterns .
 .It Fl R
 Permit recursion into directories.
 If this option is not specified,
@@ -3857,6 +3823,61 @@ If a
 argument corresponds to the work tree's root directory, display information
 for all tracked files.
 .El
+.Tg ignore
+.Ss Ignore Patterns
+.Cm got status
+and
+.Cm got add
+read patterns from
+.Pa .cvsignore
+and
+.Pa .gitignore
+files in each traversed directory and will not display or add unversioned files
+which match these patterns.
+The patterns are matched according to
+.Xr glob 7
+rules, with extensions to improve compatibility with
+.Xr cvs 1
+and
+.Xr git 1 .
+Patterns from each
+.Pa .cvsignore
+or
+.Pa .gitignore
+file are matched relative to the directory containing that file.
+If a pattern begins with two asterisks followed by a slash,
+.Dq **/ ,
+the remainder of the pattern will match at any directory at
+or below the directory containing
+.Pa .cvsignore
+or
+.Pa gitignore
+file.
+Two asterisks surrounded by slashes,
+.Dq /**/ ,
+in the middle of a pattern will match one directory or more.
+Patterns which end with a slash,
+.Dq / ,
+will only match directories, and any paths below matching directories will
+be ignored.
+.Pp
+Unlike
+.Xr cvs 1 ,
+.Nm
+only supports a single ignore pattern per line.
+Unlike
+.Xr git 1 ,
+.Nm
+does not support negated ignore patterns prefixed with
+.Dq \&! ,
+and does not match patterns at arbitrary depth relative to the
+.Pa .gitignore 
+file unless they begin with
+.Dq **/ .
+For better
+.Xr git 1
+compatibility, patterns beginning with a slash are matched as if the slash
+were not present.
 .Sh ENVIRONMENT
 .Bl -tag -width GOT_IGNORE_GITCONFIG
 .It Ev GOT_AUTHOR
commit - 26e67dac35191e72095a1e9a7216719d3abfbb7c
blob - fd2e3ed8fe3b3dbd2e5e8de7acc219ee467ee052
file + lib/worktree.c
--- lib/worktree.c
+++ lib/worktree.c
@@ -3813,19 +3813,29 @@ match_path(const char *pattern, size_t pattern_len, co
     int flags)
 {
 	char buf[PATH_MAX];
+	const char *pat = pattern;
+	size_t len = pattern_len;
 
 	/*
+	 * For gitignore(7) compatibility, ignore leading slashes
+	 */
+	if (len > 0 && pat[0] == '/') {
+		pat++;
+		len--;
+	}
+
+	/*
 	 * Trailing slashes signify directories.
 	 * Append a * to make such patterns conform to fnmatch rules.
 	 */
-	if (pattern_len > 0 && pattern[pattern_len - 1] == '/') {
-		if (snprintf(buf, sizeof(buf), "%s*", pattern) >= sizeof(buf))
+	if (pat > 0 && pat[len - 1] == '/') {
+		if (snprintf(buf, sizeof(buf), "%s*", pat) >= sizeof(buf))
 			return FNM_NOMATCH; /* XXX */
 
 		return fnmatch(buf, path, flags);
 	}
 
-	return fnmatch(pattern, path, flags);
+	return fnmatch(pat, path, flags);
 }
 
 static int
commit - 26e67dac35191e72095a1e9a7216719d3abfbb7c
blob - 018736a36965a3780dcc34fa3fd41f8d9f337425
file + regress/cmdline/status.sh
--- regress/cmdline/status.sh
+++ regress/cmdline/status.sh
@@ -654,6 +654,7 @@ test_status_gitignore() {
 	echo "unversioned file" > $testroot/wt/epsilon/boo
 	echo "unversioned file" > $testroot/wt/epsilon/moo
 	mkdir -p $testroot/wt/a/b/c/
+	echo "git would ignore this" > $testroot/wt/a/foo
 	echo "unversioned file" > $testroot/wt/a/b/c/foo
 	echo "unversioned file" > $testroot/wt/a/b/c/zoo
 	echo "foo" > $testroot/wt/.gitignore
@@ -663,6 +664,7 @@ test_status_gitignore() {
 	echo "**/zoo" >> $testroot/wt/.gitignore
 
 	echo '?  .gitignore' > $testroot/stdout.expected
+	echo '?  a/foo' >> $testroot/stdout.expected
 	echo '?  foop' >> $testroot/stdout.expected
 	(cd $testroot/wt && got status > $testroot/stdout)
 
@@ -675,6 +677,7 @@ test_status_gitignore() {
 	fi
 
 	echo '?  .gitignore' > $testroot/stdout.expected
+	echo '?  a/foo' >> $testroot/stdout.expected
 	echo '?  foop' >> $testroot/stdout.expected
 	(cd $testroot/wt/gamma && got status > $testroot/stdout)
 
@@ -690,6 +693,7 @@ test_status_gitignore() {
 ?  .gitignore
 ?  a/b/c/foo
 ?  a/b/c/zoo
+?  a/foo
 ?  barp
 ?  epsilon/bar
 ?  epsilon/boo
@@ -713,6 +717,41 @@ EOF
 	test_done "$testroot" "$ret"
 }
 
+test_status_gitignore_leading_slashes() {
+	local testroot=`test_init status_gitignore_leading_slashes`
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "unversioned file" > $testroot/wt/foo
+	echo "unversioned file" > $testroot/wt/epsilon/foo
+	echo "unversioned file" > $testroot/wt/epsilon/bar
+	mkdir -p $testroot/wt/nu
+	echo "unversioned file" > $testroot/wt/nu/baz
+	mkdir -p $testroot/wt/epsilon/nu
+	echo "unversioned file" > $testroot/wt/epsilon/nu/baz
+
+	echo "/foo" > $testroot/wt/.gitignore
+	echo "/nu" >> $testroot/wt/.gitignore
+
+	echo '?  .gitignore' > $testroot/stdout.expected
+	echo '?  epsilon/bar' >> $testroot/stdout.expected
+	echo '?  epsilon/foo' >> $testroot/stdout.expected
+	echo '?  epsilon/nu/baz' >> $testroot/stdout.expected
+	(cd $testroot/wt && got status > $testroot/stdout)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
 test_status_gitignore_trailing_slashes() {
 	local testroot=`test_init status_gitignore_trailing_slashes`
 
@@ -1204,6 +1243,7 @@ run_test test_status_empty_dir_unversioned_file
 run_test test_status_many_paths
 run_test test_status_cvsignore
 run_test test_status_gitignore
+run_test test_status_gitignore_leading_slashes
 run_test test_status_gitignore_trailing_slashes
 run_test test_status_gitignore_comments
 run_test test_status_multiple_gitignore_files