Browse Source

Support exclusion patterns from various VCS ignore lists.

* src/Makefile.am (tar_SOURCES): Add exclist.c
* src/common.h (EXCL_DEFAULT, EXCL_RECURSIVE)
(EXCL_NON_RECURSIVE): New flags.
(excfile_add, info_attach_exclist)
(info_cleanup_exclist,info_free_exclist)
(exclude_vcs_ignores): New prototypes.
* src/create.c (dump_dir0): Call info_attach_exclist.
* src/exclist.c: New file.
* src/incremen.c (scan_directory): Call info_attach_exclist.
* src/names.c (excluded_name): Moved to exclist.c. Change signature.
All uses updated.
* src/tar.c: New options: --exclude-ignore, --exclude-ignore-recursive
and --exclude-vcs-ignores.
(tar_stat_destroy): Free exclist.
* src/tar.h (tar_stat_info): New member exclude_list.

* NEWS: Document new exclusion options.
* doc/tar.texi: Likewise.
* doc/tar.1: Likewise.
Sergey Poznyakoff 11 years ago
parent
commit
93906c238d
13 changed files with 544 additions and 20 deletions
  1. 15 1
      NEWS
  2. 18 1
      doc/tar.1
  3. 95 1
      doc/tar.texi
  4. 1 0
      src/Makefile.am
  5. 12 2
      src/common.h
  6. 8 6
      src/create.c
  7. 361 0
      src/exclist.c
  8. 3 1
      src/incremen.c
  9. 2 1
      src/list.c
  10. 0 6
      src/names.c
  11. 25 0
      src/tar.c
  12. 3 0
      src/tar.h
  13. 1 1
      src/update.c

+ 15 - 1
NEWS

@@ -1,4 +1,4 @@
-GNU tar NEWS - User visible changes. 2014-02-14
+GNU tar NEWS - User visible changes. 2014-02-21
 Please send GNU tar bug reports to <bug-tar@gnu.org>
 Please send GNU tar bug reports to <bug-tar@gnu.org>
 
 
 
 
@@ -48,6 +48,20 @@ is uniform and reproducible.  Using --sort=inode reduces the number
 of disk seeks made when creating the archive and thus can considerably
 of disk seeks made when creating the archive and thus can considerably
 speed up archivation.
 speed up archivation.
 
 
+* New exclusion options
+
+  --exclude-ignore=FILE   Before dumping a directory check if it
+                          contains FILE, and if so read exclude
+                          patterns for this directory from FILE.
+  --exclude-ignore-recursive=FILE
+                          Same as above, but the exclusion patterns
+                          read from FILE remain in effect for any
+			  subdirectory, recursively.
+  --exclude-vcs-ignores   Read exclude tags from VCS ignore files,
+                          where such files exist.  Supported VCS's
+                          are: CVS, Git, Bazaar, Mercurial.
+			  
+
 * Manpages
 * Manpages
 
 
 This release includes official tar(1) and rmt(8) manpages.
 This release includes official tar(1) and rmt(8) manpages.

+ 18 - 1
doc/tar.1

@@ -13,7 +13,7 @@
 .\"
 .\"
 .\" You should have received a copy of the GNU General Public License
 .\" You should have received a copy of the GNU General Public License
 .\" along with this program.  If not, see <http://www.gnu.org/licenses/>.
 .\" along with this program.  If not, see <http://www.gnu.org/licenses/>.
-.TH TAR 1 "February 14, 2014" "TAR" "GNU TAR Manual"
+.TH TAR 1 "February 22, 2014" "TAR" "GNU TAR Manual"
 .SH NAME
 .SH NAME
 tar \- an archiving utility
 tar \- an archiving utility
 .SH SYNOPSIS
 .SH SYNOPSIS
@@ -788,6 +788,15 @@ Exclude directories containing file \fBCACHEDIR.TAG\fR and the file itself.
 \fB\-\-exclude\-caches\-under\fR
 \fB\-\-exclude\-caches\-under\fR
 Exclude everything under directories containing \fBCACHEDIR.TAG\fR
 Exclude everything under directories containing \fBCACHEDIR.TAG\fR
 .TP
 .TP
