commit - db1d3576eb40d1c48c15fd8d531e030296874e9f
commit + bd8de4305a32b69e3b4d44c9785663889a5d9eff
blob - 263cc8d4745cef602cb6a35a07c30ca7ebb6a9a1
blob + 87b6cffd29ca3fa2f2dbb432731c506f690c7afe
--- got/got.1
+++ got/got.1
.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
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");
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
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;
}
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");
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,
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
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
run_test test_status_empty_dir_unversioned_file
run_test test_status_many_paths
run_test test_status_cvsignore
+run_test test_status_gitignore