Commit Diff


commit - db1d3576eb40d1c48c15fd8d531e030296874e9f
commit + bd8de4305a32b69e3b4d44c9785663889a5d9eff
blob - 263cc8d4745cef602cb6a35a07c30ca7ebb6a9a1
blob + 87b6cffd29ca3fa2f2dbb432731c506f690c7afe
--- got/got.1
+++ got/got.1
@@ -279,20 +279,37 @@ Changes created on top of staged changes are indicated
 .El
 .Pp
 For compatibility with
-.Xr cvs 1 ,
+.Xr cvs 1
+and
+.Xr git 1 ,
 .Cm got status
-parses
+reads
+.Xr glob 7
+patterns from
 .Pa .cvsignore
+and
+.Pa .gitignore
 files in each traversed directory and will not display unversioned files
-which match
+which match these patterns.
+As an extension to
 .Xr glob 7
-ignore patterns contained in
-.Pa .cvsignore
-files.
+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 Cm st
 Short alias for
 .Cm status .
blob - 729abd35f59472f4970e7b7f254344f6af2e1b79
blob + d7dc1224936d7d6588be61fac9a25737e3bc8965
--- lib/worktree.c
+++ lib/worktree.c
@@ -2382,6 +2382,15 @@ read_ignores(struct got_pathlist_head *ignores, const 
 	while ((linelen = getline(&line, &linesize, f)) != -1) {
 		if (linelen > 0 && line[linelen - 1] == '\n')
 			line[linelen - 1] = '\0';
+
+		/* Git's ignores may contain comments. */
+		if (line[0] == '#')
+			continue;
+
+		/* Git's negated patterns are not (yet?) supported. */
+		if (line[0] == '!')
+			continue;
+
 		if (asprintf(&pattern, "%s%s%s", path, path[0] ? "/" : "",
 		    line) == -1) {
 			err = got_error_from_errno("asprintf");
@@ -2415,7 +2424,34 @@ int
 match_ignores(struct got_pathlist_head *ignores, const char *path)
 {
 	struct got_pathlist_entry *pe;
+
+	/* Handle patterns which match in all directories. */
+	TAILQ_FOREACH(pe, ignores, entry) {
+		struct got_pathlist_head *ignorelist = pe->data;
+		struct got_pathlist_entry *pi;
+
+		TAILQ_FOREACH(pi, ignorelist, entry) {
+			const char *p, *pattern = pi->path;
 
+			if (strncmp(pattern, "**/", 3) != 0)
+				continue;
+			pattern += 3;
+			p = path;
+			while (*p) {
+				if (fnmatch(pattern, p,
+				    FNM_PATHNAME | FNM_LEADING_DIR)) {
+					/* Retry in next directory. */
+					while (*p && *p != '/')
+						p++;
+					while (*p == '/')
+						p++;
+					continue;
+				}
+				return 1;
+			}
+		}
+	}
+
 	/*
 	 * The ignores pathlist contains ignore lists from children before
 	 * parents, so we can find the most specific ignorelist by walking
@@ -2427,8 +2463,11 @@ match_ignores(struct got_pathlist_head *ignores, const
 			struct got_pathlist_head *ignorelist = pe->data;
 			struct got_pathlist_entry *pi;
 			TAILQ_FOREACH(pi, ignorelist, entry) {
-				if (fnmatch(pi->path, path,
-				    FNM_PATHNAME | FNM_LEADING_DIR))
+				const char *pattern = pi->path;
+				int flags = FNM_LEADING_DIR;
+				if (strstr(pattern, "/**/") == NULL)
+					flags |= FNM_PATHNAME;
+				if (fnmatch(pattern, path, flags))
 					continue;
 				return 1;
 			}
@@ -2441,15 +2480,14 @@ match_ignores(struct got_pathlist_head *ignores, const
 
 static const struct got_error *
 add_ignores(struct got_pathlist_head *ignores, const char *root_path,
-    const char *path)
+    const char *path, const char *ignores_filename)
 {
 	const struct got_error *err = NULL;
 	char *ignorespath;
 	FILE *ignoresfile = NULL;
 
-	/* TODO: read .gitignores as well... */
-	if (asprintf(&ignorespath, "%s/%s%s.cvsignore", root_path, path,
-	    path[0] ? "/" : "") == -1)
+	if (asprintf(&ignorespath, "%s/%s%s%s", root_path, path,
+	    path[0] ? "/" : "", ignores_filename) == -1)
 		return got_error_from_errno("asprintf");
 
 	ignoresfile = fopen(ignorespath, "r");
@@ -2487,8 +2525,13 @@ status_new(void *arg, struct dirent *de, const char *p
 		path = de->d_name;
 	}
 
-	if (de->d_type == DT_DIR)
-		err = add_ignores(&a->ignores, a->worktree->root_path, path);
+	if (de->d_type == DT_DIR) {
+		err = add_ignores(&a->ignores, a->worktree->root_path, path,
+		    ".cvsignore");
+		if (err == NULL)
+			err = add_ignores(&a->ignores, a->worktree->root_path,
+			    path, ".gitignore");
+	}
 	else if (got_path_is_child(path, a->status_path, a->status_path_len)
 	    && !match_ignores(&a->ignores, path))
 		err = (*a->status_cb)(a->status_arg, GOT_STATUS_UNVERSIONED,
@@ -2563,8 +2606,12 @@ worktree_status(struct got_worktree *worktree, const c
 		arg.cancel_cb = cancel_cb;
 		arg.cancel_arg = cancel_arg;
 		TAILQ_INIT(&arg.ignores);
-		err = add_ignores(&arg.ignores, worktree->root_path, path);
+		err = add_ignores(&arg.ignores, worktree->root_path, path,
+		    ".cvsignore");
 		if (err == NULL)
+			err = add_ignores(&arg.ignores, worktree->root_path,
+			    path, ".gitignore");
+		if (err == NULL)
 			err = got_fileindex_diff_dir(fileindex, workdir,
 			    worktree->root_path, path, repo, &fdiff_cb, &arg);
 		free_ignores(&arg.ignores);
blob - 5fde224adeff7a5c44b9167d6ceb971c1493e503
blob + c03418c5b2c40e6b760d26e575fb2febcd53ff37
--- regress/cmdline/status.sh
+++ regress/cmdline/status.sh
@@ -519,7 +519,56 @@ function test_status_cvsignore {
 	echo '?  .cvsignore' > $testroot/stdout.expected
 	echo '?  epsilon/.cvsignore' >> $testroot/stdout.expected
 	echo '?  epsilon/boo' >> $testroot/stdout.expected
+	echo '?  foop' >> $testroot/stdout.expected
+	(cd $testroot/wt/gamma && got status > $testroot/stdout)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
+function test_status_gitignore {
+	local testroot=`test_init status_gitignore`
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "unversioned file" > $testroot/wt/foo
+	echo "unversioned file" > $testroot/wt/foop
+	echo "unversioned file" > $testroot/wt/barp
+	echo "unversioned file" > $testroot/wt/epsilon/bar
+	echo "unversioned file" > $testroot/wt/epsilon/boo
+	echo "unversioned file" > $testroot/wt/epsilon/moo
+	mkdir -p $testroot/wt/a/b/c/
+	echo "unversioned file" > $testroot/wt/a/b/c/foo
+	echo "unversioned file" > $testroot/wt/a/b/c/zoo
+	echo "foo" > $testroot/wt/.gitignore
+	echo "bar*" >> $testroot/wt/.gitignore
+	echo "epsilon/**" >> $testroot/wt/.gitignore
+	echo "a/**/foo" >> $testroot/wt/.gitignore
+	echo "**/zoo" >> $testroot/wt/.gitignore
+
+	echo '?  .gitignore' > $testroot/stdout.expected
 	echo '?  foop' >> $testroot/stdout.expected
+	(cd $testroot/wt && got status > $testroot/stdout)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo '?  .gitignore' > $testroot/stdout.expected
+	echo '?  foop' >> $testroot/stdout.expected
 	(cd $testroot/wt/gamma && got status > $testroot/stdout)
 
 	cmp -s $testroot/stdout.expected $testroot/stdout
@@ -543,3 +592,4 @@ run_test test_status_empty_dir
 run_test test_status_empty_dir_unversioned_file
 run_test test_status_many_paths
 run_test test_status_cvsignore
+run_test test_status_gitignore