+\fB\-\-exclude\-ignore=\fIFILE\fR
+Before dumping a directory, see if it contains \fIFILE\fR.
+If so, read exclusion patterns from this file.  The patterns affect
+only the directory itself.
+.TP
+\fB\-\-exclude\-ignore\-recursive=\fIFILE\fR
+Same as \fB\-\-exclude\-ignore\fR, except that patterns from
+\fIFILE\fR affect both the directory and all its subdirectories.
+.TP
 \fB\-\-exclude\-tag\fR=\fIFILE\fR
 \fB\-\-exclude\-tag\fR=\fIFILE\fR
 Exclude contents of directories containing \fIFILE\fR, except for
 Exclude contents of directories containing \fIFILE\fR, except for
 \fIFILE\fR itself.
 \fIFILE\fR itself.
@@ -801,6 +810,14 @@ Exclude everything under directories containing \fIFILE\fR.
 \fB\-\-exclude\-vcs\fR
 \fB\-\-exclude\-vcs\fR
 Exclude version control system directories.
 Exclude version control system directories.
 .TP
 .TP
+\fB\-\-exclude\-vcs\-ignores\fR
+Exclude files that match patterns read from VCS-specific ignore
+files.  Supported files are:
+.BR .cvsignore ,
+.BR .gitignore ,
+.BR .bzrignore ", and"
+.BR .hgignore .
+.TP
 \fB\-h\fR, \fB\-\-dereference\fR
 \fB\-h\fR, \fB\-\-dereference\fR
 Follow symlinks; archive and dump the files they point to.
 Follow symlinks; archive and dump the files they point to.
 .TP
 .TP

+ 95 - 1
doc/tar.texi

@@ -2608,6 +2608,19 @@ tag file, but still dump the directory node itself.
 Exclude from dump any directory containing a valid cache directory
 Exclude from dump any directory containing a valid cache directory
 tag file.  @xref{exclude}.
 tag file.  @xref{exclude}.
 
 
+@opsummary{exclude-ignore}
+@item --exclude-ignore=@var{file}
+Before dumping a directory, @command{tar} checks if it contains
+@var{file}.  If so, exclusion patterns are read from this file.
+The patterns affect only the directory itself.  @xref{exclude}.
+
+@opsummary{exclude-ignore-recursive}
+@item --exclude-ignore-recursive=@var{file}
+Before dumping a directory, @command{tar} checks if it contains
+@var{file}.  If so, exclusion patterns are read from this file.
+The patterns affect the directory and all itssubdirectories.
+@xref{exclude}.
+
 @opsummary{exclude-tag}
 @opsummary{exclude-tag}
 @item --exclude-tag=@var{file}
 @item --exclude-tag=@var{file}
 
 
@@ -2633,7 +2646,16 @@ Exclude from dump any directory containing file named @var{file}.
 Exclude from dump directories and files, that are internal for some
 Exclude from dump directories and files, that are internal for some
 widely used version control systems.
 widely used version control systems.
 
 
-@xref{exclude,,exclude-vcs}.
+@xref{exclude-vcs}.
+
+@opsummary{exclude-vcs-ignores}
+@item --exclude-vcs-ignores
+Exclude files that match patterns read from VCS-specific ignore
+files.  Supported files are: @file{.cvsignore}, @file{.gitignore},
+@file{.bzrignore}, and @file{.hgignore}.  The semantics of each file
+is the same as for the corresponding VCS, e.g. patterns read from
+@file{.gitignore} affect the directory and all its subdirectories.
+@xref{exclude-vcs-ignores}.
 
 
 @opsummary{file}
 @opsummary{file}
 @item --file=@var{archive}
 @item --file=@var{archive}
@@ -7381,6 +7403,77 @@ which is difficult to catch using text editors.
 
 
 However, empty lines are OK.
 However, empty lines are OK.
 
 
