2019-01-24 11:29:12 +03:00
|
|
|
#define USE_THE_INDEX_COMPATIBILITY_MACROS
|
2013-01-06 20:58:13 +04:00
|
|
|
#include "builtin.h"
|
|
|
|
#include "cache.h"
|
2017-06-14 21:07:36 +03:00
|
|
|
#include "config.h"
|
2013-01-06 20:58:13 +04:00
|
|
|
#include "dir.h"
|
|
|
|
#include "quote.h"
|
|
|
|
#include "pathspec.h"
|
|
|
|
#include "parse-options.h"
|
2017-05-12 01:04:24 +03:00
|
|
|
#include "submodule.h"
|
2013-01-06 20:58:13 +04:00
|
|
|
|
2013-09-05 20:08:01 +04:00
|
|
|
static int quiet, verbose, stdin_paths, show_non_matching, no_index;
|
2013-01-06 20:58:13 +04:00
|
|
|
static const char * const check_ignore_usage[] = {
|
2015-01-13 10:44:47 +03:00
|
|
|
"git check-ignore [<options>] <pathname>...",
|
usage: do not insist that standard input must come from a file
The synopsys text and the usage string of subcommands that read list
of things from the standard input are often shown like this:
git gostak [--distim] < <list-of-doshes>
This is problematic in a number of ways:
* The way to use these commands is more often to feed them the
output from another command, not feed them from a file.
* Manual pages outside Git, commands that operate on the data read
from the standard input, e.g "sort", "grep", "sed", etc., are not
described with such a "< redirection-from-file" in their synopsys
text. Our doing so introduces inconsistency.
* We do not insist on where the output should go, by saying
git gostak [--distim] < <list-of-doshes> > <output>
* As it is our convention to enclose placeholders inside <braket>,
the redirection operator followed by a placeholder filename
becomes very hard to read, both in the documentation and in the
help text.
Let's clean them all up, after making sure that the documentation
clearly describes the modes that take information from the standard
input and what kind of things are expected on the input.
[jc: stole example for fmt-merge-msg from Jonathan]
Helped-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-10-16 21:27:42 +03:00
|
|
|
"git check-ignore [<options>] --stdin",
|
2013-01-06 20:58:13 +04:00
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
2013-07-12 10:05:48 +04:00
|
|
|
static int nul_term_line;
|
2013-01-06 20:58:13 +04:00
|
|
|
|
|
|
|
static const struct option check_ignore_options[] = {
|
|
|
|
OPT__QUIET(&quiet, N_("suppress progress reporting")),
|
|
|
|
OPT__VERBOSE(&verbose, N_("be verbose")),
|
|
|
|
OPT_GROUP(""),
|
2013-08-03 15:51:19 +04:00
|
|
|
OPT_BOOL(0, "stdin", &stdin_paths,
|
|
|
|
N_("read file names from stdin")),
|
2013-09-04 23:39:02 +04:00
|
|
|
OPT_BOOL('z', NULL, &nul_term_line,
|
|
|
|
N_("terminate input and output records by a NUL character")),
|
2013-08-03 15:51:19 +04:00
|
|
|
OPT_BOOL('n', "non-matching", &show_non_matching,
|
|
|
|
N_("show non-matching input paths")),
|
2013-09-05 20:08:01 +04:00
|
|
|
OPT_BOOL(0, "no-index", &no_index,
|
|
|
|
N_("ignore index when checking")),
|
2013-01-06 20:58:13 +04:00
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
|
2019-09-03 21:04:55 +03:00
|
|
|
static void output_pattern(const char *path, struct path_pattern *pattern)
|
2013-01-06 20:58:13 +04:00
|
|
|
{
|
2019-09-03 21:04:56 +03:00
|
|
|
char *bang = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE) ? "!" : "";
|
|
|
|
char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : "";
|
2013-07-12 10:05:48 +04:00
|
|
|
if (!nul_term_line) {
|
2013-01-06 20:58:13 +04:00
|
|
|
if (!verbose) {
|
|
|
|
write_name_quoted(path, stdout, '\n');
|
|
|
|
} else {
|
2019-09-03 21:04:55 +03:00
|
|
|
if (pattern) {
|
2019-09-03 21:04:56 +03:00
|
|
|
quote_c_style(pattern->pl->src, NULL, stdout, 0);
|
2013-04-11 16:05:10 +04:00
|
|
|
printf(":%d:%s%s%s\t",
|
2019-09-03 21:04:55 +03:00
|
|
|
pattern->srcpos,
|
|
|
|
bang, pattern->pattern, slash);
|
2013-04-11 16:05:10 +04:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
printf("::\t");
|
|
|
|
}
|
2013-01-06 20:58:13 +04:00
|
|
|
quote_c_style(path, NULL, stdout, 0);
|
|
|
|
fputc('\n', stdout);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!verbose) {
|
|
|
|
printf("%s%c", path, '\0');
|
|
|
|
} else {
|
2019-09-03 21:04:55 +03:00
|
|
|
if (pattern)
|
2013-04-11 16:05:10 +04:00
|
|
|
printf("%s%c%d%c%s%s%s%c%s%c",
|
2019-09-03 21:04:56 +03:00
|
|
|
pattern->pl->src, '\0',
|
2019-09-03 21:04:55 +03:00
|
|
|
pattern->srcpos, '\0',
|
|
|
|
bang, pattern->pattern, slash, '\0',
|
2013-04-11 16:05:10 +04:00
|
|
|
path, '\0');
|
|
|
|
else
|
|
|
|
printf("%c%c%c%s%c", '\0', '\0', '\0', path, '\0');
|
2013-01-06 20:58:13 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-30 01:23:39 +04:00
|
|
|
static int check_ignore(struct dir_struct *dir,
|
2013-07-14 12:35:45 +04:00
|
|
|
const char *prefix, int argc, const char **argv)
|
2013-01-06 20:58:13 +04:00
|
|
|
{
|
2013-07-14 12:35:45 +04:00
|
|
|
const char *full_path;
|
2013-01-06 20:58:13 +04:00
|
|
|
char *seen;
|
2018-02-10 15:38:29 +03:00
|
|
|
int num_ignored = 0, i;
|
2019-09-03 21:04:55 +03:00
|
|
|
struct path_pattern *pattern;
|
2013-07-14 12:35:45 +04:00
|
|
|
struct pathspec pathspec;
|
2013-01-06 20:58:13 +04:00
|
|
|
|
2013-07-14 12:35:45 +04:00
|
|
|
if (!argc) {
|
2013-01-06 20:58:13 +04:00
|
|
|
if (!quiet)
|
|
|
|
fprintf(stderr, "no pathspec given.\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-07-14 12:35:45 +04:00
|
|
|
/*
|
|
|
|
* check-ignore just needs paths. Magic beyond :/ is really
|
|
|
|
* irrelevant.
|
|
|
|
*/
|
|
|
|
parse_pathspec(&pathspec,
|
|
|
|
PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP,
|
|
|
|
PATHSPEC_SYMLINK_LEADING_PATH |
|
|
|
|
PATHSPEC_KEEP_ORDER,
|
|
|
|
prefix, argv);
|
|
|
|
|
2017-05-12 01:04:24 +03:00
|
|
|
die_path_inside_submodule(&the_index, &pathspec);
|
|
|
|
|
2013-01-06 20:58:13 +04:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2021-04-08 23:41:25 +03:00
|
|
|
seen = find_pathspecs_matching_against_index(&pathspec, &the_index,
|
|
|
|
PS_HEED_SKIP_WORKTREE);
|
2013-07-14 12:35:45 +04:00
|
|
|
for (i = 0; i < pathspec.nr; i++) {
|
2013-07-14 12:36:00 +04:00
|
|
|
full_path = pathspec.items[i].match;
|
2019-09-03 21:04:55 +03:00
|
|
|
pattern = NULL;
|
2013-02-19 23:56:44 +04:00
|
|
|
if (!seen[i]) {
|
2018-02-10 15:38:29 +03:00
|
|
|
int dtype = DT_UNKNOWN;
|
2019-09-03 21:04:57 +03:00
|
|
|
pattern = last_matching_pattern(dir, &the_index,
|
2017-05-05 22:53:30 +03:00
|
|
|
full_path, &dtype);
|
check-ignore: fix documentation and implementation to match
check-ignore has two different modes, and neither of these modes has an
implementation that matches the documentation. These modes differ in
whether they just print paths or whether they also print the final
pattern matched by the path. The fix is different for both modes, so
I'll discuss both separately.
=== First (default) mode ===
The first mode is documented as:
For each pathname given via the command-line or from a file via
--stdin, check whether the file is excluded by .gitignore (or other
input files to the exclude mechanism) and output the path if it is
excluded.
However, it fails to do this because it did not account for negated
patterns. Commands other than check-ignore verify exclusion rules via
calling
... -> treat_one_path() -> is_excluded() -> last_matching_pattern()
while check-ignore has a call path of the form:
... -> check_ignore() -> last_matching_pattern()
The fact that the latter does not include the call to is_excluded()
means that it is susceptible to to messing up negated patterns (since
that is the only significant thing is_excluded() adds over
last_matching_pattern()). Unfortunately, we can't make it just call
is_excluded(), because the same codepath is used by the verbose mode
which needs to know the matched pattern in question. This brings us
to...
=== Second (verbose) mode ===
The second mode, known as verbose mode, references the first in the
documentation and says:
Also output details about the matching pattern (if any) for each
given pathname. For precedence rules within and between exclude
sources, see gitignore(5).
The "Also" means it will print patterns that match the exclude rules as
noted for the first mode, and also print which pattern matches. Unless
more information is printed than just pathname and pattern (which is not
done), this definition is somewhat ill-defined and perhaps even
self-contradictory for negated patterns: A path which matches a negated
exclude pattern is NOT excluded and thus shouldn't be printed by the
former logic, while it certainly does match one of the explicit patterns
and thus should be printed by the latter logic.
=== Resolution ==
Since the second mode exists to find out which pattern matches given
paths, and showing the user a pattern that begins with a '!' is
sufficient for them to figure out whether the pattern is excluded, the
existing behavior is desirable -- we just need to update the
documentation to match the implementation (i.e. it is about printing
which pattern is matched by paths, not about showing which paths are
excluded).
For the first or default mode, users just want to know whether a pattern
is excluded. As such, the existing documentation is desirable; change
the implementation to match the documented behavior.
Finally, also adjust a few tests in t0008 that were caught up by this
discrepancy in how negated paths were handled.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-19 02:05:37 +03:00
|
|
|
if (!verbose && pattern &&
|
|
|
|
pattern->flags & PATTERN_FLAG_NEGATIVE)
|
|
|
|
pattern = NULL;
|
2013-01-06 20:58:13 +04:00
|
|
|
}
|
2019-09-03 21:04:55 +03:00
|
|
|
if (!quiet && (pattern || show_non_matching))
|
|
|
|
output_pattern(pathspec.items[i].original, pattern);
|
|
|
|
if (pattern)
|
2013-04-11 16:05:10 +04:00
|
|
|
num_ignored++;
|
2013-01-06 20:58:13 +04:00
|
|
|
}
|
|
|
|
free(seen);
|
2021-04-25 17:16:14 +03:00
|
|
|
clear_pathspec(&pathspec);
|
2013-01-06 20:58:13 +04:00
|
|
|
|
|
|
|
return num_ignored;
|
|
|
|
}
|
|
|
|
|
2013-05-30 01:23:39 +04:00
|
|
|
static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix)
|
2013-01-06 20:58:13 +04:00
|
|
|
{
|
2016-01-31 14:25:26 +03:00
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
struct strbuf unquoted = STRBUF_INIT;
|
2013-04-11 16:05:12 +04:00
|
|
|
char *pathspec[2] = { NULL, NULL };
|
2016-01-15 00:31:17 +03:00
|
|
|
strbuf_getline_fn getline_fn;
|
2013-04-11 16:05:12 +04:00
|
|
|
int num_ignored = 0;
|
2013-01-06 20:58:13 +04:00
|
|
|
|
2016-01-15 00:31:17 +03:00
|
|
|
getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf;
|
|
|
|
while (getline_fn(&buf, stdin) != EOF) {
|
|
|
|
if (!nul_term_line && buf.buf[0] == '"') {
|
2016-01-31 14:25:26 +03:00
|
|
|
strbuf_reset(&unquoted);
|
|
|
|
if (unquote_c_style(&unquoted, buf.buf, NULL))
|
2013-01-06 20:58:13 +04:00
|
|
|
die("line is badly quoted");
|
2016-01-31 14:25:26 +03:00
|
|
|
strbuf_swap(&buf, &unquoted);
|
2013-01-06 20:58:13 +04:00
|
|
|
}
|
2013-04-11 16:05:12 +04:00
|
|
|
pathspec[0] = buf.buf;
|
2013-07-14 12:35:45 +04:00
|
|
|
num_ignored += check_ignore(dir, prefix,
|
|
|
|
1, (const char **)pathspec);
|
2013-04-11 16:05:12 +04:00
|
|
|
maybe_flush_or_die(stdout, "check-ignore to stdout");
|
2013-01-06 20:58:13 +04:00
|
|
|
}
|
|
|
|
strbuf_release(&buf);
|
2016-01-31 14:25:26 +03:00
|
|
|
strbuf_release(&unquoted);
|
2013-01-06 20:58:13 +04:00
|
|
|
return num_ignored;
|
|
|
|
}
|
|
|
|
|
|
|
|
int cmd_check_ignore(int argc, const char **argv, const char *prefix)
|
|
|
|
{
|
|
|
|
int num_ignored;
|
2021-07-01 13:51:27 +03:00
|
|
|
struct dir_struct dir = DIR_INIT;
|
2013-01-06 20:58:13 +04:00
|
|
|
|
|
|
|
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 {
|
2013-07-12 10:05:48 +04:00
|
|
|
if (nul_term_line)
|
2013-01-06 20:58:13 +04:00
|
|
|
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"));
|
|
|
|
}
|
2013-04-11 16:05:10 +04:00
|
|
|
if (show_non_matching && !verbose)
|
|
|
|
die(_("--non-matching is only valid with --verbose"));
|
2013-01-06 20:58:13 +04:00
|
|
|
|
2013-04-11 16:05:11 +04:00
|
|
|
/* read_cache() is only necessary so we can watch out for submodules. */
|
2013-09-05 20:08:01 +04:00
|
|
|
if (!no_index && read_cache() < 0)
|
2013-04-11 16:05:11 +04:00
|
|
|
die(_("index file corrupt"));
|
|
|
|
|
|
|
|
setup_standard_excludes(&dir);
|
2013-01-06 20:58:13 +04:00
|
|
|
|
|
|
|
if (stdin_paths) {
|
2013-05-30 01:23:39 +04:00
|
|
|
num_ignored = check_ignore_stdin_paths(&dir, prefix);
|
2013-01-06 20:58:13 +04:00
|
|
|
} else {
|
2013-07-14 12:35:45 +04:00
|
|
|
num_ignored = check_ignore(&dir, prefix, argc, argv);
|
2013-01-06 20:58:13 +04:00
|
|
|
maybe_flush_or_die(stdout, "ignore to stdout");
|
|
|
|
}
|
|
|
|
|
dir: fix problematic API to avoid memory leaks
The dir structure seemed to have a number of leaks and problems around
it. First I noticed that parent_hashmap and recursive_hashmap were
being leaked (though Peff noticed and submitted fixes before me). Then
I noticed in the previous commit that clear_directory() was only taking
responsibility for a subset of fields within dir_struct, despite the
fact that entries[] and ignored[] we allocated internally to dir.c.
That, of course, resulted in many callers either leaking or haphazardly
trying to free these arrays and their contents.
Digging further, I found that despite the pretty clear documentation
near the top of dir.h that folks were supposed to call clear_directory()
when the user no longer needed the dir_struct, there were four callers
that didn't bother doing that at all. However, two of them clearly
thought about leaks since they had an UNLEAK(dir) directive, which to me
suggests that the method to free the data was too unclear. I suspect
the non-obviousness of the API and its holes led folks to avoid it,
which then snowballed into further problems with the entries[],
ignored[], parent_hashmap, and recursive_hashmap problems.
Rename clear_directory() to dir_clear() to be more in line with other
data structures in git, and introduce a dir_init() to handle the
suggested memsetting of dir_struct to all zeroes. I hope that a name
like "dir_clear()" is more clear, and that the presence of dir_init()
will provide a hint to those looking at the code that they need to look
for either a dir_clear() or a dir_free() and lead them to find
dir_clear().
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-08-19 01:58:26 +03:00
|
|
|
dir_clear(&dir);
|
2013-04-11 16:05:11 +04:00
|
|
|
|
2013-01-06 20:58:13 +04:00
|
|
|
return !num_ignored;
|
|
|
|
}
|