mirror of
https://git.savannah.gnu.org/git/tar.git
synced 2026-08-21 16:56:06 +00:00
Fix handling of negated and rooted entries in .gitignore files.
* src/exclist.c (struct vcs_ignore_file): New field: matchfn. (struct exclist): New field: vcs. (info_attach_exclist): Initialize vcs. (excluded_name): Rewrite using excluded_file_name_status from gnulib. (git_addfn): Special handling for entries starting with '!' and '/'. (vcs_ignore_files): Initialize matchfn member in the ".gitignore" entry. * tests/exclude23.at: New test. * tests/Makefile.am: Add new test. * tests/testsuite.at: Include new test.
This commit is contained in:
+70
-19
@@ -33,6 +33,7 @@ struct vcs_ignore_file
|
||||
int flags;
|
||||
add_fn addfn;
|
||||
void *(*initfn) (void *);
|
||||
int (*matchfn) (struct exclude *, char const *);
|
||||
void *data;
|
||||
};
|
||||
|
||||
@@ -69,6 +70,7 @@ struct exclist
|
||||
char *prefix;
|
||||
size_t prefix_len;
|
||||
struct exclude *excluded;
|
||||
struct vcs_ignore_file *vcs;
|
||||
};
|
||||
|
||||
void
|
||||
@@ -115,6 +117,7 @@ info_attach_exclist (struct tar_stat_info *dir)
|
||||
ent->flags = file->flags;
|
||||
ent->prefix_len = strlen (dir->orig_file_name);
|
||||
ent->prefix = xstrdup (dir->orig_file_name);
|
||||
ent->vcs = vcsfile;
|
||||
ent->prev = tail;
|
||||
ent->next = NULL;
|
||||
|
||||
@@ -152,7 +155,7 @@ excluded_name (char const *name, struct tar_stat_info *st)
|
||||
{
|
||||
struct exclist *ep;
|
||||
char *bname = NULL;
|
||||
bool result;
|
||||
int result;
|
||||
int nr = 0;
|
||||
|
||||
name += FILE_SYSTEM_PREFIX_LEN (name);
|
||||
@@ -164,7 +167,7 @@ excluded_name (char const *name, struct tar_stat_info *st)
|
||||
if (!st)
|
||||
return false;
|
||||
|
||||
for (result = false; st && !result; st = st->parent, nr = EXCL_NON_RECURSIVE)
|
||||
for (result = 0; st; st = st->parent, nr = EXCL_NON_RECURSIVE)
|
||||
{
|
||||
for (ep = st->exclude_list; ep; ep = ep->next)
|
||||
{
|
||||
@@ -173,27 +176,37 @@ excluded_name (char const *name, struct tar_stat_info *st)
|
||||
if (ep->flags & nr)
|
||||
continue;
|
||||
|
||||
if ((result = excluded_file_name (ep->excluded, name)))
|
||||
break;
|
||||
result = excluded_file_name_status (ep->excluded, name);
|
||||
if (result & EXCLUDED_MATCHED)
|
||||
goto end;
|
||||
|
||||
if (ep->prefix_len && strlen (name) > ep->prefix_len &&
|
||||
memcmp (name, ep->prefix, ep->prefix_len) == 0)
|
||||
rname = name + ep->prefix_len;
|
||||
else
|
||||
else
|
||||
rname = name + dotslashlen (name);
|
||||
if ((result = excluded_file_name (ep->excluded, rname)))
|
||||
break;
|
||||
result = excluded_file_name_status (ep->excluded, rname);
|
||||
if (result & EXCLUDED_MATCHED)
|
||||
goto end;
|
||||
|
||||
if (ep->vcs->matchfn)
|
||||
{
|
||||
result = ep->vcs->matchfn (ep->excluded, rname);
|
||||
if (result & EXCLUDED_MATCHED)
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (!bname)
|
||||
bname = base_name (name);
|
||||
if ((result = excluded_file_name (ep->excluded, bname)))
|
||||
break;
|
||||
result = excluded_file_name_status (ep->excluded, bname);
|
||||
if (result & EXCLUDED_MATCHED)
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
free (bname);
|
||||
|
||||
return result;
|
||||
return (result & EXCLUDED_MATCHED) ? (result & EXCLUDED_EXCLUDED) : 0;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -220,11 +233,32 @@ git_addfn (struct exclude *ex, char const *pattern, int options,
|
||||
++pattern;
|
||||
if (*pattern == 0 || *pattern == '#')
|
||||
return;
|
||||
if (*pattern == '\\' && pattern[1] == '#')
|
||||
if (*pattern == '\\' && (pattern[1] == '#' || pattern[1] == '!'))
|
||||
++pattern;
|
||||
else if (*pattern == '!')
|
||||
{
|
||||
++pattern;
|
||||
options |= EXCLUDE_INCLUDE;
|
||||
}
|
||||
else if (*pattern == '/')
|
||||
{
|
||||
options |= EXCLUDE_ANCHORED;
|
||||
}
|
||||
add_exclude (ex, pattern, options);
|
||||
}
|
||||
|
||||
static int
|
||||
git_matchfn (struct exclude *ex, char const *name)
|
||||
{
|
||||
int rc;
|
||||
char *rname = xmalloc (strlen (name) + 2);
|
||||
rname[0] = '/';
|
||||
strcpy (rname + 1, name);
|
||||
rc = excluded_file_name_status (ex, rname);
|
||||
free (rname);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void
|
||||
bzr_addfn (struct exclude *ex, char const *pattern, int options,
|
||||
void *UNNAMED (data))
|
||||
@@ -303,18 +337,35 @@ hg_addfn (struct exclude *ex, char const *pattern, int options, void *data)
|
||||
}
|
||||
|
||||
static struct vcs_ignore_file vcs_ignore_files[] = {
|
||||
{ ".cvsignore", EXCL_NON_RECURSIVE, cvs_addfn, NULL, NULL },
|
||||
{ ".gitignore", 0, git_addfn, NULL, NULL },
|
||||
{ ".bzrignore", 0, bzr_addfn, NULL, NULL },
|
||||
{ ".hgignore", 0, hg_addfn, hg_initfn, NULL },
|
||||
{ NULL, 0, git_addfn, NULL, NULL }
|
||||
{
|
||||
.filename = ".cvsignore",
|
||||
.flags = EXCL_NON_RECURSIVE,
|
||||
.addfn = cvs_addfn
|
||||
},
|
||||
{
|
||||
.filename = ".gitignore",
|
||||
.addfn = git_addfn,
|
||||
.matchfn = git_matchfn
|
||||
},
|
||||
{
|
||||
.filename = ".bzrignore",
|
||||
.addfn = bzr_addfn
|
||||
},
|
||||
{
|
||||
.filename = ".hgignore",
|
||||
.addfn = hg_addfn,
|
||||
.initfn = hg_initfn
|
||||
},
|
||||
{
|
||||
.addfn = git_addfn
|
||||
}
|
||||
};
|
||||
|
||||
static struct vcs_ignore_file *
|
||||
get_vcs_ignore_file (const char *name)
|
||||
{
|
||||
struct vcs_ignore_file *p;
|
||||
|
||||
struct vcs_ignore_file *p
|
||||
;
|
||||
for (p = vcs_ignore_files; p->filename; p++)
|
||||
if (streq (p->filename, name))
|
||||
break;
|
||||
|
||||
@@ -112,6 +112,7 @@ TESTSUITE_AT = \
|
||||
exclude20.at\
|
||||
exclude21.at\
|
||||
exclude22.at\
|
||||
exclude23.at\
|
||||
extrac01.at\
|
||||
extrac02.at\
|
||||
extrac03.at\
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
AT_SETUP([--exclude-ignore-recursive with transforms])
|
||||
AT_KEYWORDS([exclude exclude-ignore-recursive extract exclude22])
|
||||
AT_TAR_CHECK([
|
||||
AT_SORT_PREREQ
|
||||
mkdir dir dir/subdir
|
||||
touch dir/file1 dir/file.ext dir/subdir/file2.foo dir/subdir/file3 \
|
||||
dir/subdir/file4.txt dir/subdir/file5.ext
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# Process this file with autom4te to create testsuite. -*- Autotest -*-
|
||||
|
||||
# Test suite for GNU tar.
|
||||
# Copyright 2026 Free Software Foundation, Inc.
|
||||
|
||||
# This file is part of GNU tar.
|
||||
|
||||
# GNU tar is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# GNU tar is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
AT_SETUP([gitignore excludes])
|
||||
AT_KEYWORDS([exclude gitignore extract exclude23])
|
||||
AT_TAR_CHECK([
|
||||
AT_SORT_PREREQ
|
||||
# Test whether negations (!) and rooted patterns (/) work in
|
||||
# .gitignore files.
|
||||
mkdir dir dir/subdir
|
||||
touch dir/subdir/some.1 dir/subdir/any.1 dir/subdir/bar dir/subdir/FILE
|
||||
cat > dir/subdir/.gitignore <<EOT
|
||||
!any.1
|
||||
EOT
|
||||
touch dir/foo.1 dir/file.1 dir/file.2 dir/file.3 dir/file.4 dir/FILE
|
||||
cat > dir/.gitignore <<EOT
|
||||
*.1
|
||||
*.2
|
||||
!foo.1
|
||||
*.3
|
||||
/FILE
|
||||
/subdir/b?r
|
||||
EOT
|
||||
set -e
|
||||
tar --exclude-ignore-recursive=.gitignore -cf dir.tar dir
|
||||
tar tf dir.tar | sort
|
||||
],
|
||||
[0],
|
||||
[dir/
|
||||
dir/.gitignore
|
||||
dir/file.4
|
||||
dir/foo.1
|
||||
dir/subdir/
|
||||
dir/subdir/.gitignore
|
||||
dir/subdir/FILE
|
||||
dir/subdir/any.1
|
||||
])
|
||||
AT_CLEANUP
|
||||
@@ -319,6 +319,7 @@ m4_include([exclude19.at])
|
||||
m4_include([exclude20.at])
|
||||
m4_include([exclude21.at])
|
||||
m4_include([exclude22.at])
|
||||
m4_include([exclude23.at])
|
||||
|
||||
AT_BANNER([Deletions])
|
||||
m4_include([delete01.at])
|
||||
|
||||
Reference in New Issue
Block a user