+@cindex VCS, excluding patterns from ignore files
+@cindex VCS, ignore files
+@cindex CVS, ignore files
+@cindex Git, ignore files
+@cindex Bazaar, ignore files
+@cindex Mercurial, ignore files
+When archiving directories that are under some version control system (VCS), 
+it is often convenient to read exclusion patterns from this VCS'
+ignore files (e.g. @file{.cvsignore}, @file{.gitignore}, etc.)  The
+following options provide such possibilty:
+
+@table @option
+@anchor{exclude-vcs-ignores}
+@opindex exclude-vcs-ignores
+@item --exclude-vcs-ignores
+Before archiving a directory, see if it contains any of the following
+files: @file{cvsignore}, @file{.gitignore}, @file{.bzrignore}, or
+@file{.hgignore}.  If so, read ignore patterns from these files.
+
+The patterns are treated much as the corresponding VCS would treat
+them, i.e.:
+
+@table @file
+@findex .cvsignore
+@item .cvsignore
+Contains shell-style globbing patterns that apply only to the
+directory where this file resides.  No comments are allowed in the
+file.  Empty lines are ignored.
+
+@findex .gitignore
+@item .gitignore
+Contains shell-style globbing patterns.  Applies to the directory
+where @file{.gitfile} is located and all its subdirectories.
+
+Any line beginning with a @samp{#} is a comment.  Backslash escapes
+the comment character.
+
+@findex .bzrignore
+@item .bzrignore
+Contains shell globbing-patterns and regular expressions (if prefixed
+with @samp{RE:}@footnote{According to the Bazaar docs,
+globbing-patterns are Korn-shell style and regular expressions are
+perl-style.  As of @GNUTAR{} version @value{VERSION}, these are
+treated as shell-style globs and posix extended regexps.  This will be
+fixed in future releases.}.  Patterns affect the directory and all its
+subdirectories.
+
+Any line beginning with a @samp{#} is a comment.
+
+@findex .hgignore
+@item .hgignore
+Contains posix regular expressions@footnote{Support for perl-style
+regexps will appear in future releases.}.  The line @samp{syntax:
+glob} switches to shell globbing patterns.  The line @samp{syntax:
+regexp} switches back.  Comments begin with a @samp{#}.  Patterns
+affect the directory and all its subdirectories.
+@end table
+
+@opindex exclude-ignore
+@item --exclude-ignore=@var{file}
+Before dumping a directory, @command{tar} checks if it contains
+@var{file}.  If so, exclusion patterns are read from this file.
+The patterns affect only the directory itself.
+
+@opindex exclude-ignore-recursive
+@item --exclude-ignore-recursive=@var{file}
+Same as @option{--exclude-ignore}, except that the patterns read
+affect both the directory where @var{file} resides and all its
+subdirectories.
+@end table
+
 @table @option
 @table @option
 @cindex version control system, excluding files
 @cindex version control system, excluding files
 @cindex VCS, excluding files
 @cindex VCS, excluding files
@@ -7393,6 +7486,7 @@ However, empty lines are OK.
 @cindex Arch, excluding files
 @cindex Arch, excluding files
 @cindex Mercurial, excluding files
 @cindex Mercurial, excluding files
 @cindex Darcs, excluding files
 @cindex Darcs, excluding files
+@anchor{exclude-vcs}
 @opindex exclude-vcs
 @opindex exclude-vcs
 @item --exclude-vcs
 @item --exclude-vcs
 Exclude files and directories used by following version control
 Exclude files and directories used by following version control

+ 1 - 0
src/Makefile.am

@@ -28,6 +28,7 @@ tar_SOURCES = \
  create.c\
  create.c\
  delete.c\
  delete.c\
  exit.c\
  exit.c\
+ exclist.c\
  extract.c\
  extract.c\
  xheader.c\
  xheader.c\
  incremen.c\
  incremen.c\

+ 12 - 2
src/common.h

@@ -743,8 +743,6 @@ char *new_name (const char *dir_name, const char *name);
 size_t stripped_prefix_len (char const *file_name, size_t num);
 size_t stripped_prefix_len (char const *file_name, size_t num);
 bool all_names_found (struct tar_stat_info *st);
 bool all_names_found (struct tar_stat_info *st);
 
 
-bool excluded_name (char const *name);
-
 void add_avoided_name (char const *name);
 void add_avoided_name (char const *name);
 bool is_avoided_name (char const *name);
 bool is_avoided_name (char const *name);
 
 
@@ -921,4 +919,16 @@ void finish_deferred_unlinks (void);
 /* Module exit.c */
 /* Module exit.c */
 extern void (*fatal_exit_hook) (void);
 extern void (*fatal_exit_hook) (void);
 
 
+/* Module exclist.c */
+#define EXCL_DEFAULT       0x00
+#define EXCL_RECURSIVE     0x01
+#define EXCL_NON_RECURSIVE 0x02
+
+void excfile_add (const char *name, int flags);
+void info_attach_exclist (struct tar_stat_info *dir);
+void info_cleanup_exclist (struct tar_stat_info *dir);
+void info_free_exclist (struct tar_stat_info *dir);
+bool excluded_name (char const *name, struct tar_stat_info *st);
+void exclude_vcs_ignores (void);
+
 _GL_INLINE_HEADER_END
 _GL_INLINE_HEADER_END

+ 8 - 6
src/create.c

@@ -1113,6 +1113,8 @@ dump_dir0 (struct tar_stat_info *st, char const *directory)
   if (!blk)
   if (!blk)
     return;
     return;
 
 
+  info_attach_exclist (st);
+  
   if (incremental_option && archive_format != POSIX_FORMAT)
   if (incremental_option && archive_format != POSIX_FORMAT)
     blk->header.typeflag = GNUTYPE_DUMPDIR;
     blk->header.typeflag = GNUTYPE_DUMPDIR;
   else /* if (standard_option) */
   else /* if (standard_option) */
@@ -1196,7 +1198,7 @@ dump_dir0 (struct tar_stat_info *st, char const *directory)
 	    char const *entry;
 	    char const *entry;
 	    size_t entry_len;
 	    size_t entry_len;
 	    size_t name_len;
 	    size_t name_len;
-
+	    
 	    name_buf = xstrdup (st->orig_file_name);
 	    name_buf = xstrdup (st->orig_file_name);
 	    name_size = name_len = strlen (name_buf);
 	    name_size = name_len = strlen (name_buf);
 
 
@@ -1210,7 +1212,7 @@ dump_dir0 (struct tar_stat_info *st, char const *directory)
 		    name_buf = xrealloc (name_buf, name_size + 1);
 		    name_buf = xrealloc (name_buf, name_size + 1);
 		  }
 		  }
 		strcpy (name_buf + name_len, entry);
 		strcpy (name_buf + name_len, entry);
-		if (!excluded_name (name_buf))
+		if (!excluded_name (name_buf, st))
 		  dump_file (st, entry, name_buf);
 		  dump_file (st, entry, name_buf);
 	      }
 	      }
 
 
