Skip to content

Commit

Permalink
add git-check-ignore sub-command
Browse files Browse the repository at this point in the history
This works in a similar manner to git-check-attr.

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 <git@adamspiers.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
aspiers authored and gitster committed Jan 6, 2013
1 parent 1794e6e commit 368aa52
Show file tree
Hide file tree
Showing 11 changed files with 905 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
/git-bundle
/git-cat-file
/git-check-attr
/git-check-ignore
/git-check-ref-format
/git-checkout
/git-checkout-index
Expand Down
89 changes: 89 additions & 0 deletions Documentation/git-check-ignore.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
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`, show the pattern from .gitignore (or other input files to
the exclude mechanism) that decides if the pathname is excluded or
included. Later patterns within a file take precedence over earlier
ones.

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 pathnames in the output are delimited by the
null character; if `--verbose` is also specified then null characters
are also used instead of colons and hard tabs:

<source> <NULL> <linenum> <NULL> <pattern> <NULL> <pathname> <NULL>


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
6 changes: 4 additions & 2 deletions Documentation/gitignore.txt
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,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
---
Expand Down
2 changes: 1 addition & 1 deletion Documentation/technical/api-directory-listing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,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.
* Call `clear_directory()` when none of the contained elements are no longer in use.

(JC)
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,7 @@ BUILTIN_OBJS += builtin/branch.o
BUILTIN_OBJS += builtin/bundle.o
BUILTIN_OBJS += builtin/cat-file.o
BUILTIN_OBJS += builtin/check-attr.o
BUILTIN_OBJS += builtin/check-ignore.o
BUILTIN_OBJS += builtin/check-ref-format.o
BUILTIN_OBJS += builtin/checkout-index.o
BUILTIN_OBJS += builtin/checkout.o
Expand Down
1 change: 1 addition & 0 deletions builtin.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
extern int cmd_checkout(int argc, const char **argv, const char *prefix);
extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
extern int cmd_check_ignore(int argc, const char **argv, const char *prefix);
extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
extern int cmd_cherry(int argc, const char **argv, const char *prefix);
extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
Expand Down
173 changes: 173 additions & 0 deletions builtin/check-ignore.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#include "builtin.h"
#include "cache.h"
#include "dir.h"
#include "quote.h"
#include "pathspec.h"
#include "parse-options.h"

static int quiet, verbose, stdin_paths;
static const char * const check_ignore_usage[] = {
"git check-ignore [options] pathname...",
"git check-ignore [options] --stdin < <list-of-paths>",
NULL
};

static int null_term_line;

static const struct option check_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()
};

static void output_exclude(const char *path, struct exclude *exclude)
{
char *bang = exclude->flags & EXC_FLAG_NEGATIVE ? "!" : "";
char *slash = exclude->flags & EXC_FLAG_MUSTBEDIR ? "/" : "";
if (!null_term_line) {
if (!verbose) {
write_name_quoted(path, stdout, '\n');
} else {
quote_c_style(exclude->el->src, NULL, stdout, 0);
printf(":%d:%s%s%s\t",
exclude->srcpos,
bang, exclude->pattern, slash);
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->el->src, '\0',
exclude->srcpos, '\0',
bang, exclude->pattern, slash, '\0',
path, '\0');
}
}
}

static int check_ignore(const char *prefix, const char **pathspec)
{
struct dir_struct dir;
const char *path, *full_path;
char *seen;
int num_ignored = 0, dtype = DT_UNKNOWN, i;
struct path_exclude_check check;
struct exclude *exclude;

/* 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 || !*pathspec) {
if (!quiet)
fprintf(stderr, "no pathspec given.\n");
return 0;
}

path_exclude_check_init(&check, &dir);
/*
* look for pathspecs matching entries in the index, since these
* should not be ignored, in order to be consistent with
* 'git status', 'git add' etc.
*/
seen = find_pathspecs_matching_against_index(pathspec);
for (i = 0; pathspec[i]; i++) {
path = pathspec[i];
full_path = prefix_path(prefix, prefix
? strlen(prefix) : 0, path);
full_path = check_path_for_gitlink(full_path);
die_if_path_beyond_symlink(full_path, prefix);
if (!seen[i] && path[0]) {
exclude = last_exclude_matching_path(&check, full_path,
-1, &dtype);
if (exclude) {
if (!quiet)
output_exclude(path, exclude);
num_ignored++;
}
}
}
free(seen);
clear_directory(&dir);
path_exclude_check_clear(&check);

return num_ignored;
}

static int check_ignore_stdin_paths(const char *prefix)
{
struct strbuf buf, nbuf;
char **pathspec = NULL;
size_t nr = 0, alloc = 0;
int line_termination = null_term_line ? 0 : '\n';
int num_ignored;

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;
num_ignored = check_ignore(prefix, (const char **)pathspec);
maybe_flush_or_die(stdout, "attribute to stdout");
strbuf_release(&buf);
strbuf_release(&nbuf);
free(pathspec);
return num_ignored;
}

int cmd_check_ignore(int argc, const char **argv, const char *prefix)
{
int num_ignored;

git_config(git_default_config, NULL);

argc = parse_options(argc, argv, prefix, check_ignore_options,
check_ignore_usage, 0);

if (stdin_paths) {
if (argc > 0)
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"));
}

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");
}

return !num_ignored;
}
1 change: 1 addition & 0 deletions command-list.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ git-branch mainporcelain common
git-bundle mainporcelain
git-cat-file plumbinginterrogators
git-check-attr purehelpers
git-check-ignore purehelpers
git-checkout mainporcelain common
git-checkout-index plumbingmanipulators
git-check-ref-format purehelpers
Expand Down
1 change: 1 addition & 0 deletions contrib/completion/git-completion.bash
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,7 @@ __git_list_porcelain_commands ()
archimport) : import;;
cat-file) : plumbing;;
check-attr) : plumbing;;
check-ignore) : plumbing;;
check-ref-format) : plumbing;;
checkout-index) : plumbing;;
commit-tree) : plumbing;;
Expand Down
1 change: 1 addition & 0 deletions git.c
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "bundle", cmd_bundle, RUN_SETUP_GENTLY },
{ "cat-file", cmd_cat_file, RUN_SETUP },
{ "check-attr", cmd_check_attr, RUN_SETUP },
{ "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE },
{ "check-ref-format", cmd_check_ref_format },
{ "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
{ "checkout-index", cmd_checkout_index,
Expand Down
Loading

0 comments on commit 368aa52

Please sign in to comment.