#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n"
/**
- * A negative ignore pattern can match a positive one without
- * wildcards if its pattern equals the tail of the positive
- * pattern. Thus
+ * A negative ignore pattern can negate a positive one without
+ * wildcards if it is a basename only and equals the basename of
+ * the positive pattern. Thus
*
* foo/bar
* !bar
*
- * would result in foo/bar being unignored again.
+ * would result in foo/bar being unignored again while
+ *
+ * moo/foo/bar
+ * !foo/bar
+ *
+ * would do nothing. The reverse also holds true: a positive
+ * basename pattern can be negated by unignoring the basename in
+ * subdirectories. Thus
+ *
+ * bar
+ * !foo/bar
+ *
+ * would result in foo/bar being unignored again. As with the
+ * first case,
+ *
+ * foo/bar
+ * !moo/foo/bar
+ *
+ * would do nothing, again.
*/
static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg)
{
+ git_attr_fnmatch *longer, *shorter;
char *p;
if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0
&& (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0) {
- /*
- * no chance of matching if rule is shorter than
- * the negated one
- */
- if (rule->length < neg->length)
+
+ /* If lengths match we need to have an exact match */
+ if (rule->length == neg->length) {
+ return strcmp(rule->pattern, neg->pattern) == 0;
+ } else if (rule->length < neg->length) {
+ shorter = rule;
+ longer = neg;
+ } else {
+ shorter = neg;
+ longer = rule;
+ }
+
+ /* Otherwise, we need to check if the shorter
+ * rule is a basename only (that is, it contains
+ * no path separator) and, if so, if it
+ * matches the tail of the longer rule */
+ p = longer->pattern + longer->length - shorter->length;
+
+ if (p[-1] != '/')
+ return false;
+ if (memchr(shorter->pattern, '/', shorter->length) != NULL)
return false;
- /*
- * shift pattern so its tail aligns with the
- * negated pattern
- */
- p = rule->pattern + rule->length - neg->length;
- if (strcmp(p, neg->pattern) == 0)
- return true;
+ return memcmp(p, shorter->pattern, shorter->length) == 0;
}
return false;
assert_is_ignored("padded_parent/child8/bar.txt");
}
+void test_status_ignore__unignore_entry_in_ignored_dir(void)
+{
+ static const char *test_files[] = {
+ "empty_standard_repo/bar.txt",
+ "empty_standard_repo/parent/bar.txt",
+ "empty_standard_repo/parent/child/bar.txt",
+ "empty_standard_repo/nested/parent/child/bar.txt",
+ NULL
+ };
+
+ make_test_data("empty_standard_repo", test_files);
+ cl_git_mkfile(
+ "empty_standard_repo/.gitignore",
+ "bar.txt\n"
+ "!parent/child/bar.txt\n");
+
+ assert_is_ignored("bar.txt");
+ assert_is_ignored("parent/bar.txt");
+ refute_is_ignored("parent/child/bar.txt");
+ assert_is_ignored("nested/parent/child/bar.txt");
+}
+
+void test_status_ignore__do_not_unignore_basename_prefix(void)
+{
+ static const char *test_files[] = {
+ "empty_standard_repo/foo_bar.txt",
+ NULL
+ };
+
+ make_test_data("empty_standard_repo", test_files);
+ cl_git_mkfile(
+ "empty_standard_repo/.gitignore",
+ "foo_bar.txt\n"
+ "!bar.txt\n");
+
+ assert_is_ignored("foo_bar.txt");
+}
+
void test_status_ignore__filename_with_cr(void)
{
int ignored;