@@ -1339,12 +1341,12 @@ create_archive (void)
       collect_and_sort_names ();
       collect_and_sort_names ();
 
 
       while ((p = name_from_list ()) != NULL)
       while ((p = name_from_list ()) != NULL)
-	if (!excluded_name (p->name))
+	if (!excluded_name (p->name, NULL))
 	  dump_file (0, p->name, p->name);
 	  dump_file (0, p->name, p->name);
 
 
       blank_name_list ();
       blank_name_list ();
       while ((p = name_from_list ()) != NULL)
       while ((p = name_from_list ()) != NULL)
-	if (!excluded_name (p->name))
+	if (!excluded_name (p->name, NULL))
 	  {
 	  {
 	    struct tar_stat_info st;
 	    struct tar_stat_info st;
 	    size_t plen = strlen (p->name);
 	    size_t plen = strlen (p->name);
@@ -1358,7 +1360,7 @@ create_archive (void)
 	    if (! ISSLASH (buffer[plen - 1]))
 	    if (! ISSLASH (buffer[plen - 1]))
 	      buffer[plen++] = DIRECTORY_SEPARATOR;
 	      buffer[plen++] = DIRECTORY_SEPARATOR;
 	    tar_stat_init (&st);
 	    tar_stat_init (&st);
-	    q = directory_contents (gnu_list_name->directory);
+	    q = directory_contents (p->directory);
 	    if (q)
 	    if (q)
 	      while (*q)
 	      while (*q)
 		{
 		{
@@ -1401,7 +1403,7 @@ create_archive (void)
     {
     {
       const char *name;
       const char *name;
       while ((name = name_next (1)) != NULL)
       while ((name = name_next (1)) != NULL)
-	if (!excluded_name (name))
+	if (!excluded_name (name, NULL))
 	  dump_file (0, name, name);
 	  dump_file (0, name, name);
     }
     }
 
 

+ 361 - 0
src/exclist.c

@@ -0,0 +1,361 @@
+/* Per-directory exclusion files for tar.
+
+   Copyright 2014 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 <quotearg.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 *);
+  void *data;
+};
+
+static struct vcs_ignore_file *get_vcs_ignore_file (const char *name);
+
+struct excfile
+{
+  struct excfile *next;
+  int flags;
+  char name[1];
+};
+
+struct excfile *excfile_head, *excfile_tail;
+
+void
+excfile_add (const char *name, int flags)
+{
+  struct excfile *p = xmalloc (sizeof (*p) + strlen (name));
+  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;
+  struct exclude *excluded;
+};
+
+void
+info_attach_exclist (struct tar_stat_info *dir)
+{
+  struct excfile *file;
+  struct exclist *head = NULL, *tail = NULL, *ent;
+  struct vcs_ignore_file *vcsfile;
+    
+  if (dir->exclude_list)
+    return;
+  for (file = excfile_head; file; file = file->next)
+    {
+      if (faccessat (dir ? dir->fd : chdir_fd, file->name, F_OK, 0) == 0)
+	{
+	  FILE *fp;
+	  struct exclude *ex = NULL;
+	  int fd = subfile_open (dir, file->name, O_RDONLY);
+	  if (fd == -1)
+	    {
+	      open_error (file->name);
+	      continue;
+	    }
+	  fp = fdopen (fd, "r");
+	  if (!fp)
+	    {
+	      ERROR ((0, errno, _("%s: fdopen failed"), file->name));
+	      close (fd);
+	      continue;
+	    }
+
+	  if (!ex)
+	    ex = new_exclude ();
+
+	  vcsfile = get_vcs_ignore_file (file->name);
+
+	  if (vcsfile->initfn)
+	    vcsfile->data = vcsfile->initfn (vcsfile->data);
+	  
+	  if (add_exclude_fp (vcsfile->addfn, ex, fp,
+			      EXCLUDE_WILDCARDS|EXCLUDE_ANCHORED, '\n',
+			      vcsfile->data))
+	    {
+	      int e = errno;
+	      FATAL_ERROR ((0, e, "%s", quotearg_colon (file->name)));
+	    }
+	  fclose (fp);
+	  
+	  ent = xmalloc (sizeof (*ent));
+	  ent->excluded = ex;
+	  ent->flags = file->flags == EXCL_DEFAULT
+	               ? file->flags : vcsfile->flags;
+	  ent->prev = tail;
+	  ent->next = NULL;
+
+	  if (tail)
+	    tail->next = ent;
+	  else
+	    head = ent;
+	  tail = ent;
+	}
+    }
+  dir->exclude_list = head;
+}
+
+void
+info_cleanup_exclist (struct tar_stat_info *dir)
+{
+  struct exclist *ep = dir->exclude_list;
+
+  while (ep)
+    {
+      struct exclist *next = ep->next;
+      
+      if (ep->flags & EXCL_NON_RECURSIVE)
+	{
+	  
+	  /* Remove the entry */
+	  if (ep->prev)
+	    ep->prev->next = ep->next;
+	  else
+	    dir->exclude_list = ep->next;
+
+	  if (ep->next)
+	    ep->next->prev = ep->prev;
+
+	  free_exclude (ep->excluded);
+	  free (ep);
+	}
+      ep = next;
+    }
+}
+
+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);
+      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;
+  const char *rname = NULL;
+  char *bname = NULL;
+  bool 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 = false; st && !result; st = st->parent, nr = EXCL_NON_RECURSIVE)
+    {
+      for (ep = st->exclude_list; ep; ep = ep->next)
+	{
+	  if (ep->flags & nr)
+	    continue;
+	  if ((result = excluded_file_name (ep->excluded, name)))
+	    break;
+	  
+	  if (!rname)
+	    {
+	      rname = name;
+	      /* Skip leading ./ */
+	      while (*rname == '.' && ISSLASH (rname[1]))
+		rname += 2;
+	    }
+	  if ((result = excluded_file_name (ep->excluded, rname)))
+	    break;
+
+	  if (!bname)
+	    bname = base_name (name);
+	  if ((result = excluded_file_name (ep->excluded, bname)))
+	    break;
+	}
+    }
+
+  free (bname);
+
+  return result;
+}
+
+static void
+cvs_addfn (struct exclude *ex, char const *pattern, int options, void *data)
+{
+  struct wordsplit ws;
+  size_t i;
+    
+  if (wordsplit (pattern, &ws, 
+		 WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_SQUEEZE_DELIMS))
+    return;
+  for (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 *data)
+{
+  while (isspace (*pattern))
+    ++pattern;
+  if (*pattern == 0 || *pattern == '#')
+    return;
+  if (*pattern == '\\' && pattern[1] == '#')
+    ++pattern;
+  add_exclude (ex, pattern, options);
+}
+
+static void
+bzr_addfn (struct exclude *ex, char const *pattern, int options, void *data)
+{
+  while (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)
+{
+  int *hgopt;
+  static int hg_options;
+  
+  if (!data)
+    hgopt = &hg_options;
+
+  *hgopt = EXCLUDE_REGEX;
+  return hgopt;
+}
+  
+static void
+hg_addfn (struct exclude *ex, char const *pattern, int options, void *data)
+{
+  int *hgopt = data;
+  size_t len;
+  
+  while (isspace (*pattern))
+    ++pattern;
+  if (*pattern == 0 || *pattern == '#')
+    return;
+  if (strncmp (pattern, "syntax:", 7) == 0)
+    {
+      for (pattern += 7; isspace (*pattern); ++pattern)
+	;
+      if (strcmp (pattern, "regexp") == 0)
+	/* FIXME: Regexps must be perl-style */
+	*hgopt = EXCLUDE_REGEX;
+      else if (strcmp (pattern, "glob") == 0)
+	*hgopt = EXCLUDE_WILDCARDS;
+      /* Ignore unknown syntax */
+      return;
+    }
+
+  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);
+}
+
+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 }
+};
+  
+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 (strcmp (p->filename, name) == 0)
+      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, EXCL_DEFAULT);
+}

