mirror of
https://git.savannah.gnu.org/git/tar.git
synced 2026-08-22 09:16:02 +00:00
* 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.
384 lines
8.3 KiB
C
384 lines
8.3 KiB
C
/* Per-directory exclusion files for tar.
|
|
|
|
Copyright 2014-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/>.
|
|
*/
|
|
#include <system.h>
|
|
#include <c-ctype.h>
|
|
#include <quotearg.h>
|
|
#include <flexmember.h>
|
|
#include <fnmatch.h>
|
|
#include <wordsplit.h>
|
|
#include "common.h"
|
|
|
|
typedef void (*add_fn) (struct exclude *, char const *, int, void *);
|
|
|
|
struct vcs_ignore_file
|
|
{
|
|
char const *filename;
|
|
int flags;
|
|
add_fn addfn;
|
|
void *(*initfn) (void *);
|
|
int (*matchfn) (struct exclude *, char const *);
|
|
void *data;
|
|
};
|
|
|
|
static struct vcs_ignore_file *get_vcs_ignore_file (const char *name);
|
|
|
|
struct excfile
|
|
{
|
|
struct excfile *next;
|
|
int flags;
|
|
char name[FLEXIBLE_ARRAY_MEMBER];
|
|
};
|
|
|
|
static struct excfile *excfile_head, *excfile_tail;
|
|
|
|
void
|
|
excfile_add (const char *name, int flags)
|
|
{
|
|
struct excfile *p = xmalloc (FLEXNSIZEOF (struct excfile, name,
|
|
strlen (name) + 1));
|
|
p->next = NULL;
|
|
p->flags = flags;
|
|
strcpy (p->name, name);
|
|
if (excfile_tail)
|
|
excfile_tail->next = p;
|
|
else
|
|
excfile_head = p;
|
|
excfile_tail = p;
|
|
}
|
|
|
|
struct exclist
|
|
{
|
|
struct exclist *next, *prev;
|
|
int flags;
|
|
char *prefix;
|
|
size_t prefix_len;
|
|
struct exclude *excluded;
|
|
struct vcs_ignore_file *vcs;
|
|
};
|
|
|
|
void
|
|
info_attach_exclist (struct tar_stat_info *dir)
|
|
{
|
|
struct exclist *head = NULL, *tail = NULL;
|
|
if (dir->exclude_list)
|
|
return;
|
|
|
|
for (struct excfile *file = excfile_head; file; file = file->next)
|
|
{
|
|
if (faccessat (dir->fd, file->name, F_OK, 0) == 0)
|
|
{
|
|
int fd = subfile_open (dir, file->name, O_RDONLY);
|
|
if (fd < 0)
|
|
{
|
|
open_error (file->name);
|
|
continue;
|
|
}
|
|
FILE *fp = fdopen (fd, "r");
|
|
if (!fp)
|
|
{
|
|
paxerror (errno, _("%s: fdopen failed"), file->name);
|
|
close (fd);
|
|
continue;
|
|
}
|
|
|
|
struct exclude *ex = new_exclude ();
|
|
|
|
struct vcs_ignore_file *vcsfile = get_vcs_ignore_file (file->name);
|
|
|
|
if (vcsfile->initfn)
|
|
vcsfile->data = vcsfile->initfn (vcsfile->data);
|
|
|
|
if (add_exclude_fp (vcsfile->addfn, ex, fp,
|
|
FNM_FILE_NAME|EXCLUDE_WILDCARDS|EXCLUDE_ANCHORED,
|
|
'\n',
|
|
vcsfile->data))
|
|
paxfatal (errno, "%s", quotearg_colon (file->name));
|
|
fclose (fp);
|
|
|
|
struct exclist *ent = xmalloc (sizeof *ent);
|
|
ent->excluded = ex;
|
|
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;
|
|
|
|
if (tail)
|
|
tail->next = ent;
|
|
else
|
|
head = ent;
|
|
tail = ent;
|
|
}
|
|
}
|
|
dir->exclude_list = head;
|
|
}
|
|
|
|
void
|
|
info_free_exclist (struct tar_stat_info *dir)
|
|
{
|
|
struct exclist *ep = dir->exclude_list;
|
|
|
|
while (ep)
|
|
{
|
|
struct exclist *next = ep->next;
|
|
free_exclude (ep->excluded);
|
|
free (ep->prefix);
|
|
free (ep);
|
|
ep = next;
|
|
}
|
|
|
|
dir->exclude_list = NULL;
|
|
}
|
|
|
|
|
|
/* Return nonzero if file NAME is excluded. */
|
|
bool
|
|
excluded_name (char const *name, struct tar_stat_info *st)
|
|
{
|
|
struct exclist *ep;
|
|
char *bname = NULL;
|
|
int result;
|
|
int nr = 0;
|
|
|
|
name += FILE_SYSTEM_PREFIX_LEN (name);
|
|
|
|
/* Try global exclusion list first */
|
|
if (excluded_file_name (excluded, name))
|
|
return true;
|
|
|
|
if (!st)
|
|
return false;
|
|
|
|
for (result = 0; st; st = st->parent, nr = EXCL_NON_RECURSIVE)
|
|
{
|
|
for (ep = st->exclude_list; ep; ep = ep->next)
|
|
{
|
|
const char *rname;
|
|
|
|
if (ep->flags & nr)
|
|
continue;
|
|
|
|
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
|
|
rname = name + dotslashlen (name);
|
|
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);
|
|
result = excluded_file_name_status (ep->excluded, bname);
|
|
if (result & EXCLUDED_MATCHED)
|
|
goto end;
|
|
}
|
|
}
|
|
end:
|
|
free (bname);
|
|
|
|
return (result & EXCLUDED_MATCHED) ? (result & EXCLUDED_EXCLUDED) : 0;
|
|
}
|
|
|
|
static void
|
|
cvs_addfn (struct exclude *ex, char const *pattern, int options,
|
|
void *UNNAMED (data))
|
|
{
|
|
struct wordsplit ws;
|
|
|
|
options |= EXCLUDE_ALLOC;
|
|
if (wordsplit (pattern, &ws,
|
|
WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_SQUEEZE_DELIMS)
|
|
!= WRDSE_OK)
|
|
return;
|
|
for (idx_t i = 0; i < ws.ws_wordc; i++)
|
|
add_exclude (ex, ws.ws_wordv[i], options);
|
|
wordsplit_free (&ws);
|
|
}
|
|
|
|
static void
|
|
git_addfn (struct exclude *ex, char const *pattern, int options,
|
|
void *UNNAMED (data))
|
|
{
|
|
while (c_isspace (*pattern))
|
|
++pattern;
|
|
if (*pattern == 0 || *pattern == '#')
|
|
return;
|
|
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))
|
|
{
|
|
while (c_isspace (*pattern))
|
|
++pattern;
|
|
if (*pattern == 0 || *pattern == '#')
|
|
return;
|
|
if (*pattern == '!')
|
|
{
|
|
if (*++pattern == '!')
|
|
++pattern;
|
|
else
|
|
options |= EXCLUDE_INCLUDE;
|
|
}
|
|
/* FIXME: According to the docs, globbing patterns are rsync-style,
|
|
and regexps are perl-style. */
|
|
if (strncmp (pattern, "RE:", 3) == 0)
|
|
{
|
|
pattern += 3;
|
|
options &= ~EXCLUDE_WILDCARDS;
|
|
options |= EXCLUDE_REGEX;
|
|
}
|
|
add_exclude (ex, pattern, options);
|
|
}
|
|
|
|
static void *
|
|
hg_initfn (void *data)
|
|
{
|
|
static int hg_options;
|
|
int *hgopt = data ? data : &hg_options;
|
|
*hgopt = EXCLUDE_REGEX;
|
|
return hgopt;
|
|
}
|
|
|
|
static void
|
|
hg_addfn (struct exclude *ex, char const *pattern, int options, void *data)
|
|
{
|
|
int *hgopt = data;
|
|
|
|
while (c_isspace (*pattern))
|
|
++pattern;
|
|
if (*pattern == 0 || *pattern == '#')
|
|
return;
|
|
if (strncmp (pattern, "syntax:", 7) == 0)
|
|
{
|
|
for (pattern += 7; c_isspace (*pattern); ++pattern)
|
|
;
|
|
if (streq (pattern, "regexp"))
|
|
/* FIXME: Regexps must be perl-style */
|
|
*hgopt = EXCLUDE_REGEX;
|
|
else if (streq (pattern, "glob"))
|
|
*hgopt = EXCLUDE_WILDCARDS;
|
|
/* Ignore unknown syntax */
|
|
return;
|
|
}
|
|
|
|
idx_t len = strlen (pattern);
|
|
if (pattern[len-1] == '/')
|
|
{
|
|
char *p;
|
|
|
|
--len;
|
|
p = xmalloc (len+1);
|
|
memcpy (p, pattern, len);
|
|
p[len] = 0;
|
|
pattern = p;
|
|
exclude_add_pattern_buffer (ex, p);
|
|
options |= FNM_LEADING_DIR|EXCLUDE_ALLOC;
|
|
}
|
|
|
|
add_exclude (ex, pattern,
|
|
((*hgopt == EXCLUDE_REGEX)
|
|
? (options & ~EXCLUDE_WILDCARDS)
|
|
: (options & ~EXCLUDE_REGEX)) | *hgopt);
|
|
}
|
|
|
|
static struct vcs_ignore_file vcs_ignore_files[] = {
|
|
{
|
|
.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
|
|
;
|
|
for (p = vcs_ignore_files; p->filename; p++)
|
|
if (streq (p->filename, name))
|
|
break;
|
|
|
|
return p;
|
|
}
|
|
|
|
void
|
|
exclude_vcs_ignores (void)
|
|
{
|
|
struct vcs_ignore_file *p;
|
|
|
|
for (p = vcs_ignore_files; p->filename; p++)
|
|
excfile_add (p->filename, p->flags);
|
|
}
|