+ 3 - 1
src/incremen.c

@@ -734,6 +734,8 @@ scan_directory (struct tar_stat_info *st)
   if (! dirp)
   if (! dirp)
     savedir_error (dir);
     savedir_error (dir);
 
 
+  info_attach_exclist (st);
+  
   tmp = xstrdup (dir);
   tmp = xstrdup (dir);
   zap_slashes (tmp);
   zap_slashes (tmp);
 
 
@@ -762,7 +764,7 @@ scan_directory (struct tar_stat_info *st)
 
 
 	      if (*entry == 'I') /* Ignored entry */
 	      if (*entry == 'I') /* Ignored entry */
 		*entry = 'N';
 		*entry = 'N';
-	      else if (excluded_name (full_name))
+	      else if (excluded_name (full_name, st))
 		*entry = 'N';
 		*entry = 'N';
 	      else
 	      else
 		{
 		{

+ 2 - 1
src/list.c

@@ -203,7 +203,8 @@ read_and (void (*do_something) (void))
 		      mtime.tv_nsec = 0,
 		      mtime.tv_nsec = 0,
 		      current_stat_info.mtime = mtime,
 		      current_stat_info.mtime = mtime,
 		      OLDER_TAR_STAT_TIME (current_stat_info, m)))
 		      OLDER_TAR_STAT_TIME (current_stat_info, m)))
-	      || excluded_name (current_stat_info.file_name))
+	      || excluded_name (current_stat_info.file_name,
+				current_stat_info.parent))
 	    {
 	    {
 	      switch (current_header->header.typeflag)
 	      switch (current_header->header.typeflag)
 		{
 		{

+ 0 - 6
src/names.c

@@ -1373,12 +1373,6 @@ new_name (const char *file_name, const char *name)
   return buffer;
   return buffer;
 }
 }
 
 
-/* Return nonzero if file NAME is excluded.  */
-bool
-excluded_name (char const *name)
-{
-  return excluded_file_name (excluded, name + FILE_SYSTEM_PREFIX_LEN (name));
-}
 
 
 
 
 /* Return the size of the prefix of FILE_NAME that is removed after
 /* Return the size of the prefix of FILE_NAME that is removed after

+ 25 - 0
src/tar.c

@@ -284,10 +284,13 @@ enum
   EXCLUDE_CACHES_UNDER_OPTION,
   EXCLUDE_CACHES_UNDER_OPTION,
   EXCLUDE_CACHES_ALL_OPTION,
   EXCLUDE_CACHES_ALL_OPTION,
   EXCLUDE_OPTION,
   EXCLUDE_OPTION,
+  EXCLUDE_IGNORE_OPTION,
+  EXCLUDE_IGNORE_RECURSIVE_OPTION,
   EXCLUDE_TAG_OPTION,
   EXCLUDE_TAG_OPTION,
   EXCLUDE_TAG_UNDER_OPTION,
   EXCLUDE_TAG_UNDER_OPTION,
   EXCLUDE_TAG_ALL_OPTION,
   EXCLUDE_TAG_ALL_OPTION,
   EXCLUDE_VCS_OPTION,
   EXCLUDE_VCS_OPTION,
+  EXCLUDE_VCS_IGNORES_OPTION,
   FORCE_LOCAL_OPTION,
   FORCE_LOCAL_OPTION,
   FULL_TIME_OPTION,
   FULL_TIME_OPTION,
   GROUP_OPTION,
   GROUP_OPTION,
@@ -732,12 +735,20 @@ static struct argp_option options[] = {
   {"exclude-tag", EXCLUDE_TAG_OPTION, N_("FILE"), 0,
   {"exclude-tag", EXCLUDE_TAG_OPTION, N_("FILE"), 0,
    N_("exclude contents of directories containing FILE, except"
    N_("exclude contents of directories containing FILE, except"
       " for FILE itself"), GRID+1 },
       " for FILE itself"), GRID+1 },
+  {"exclude-ignore", EXCLUDE_IGNORE_OPTION, N_("FILE"), 0,
+    N_("read exclude patterns for each directory from FILE, if it exists"),
+   GRID+1 }, 
+  {"exclude-ignore-recursive", EXCLUDE_IGNORE_RECURSIVE_OPTION, N_("FILE"), 0,
+    N_("read exclude patterns for each directory and its subdirectories "
+       "from FILE, if it exists"), GRID+1 },
   {"exclude-tag-under", EXCLUDE_TAG_UNDER_OPTION, N_("FILE"), 0,
   {"exclude-tag-under", EXCLUDE_TAG_UNDER_OPTION, N_("FILE"), 0,
    N_("exclude everything under directories containing FILE"), GRID+1 },
    N_("exclude everything under directories containing FILE"), GRID+1 },
   {"exclude-tag-all", EXCLUDE_TAG_ALL_OPTION, N_("FILE"), 0,
   {"exclude-tag-all", EXCLUDE_TAG_ALL_OPTION, N_("FILE"), 0,
    N_("exclude directories containing FILE"), GRID+1 },
    N_("exclude directories containing FILE"), GRID+1 },
   {"exclude-vcs", EXCLUDE_VCS_OPTION, NULL, 0,
   {"exclude-vcs", EXCLUDE_VCS_OPTION, NULL, 0,
    N_("exclude version control system directories"), GRID+1 },
    N_("exclude version control system directories"), GRID+1 },
+  {"exclude-vcs-ignores", EXCLUDE_VCS_IGNORES_OPTION, NULL, 0,
+   N_("read exclude patterns from the VCS ignore files"), GRID+1 },
   {"exclude-backups", EXCLUDE_BACKUPS_OPTION, NULL, 0,
   {"exclude-backups", EXCLUDE_BACKUPS_OPTION, NULL, 0,
    N_("exclude backup and lock files"), GRID+1 },
    N_("exclude backup and lock files"), GRID+1 },
   {"no-recursion", NO_RECURSION_OPTION, 0, 0,
   {"no-recursion", NO_RECURSION_OPTION, 0, 0,
@@ -1776,6 +1787,14 @@ parse_opt (int key, char *arg, struct argp_state *state)
 			 cachedir_file_p);
 			 cachedir_file_p);
       break;
       break;
 
 
+    case EXCLUDE_IGNORE_OPTION:
+      excfile_add (arg, EXCL_NON_RECURSIVE);
+      break;
+
+    case EXCLUDE_IGNORE_RECURSIVE_OPTION:
+      excfile_add (arg, EXCL_RECURSIVE);
+      break;
+      
     case EXCLUDE_TAG_OPTION:
     case EXCLUDE_TAG_OPTION:
       add_exclusion_tag (arg, exclusion_tag_contents, NULL);
       add_exclusion_tag (arg, exclusion_tag_contents, NULL);
       break;
       break;
@@ -1792,6 +1811,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
       add_exclude_array (vcs_file_table, 0);
       add_exclude_array (vcs_file_table, 0);
       break;
       break;
 
 
+    case EXCLUDE_VCS_IGNORES_OPTION:
+      exclude_vcs_ignores ();
+      break;
+      
     case FORCE_LOCAL_OPTION:
     case FORCE_LOCAL_OPTION:
       force_local_option = true;
       force_local_option = true;
       break;
       break;
@@ -2304,6 +2327,7 @@ decode_options (int argc, char **argv)
   blocking_factor = DEFAULT_BLOCKING;
   blocking_factor = DEFAULT_BLOCKING;
   record_size = DEFAULT_BLOCKING * BLOCKSIZE;
   record_size = DEFAULT_BLOCKING * BLOCKSIZE;
   excluded = new_exclude ();
   excluded = new_exclude ();
+  
   newer_mtime_option.tv_sec = TYPE_MINIMUM (time_t);
   newer_mtime_option.tv_sec = TYPE_MINIMUM (time_t);
   newer_mtime_option.tv_nsec = -1;
   newer_mtime_option.tv_nsec = -1;
   recursion_option = FNM_LEADING_DIR;
   recursion_option = FNM_LEADING_DIR;
@@ -2849,6 +2873,7 @@ tar_stat_destroy (struct tar_stat_info *st)
   free (st->sparse_map);
   free (st->sparse_map);
   free (st->dumpdir);
   free (st->dumpdir);
   xheader_destroy (&st->xhdr);
   xheader_destroy (&st->xhdr);
+  info_free_exclist (st);
   memset (st, 0, sizeof (*st));
   memset (st, 0, sizeof (*st));
 }
 }
 
 

+ 3 - 0
src/tar.h

@@ -358,6 +358,9 @@ struct tar_stat_info
      It is negative if it could not be reopened after it was closed.
      It is negative if it could not be reopened after it was closed.
      Negate it to find out what errno was when the reopen failed.  */
      Negate it to find out what errno was when the reopen failed.  */
   int fd;
   int fd;
+
+  /* Exclusion list */
+  struct exclist *exclude_list;
 };
 };
 
 
 union block
 union block

+ 1 - 1
src/update.c

@@ -216,7 +216,7 @@ update_archive (void)
     while ((p = name_from_list ()) != NULL)
     while ((p = name_from_list ()) != NULL)
       {
       {
 	char *file_name = p->name;
 	char *file_name = p->name;
-	if (excluded_name (file_name))
+	if (excluded_name (file_name, NULL))
 	  continue;
 	  continue;
 	if (interactive_option && !confirm ("add", file_name))
 	if (interactive_option && !confirm ("add", file_name))
 	  continue;
 	  continue;