rdiff-backup-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Rdiff-backup-commits] rdiff-backup ./CHANGELOG rdiff_backup/selection..


From: Ben Escoto
Subject: [Rdiff-backup-commits] rdiff-backup ./CHANGELOG rdiff_backup/selection... [r1-0]
Date: Tue, 10 Jan 2006 06:15:42 +0000

CVSROOT:        /sources/rdiff-backup
Module name:    rdiff-backup
Branch:         r1-0
Changes by:     Ben Escoto <address@hidden>     06/01/10 06:15:42

Modified files:
        .              : CHANGELOG 
        rdiff_backup   : selection.py 
        testing        : selectiontest.py 

Log message:
        Fix for Toni Price's empty dir --include **XXX selection bug

CVSWeb URLs:
http://cvs.savannah.gnu.org/viewcvs/rdiff-backup/rdiff-backup/CHANGELOG.diff?only_with_tag=r1-0&tr1=1.147.2.12&tr2=1.147.2.13&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/rdiff-backup/rdiff-backup/rdiff_backup/selection.py.diff?only_with_tag=r1-0&tr1=1.41&tr2=1.41.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/rdiff-backup/rdiff-backup/testing/selectiontest.py.diff?only_with_tag=r1-0&tr1=1.15&tr2=1.15.2.1&r1=text&r2=text

Patches:
Index: rdiff-backup/CHANGELOG
diff -u rdiff-backup/CHANGELOG:1.147.2.12 rdiff-backup/CHANGELOG:1.147.2.13
--- rdiff-backup/CHANGELOG:1.147.2.12   Thu Dec 22 03:59:02 2005
+++ rdiff-backup/CHANGELOG      Tue Jan 10 06:15:41 2006
@@ -7,6 +7,10 @@
 Another fix for long-filenames crash, this time when a long-named
 directory with files in it gets deleted
 
+Selection fix: empty directories could sometimes be improperly
+excluded if certain include expressions involving a non-trailing '**'
+were used.  Bug reported by Toni Price.
+
 
 New in v1.0.3 (2005/11/25)
 --------------------------
Index: rdiff-backup/rdiff_backup/selection.py
diff -u /dev/null rdiff-backup/rdiff_backup/selection.py:1.41.2.1
--- /dev/null   Tue Jan 10 06:15:42 2006
+++ rdiff-backup/rdiff_backup/selection.py      Tue Jan 10 06:15:42 2006
@@ -0,0 +1,724 @@
+# Copyright 2002 Ben Escoto
+#
+# This file is part of rdiff-backup.
+#
+# rdiff-backup is free software; you can redistribute it and/or modify
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# rdiff-backup 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 rdiff-backup; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+
+"""Iterate exactly the requested files in a directory
+
+Parses includes and excludes to yield correct files.  More
+documentation on what this code does can be found on the man page.
+
+"""
+
+from __future__ import generators
+import re
+import FilenameMapping, robust, rpath, Globals, log, rorpiter
+
+
+class SelectError(Exception):
+       """Some error dealing with the Select class"""
+       pass
+
+class FilePrefixError(SelectError):
+       """Signals that a specified file doesn't start with correct prefix"""
+       pass
+
+class GlobbingError(SelectError):
+       """Something has gone wrong when parsing a glob string"""
+       pass
+
+
+class Select:
+       """Iterate appropriate RPaths in given directory
+
+       This class acts as an iterator on account of its next() method.
+       Basically, it just goes through all the files in a directory in
+       order (depth-first) and subjects each file to a bunch of tests
+       (selection functions) in order.  The first test that includes or
+       excludes the file means that the file gets included (iterated) or
+       excluded.  The default is include, so with no tests we would just
+       iterate all the files in the directory in order.
+
+       The one complication to this is that sometimes we don't know
+       whether or not to include a directory until we examine its
+       contents.  For instance, if we want to include all the **.py
+       files.  If /home/ben/foo.py exists, we should also include /home
+       and /home/ben, but if these directories contain no **.py files,
+       they shouldn't be included.  For this reason, a test may not
+       include or exclude a directory, but merely "scan" it.  If later a
+       file in the directory gets included, so does the directory.
+
+       As mentioned above, each test takes the form of a selection
+       function.  The selection function takes an rpath, and returns:
+
+       None - means the test has nothing to say about the related file
+       0 - the file is excluded by the test
+       1 - the file is included
+       2 - the test says the file (must be directory) should be scanned
+
+       Also, a selection function f has a variable f.exclude which should
+       be true iff f could potentially exclude some file.  This is used
+       to signal an error if the last function only includes, which would
+       be redundant and presumably isn't what the user intends.
+
+       """
+       # This re should not match normal filenames, but usually just globs
+       glob_re = re.compile("(.*[*?[]|ignorecase\\:)", re.I | re.S)
+
+       def __init__(self, rootrp):
+               """Select initializer.  rpath is the root directory"""
+               assert isinstance(rootrp, rpath.RPath)
+               self.selection_functions = []
+               self.rpath = rootrp
+               self.prefix = self.rpath.path
+
+       def set_iter(self, sel_func = None):
+               """Initialize more variables, get ready to iterate
+
+               Selection function sel_func is called on each rpath and is
+               usually self.Select.  Returns self just for convenience.
+
+               """
+               if not sel_func: sel_func = self.Select
+               self.rpath.setdata() # this may have changed since Select init
+               self.iter = self.Iterate_fast(self.rpath, sel_func)
+               self.next = self.iter.next
+               self.__iter__ = lambda: self
+               return self
+
+       def Iterate_fast(self, rpath, sel_func):
+               """Like Iterate, but don't recur, saving time"""
+               def error_handler(exc, filename):
+                       log.ErrorLog.write_if_open("ListError",
+                                                                          
rpath.index + (filename,), exc)
+                       return None
+
+               def diryield(rpath):
+                       """Generate relevant files in directory rpath
+
+                       Returns (rpath, num) where num == 0 means rpath should 
be
+                       generated normally, num == 1 means the rpath is a 
directory
+                       and should be included iff something inside is included.
+
+                       """
+                       for filename in self.listdir(rpath):
+                               new_rpath = 
robust.check_common_error(error_handler,
+                                                                               
rpath.append, (filename,))
+                               if new_rpath and new_rpath.lstat():
+                                       s = sel_func(new_rpath)
+                                       if s == 1: yield (new_rpath, 0)
+                                       elif s == 2 and new_rpath.isdir(): 
yield (new_rpath, 1)
+
+               yield rpath
+               if not rpath.isdir(): return
+               diryield_stack = [diryield(rpath)]
+               delayed_rp_stack = []
+
+               while diryield_stack:
+                       try: rpath, val = diryield_stack[-1].next()
+                       except StopIteration:
+                               diryield_stack.pop()
+                               if delayed_rp_stack: delayed_rp_stack.pop()
+                               continue
+                       if val == 0:
+                               if delayed_rp_stack:
+                                       for delayed_rp in delayed_rp_stack: 
yield delayed_rp
+                                       del delayed_rp_stack[:]
+                               yield rpath
+                               if rpath.isdir(): 
diryield_stack.append(diryield(rpath))
+                       elif val == 1:
+                               delayed_rp_stack.append(rpath)
+                               diryield_stack.append(diryield(rpath))
+
+       def Iterate(self, rp, rec_func, sel_func):
+               """Return iterator yielding rpaths in rpath
+
+               rec_func is usually the same as this function and is what
+               Iterate uses to find files in subdirectories.  It is used in
+               iterate_starting_from.
+
+               sel_func is the selection function to use on the rpaths.  It
+               is usually self.Select.
+
+               """
+               s = sel_func(rp)
+               if s == 0: return
+               elif s == 1: # File is included
+                       yield rp
+                       if rp.isdir():
+                               for rp2 in self.iterate_in_dir(rp, rec_func, 
sel_func):
+                                       yield rp2
+               elif s == 2:
+                       if rp.isdir(): # Directory is merely scanned
+                               iid = self.iterate_in_dir(rp, rec_func, 
sel_func)
+                               try: first = iid.next()
+                               except StopIteration: return # no files inside; 
skip rp
+                               yield rp
+                               yield first
+                               for rp2 in iid: yield rp2
+               else: assert 0, "Invalid selection result %s" % (str(s),)
+
+       def listdir(self, dir_rp):
+               """List directory rpath with error logging"""
+               def error_handler(exc):
+                       log.ErrorLog.write_if_open("ListError", dir_rp, exc)
+                       return []
+               dir_listing = robust.check_common_error(error_handler, 
dir_rp.listdir)
+               dir_listing.sort()
+               return dir_listing
+
+       def iterate_in_dir(self, rpath, rec_func, sel_func):
+               """Iterate the rpaths in directory rpath."""
+               def error_handler(exc, filename):
+                       log.ErrorLog.write_if_open("ListError",
+                                                                          
rpath.index + (filename,), exc)
+                       return None
+
+               for filename in self.listdir(rpath):
+                       new_rp = robust.check_common_error(
+                               error_handler, rpath.append, [filename])
+                       if new_rp:
+                               for rp in rec_func(new_rp, rec_func, sel_func):
+                                       yield rp
+
+       def FilterIter(self, rorp_iter):
+               """Filter rorp_iter using Select below, removing excluded 
rorps"""
+               def getrpiter(rorp_iter):
+                       """Return rp iter by adding indicies of rorp_iter to 
self.rpath"""
+                       for rorp in rorp_iter:
+                               yield rpath.RPath(self.rpath.conn, 
self.rpath.base,
+                                                                 rorp.index, 
rorp.data)
+
+               ITR = rorpiter.IterTreeReducer(FilterIterITRB, [self])
+               for rp in rp_iter: ITR(rp.index, rp)
+               ITR.Finish()
+
+       def Select(self, rp):
+               """Run through the selection functions and return dominant val 
0/1/2"""
+               scanned = 0 # 0, by default, or 2 if prev sel func scanned rp
+               for sf in self.selection_functions:
+                       result = sf(rp)
+                       if result == 1: return 1
+                       elif result == 0: return scanned
+                       elif result == 2: scanned = 2
+               return 1
+
+       def ParseArgs(self, argtuples, filelists):
+               """Create selection functions based on list of tuples
+
+               The tuples have the form (option string, additional argument)
+               and are created when the initial commandline arguments are
+               read.  The reason for the extra level of processing is that
+               the filelists may only be openable by the main connection, but
+               the selection functions need to be on the backup reader or
+               writer side.  When the initial arguments are parsed the right
+               information is sent over the link.
+
+               """
+               filelists_index = 0
+               try:
+                       for opt, arg in argtuples:
+                               if opt == "--exclude":
+                                       
self.add_selection_func(self.glob_get_sf(arg, 0))
+                               elif opt == "--exclude-device-files":
+                                       
self.add_selection_func(self.devfiles_get_sf(0))
+                               elif opt == "--exclude-symbolic-links":
+                                       
self.add_selection_func(self.symlinks_get_sf(0))
+                               elif opt == "--exclude-sockets":
+                                       
self.add_selection_func(self.sockets_get_sf(0))
+                               elif opt == "--exclude-fifos":
+                                       
self.add_selection_func(self.fifos_get_sf(0))
+                               elif opt == "--exclude-filelist":
+                                       
self.add_selection_func(self.filelist_get_sf(
+                                               filelists[filelists_index], 0, 
arg))
+                                       filelists_index += 1
+                               elif opt == "--exclude-globbing-filelist":
+                                       map(self.add_selection_func,
+                                               self.filelist_globbing_get_sfs(
+                                                          
filelists[filelists_index], 0, arg))
+                                       filelists_index += 1
+                               elif opt == "--exclude-other-filesystems":
+                                       
self.add_selection_func(self.other_filesystems_get_sf(0))
+                               elif opt == "--exclude-regexp":
+                                       
self.add_selection_func(self.regexp_get_sf(arg, 0))
+                               elif opt == "--exclude-special-files":
+                                       
self.add_selection_func(self.special_get_sf(0))
+                               elif opt == "--include":
+                                       
self.add_selection_func(self.glob_get_sf(arg, 1))
+                               elif opt == "--include-filelist":
+                                       
self.add_selection_func(self.filelist_get_sf(
+                                               filelists[filelists_index], 1, 
arg))
+                                       filelists_index += 1
+                               elif opt == "--include-globbing-filelist":
+                                       map(self.add_selection_func,
+                                               self.filelist_globbing_get_sfs(
+                                                         
filelists[filelists_index], 1, arg))
+                                       filelists_index += 1
+                               elif opt == "--include-regexp":
+                                       
self.add_selection_func(self.regexp_get_sf(arg, 1))
+                               elif opt == "--include-special-files":
+                                       
self.add_selection_func(self.special_get_sf(1))
+                               elif opt == "--include-symbolic-links":
+                                       
self.add_selection_func(self.symlinks_get_sf(1))
+                               else: assert 0, "Bad selection option %s" % opt
+               except SelectError, e: self.parse_catch_error(e)
+               assert filelists_index == len(filelists)
+
+               self.parse_last_excludes()
+               self.parse_rbdir_exclude()
+
+       def parse_catch_error(self, exc):
+               """Deal with selection error exc"""
+               if isinstance(exc, FilePrefixError):
+                       log.Log.FatalError(
+"""Fatal Error: The file specification
+    '%s'
+cannot match any files in the base directory
+    '%s'
+Useful file specifications begin with the base directory or some
+pattern (such as '**') which matches the base directory.""" %
+                       (exc, self.prefix))
+               elif isinstance(e, GlobbingError):
+                       log.Log.FatalError("Fatal Error while processing 
expression\n"
+                                                          "%s" % exc)
+               else: raise
+
+       def parse_rbdir_exclude(self):
+               """Add exclusion of rdiff-backup-data dir to front of list"""
+               self.add_selection_func(
+                       self.glob_get_tuple_sf(("rdiff-backup-data",), 0), 1)
+
+       def parse_last_excludes(self):
+               """Exit with error if last selection function isn't an 
exclude"""
+               if (self.selection_functions and
+                       not self.selection_functions[-1].exclude):
+                       log.Log.FatalError(
+"""Last selection expression:
+    %s
+only specifies that files be included.  Because the default is to
+include all files, the expression is redundant.  Exiting because this
+probably isn't what you meant.""" %
+                       (self.selection_functions[-1].name,))
+
+       def add_selection_func(self, sel_func, add_to_start = None):
+               """Add another selection function at the end or beginning"""
+               if add_to_start: self.selection_functions.insert(0, sel_func)
+               else: self.selection_functions.append(sel_func)
+
+       def filelist_get_sf(self, filelist_fp, inc_default, filelist_name):
+               """Return selection function by reading list of files
+
+               The format of the filelist is documented in the man page.
+               filelist_fp should be an (open) file object.
+               inc_default should be true if this is an include list,
+               false for an exclude list.
+               filelist_name is just a string used for logging.
+
+               """
+               log.Log("Reading filelist %s" % filelist_name, 4)
+               tuple_list, something_excluded = \
+                                       self.filelist_read(filelist_fp, 
inc_default, filelist_name)
+               log.Log("Sorting filelist %s" % filelist_name, 4)
+               tuple_list.sort()
+               i = [0] # We have to put index in list because of stupid 
scoping rules
+
+               def selection_function(rp):
+                       while 1:
+                               if i[0] >= len(tuple_list): return None
+                               include, move_on = \
+                                                self.filelist_pair_match(rp, 
tuple_list[i[0]])
+                               if move_on:
+                                       i[0] += 1
+                                       if include is None: continue # later 
line may match
+                               return include
+
+               selection_function.exclude = something_excluded or inc_default 
== 0
+               selection_function.name = "Filelist: " + filelist_name
+               return selection_function
+
+       def filelist_read(self, filelist_fp, include, filelist_name):
+               """Read filelist from fp, return (tuplelist, 
something_excluded)"""
+               prefix_warnings = [0]
+               def incr_warnings(exc):
+                       """Warn if prefix is incorrect"""
+                       prefix_warnings[0] += 1
+                       if prefix_warnings[0] < 6:
+                               log.Log("Warning: file specification '%s' in 
filelist %s\n"
+                                               "doesn't start with correct 
prefix %s.  Ignoring." %
+                                               (exc, filelist_name, 
self.prefix), 2)
+                               if prefix_warnings[0] == 5:
+                                       log.Log("Future prefix errors will not 
be logged.", 2)
+
+               something_excluded, tuple_list = None, []
+               separator = Globals.null_separator and "\0" or "\n"
+               for line in filelist_fp.read().split(separator):
+                       if not line: continue # skip blanks
+                       try: tuple = self.filelist_parse_line(line, include)
+                       except FilePrefixError, exc:
+                               incr_warnings(exc)
+                               continue
+                       tuple_list.append(tuple)
+                       if not tuple[1]: something_excluded = 1
+               if filelist_fp.close():
+                       log.Log("Error closing filelist %s" % filelist_name, 2)
+               return (tuple_list, something_excluded)
+
+       def filelist_parse_line(self, line, include):
+               """Parse a single line of a filelist, returning a pair
+
+               pair will be of form (index, include), where index is another
+               tuple, and include is 1 if the line specifies that we are
+               including a file.  The default is given as an argument.
+               prefix is the string that the index is relative to.
+
+               """
+               if line[:2] == "+ ": # Check for "+ "/"- " syntax
+                       include = 1
+                       line = line[2:]
+               elif line[:2] == "- ":
+                       include = 0
+                       line = line[2:]
+
+               if not line.startswith(self.prefix): raise FilePrefixError(line)
+               line = line[len(self.prefix):] # Discard prefix
+               index = tuple(filter(lambda x: x, line.split("/"))) # remove 
empties
+               return (index, include)
+
+       def filelist_pair_match(self, rp, pair):
+               """Matches a filelist tuple against a rpath
+
+               Returns a pair (include, move_on).  include is None if the
+               tuple doesn't match either way, and 0/1 if the tuple excludes
+               or includes the rpath.
+
+               move_on is true if the tuple cannot match a later index, and
+               so we should move on to the next tuple in the index.
+
+               """
+               index, include = pair
+               if include == 1:
+                       if index < rp.index: return (None, 1)
+                       if index == rp.index: return (1, 1)
+                       elif index[:len(rp.index)] == rp.index:
+                               return (1, None) # /foo/bar implicitly includes 
/foo
+                       else: return (None, None) # rp greater, not initial 
sequence
+               elif include == 0:
+                       if rp.index[:len(index)] == index:
+                               return (0, None) # /foo implicitly excludes 
/foo/bar
+                       elif index < rp.index: return (None, 1)
+                       else: return (None, None) # rp greater, not initial 
sequence
+               else: assert 0, "Include is %s, should be 0 or 1" % (include,)
+
+       def filelist_globbing_get_sfs(self, filelist_fp, inc_default, 
list_name):
+               """Return list of selection functions by reading fileobj
+
+               filelist_fp should be an open file object
+               inc_default is true iff this is an include list
+               list_name is just the name of the list, used for logging
+               See the man page on --[include/exclude]-globbing-filelist
+
+               """
+               log.Log("Reading globbing filelist %s" % list_name, 4)
+               separator = Globals.null_separator and "\0" or "\n"
+               for line in filelist_fp.read().split(separator):
+                       if not line: continue # skip blanks
+                       if line[:2] == "+ ": yield self.glob_get_sf(line[2:], 1)
+                       elif line[:2] == "- ": yield self.glob_get_sf(line[2:], 
0)
+                       else: yield self.glob_get_sf(line, inc_default)
+
+       def other_filesystems_get_sf(self, include):
+               """Return selection function matching files on other 
filesystems"""
+               assert include == 0 or include == 1
+               root_devloc = self.rpath.getdevloc()
+               def sel_func(rp):
+                       if rp.getdevloc() == root_devloc: return None
+                       else: return include
+               sel_func.exclude = not include
+               sel_func.name = "Match other filesystems"
+               return sel_func
+
+       def regexp_get_sf(self, regexp_string, include):
+               """Return selection function given by regexp_string"""
+               assert include == 0 or include == 1
+               try: regexp = re.compile(regexp_string)
+               except:
+                       log.Log("Error compiling regular expression %s" % 
regexp_string, 1)
+                       raise
+               
+               def sel_func(rp):
+                       if regexp.search(rp.path): return include
+                       else: return None
+
+               sel_func.exclude = not include
+               sel_func.name = "Regular expression: %s" % regexp_string
+               return sel_func
+
+       def gen_get_sf(self, pred, include, name):
+               """Returns a selection function that uses pred to test
+               
+               RPath is matched if pred returns true on it.  Name is a string
+               summarizing the test to the user.
+
+               """
+               def sel_func(rp):
+                       if pred(rp): return include
+                       return None
+               sel_func.exclude = not include
+               sel_func.name = (include and "include " or "exclude ") + name
+               return sel_func
+
+       def devfiles_get_sf(self, include):
+               """Return a selection function matching all dev files"""
+               return self.gen_get_sf(rpath.RORPath.isdev, include, "device 
files")
+
+       def symlinks_get_sf(self, include):
+               """Return a selection function matching all symlinks"""
+               return self.gen_get_sf(rpath.RORPath.issym, include, "symbolic 
links")
+
+       def sockets_get_sf(self, include):
+               """Return a selection function matching all sockets"""
+               return self.gen_get_sf(rpath.RORPath.issock, include, "socket 
files")
+
+       def fifos_get_sf(self, include):
+               """Return a selection function matching all fifos"""
+               return self.gen_get_sf(rpath.RORPath.isfifo, include, "fifo 
files")
+
+       def special_get_sf(self, include):
+               """Return sel function matching sockets, symlinks, sockets, 
devs"""
+               def sel_func(rp):
+                       if rp.issym() or rp.issock() or rp.isfifo() or 
rp.isdev():
+                               return include
+                       else: return None
+               sel_func.exclude = not include
+               sel_func.name = (include and "include" or "exclude") + " 
special files"
+               return sel_func
+
+       def glob_get_sf(self, glob_str, include):
+               """Return selection function given by glob string"""
+               assert include == 0 or include == 1
+               if glob_str == "**": sel_func = lambda rp: include
+               elif not self.glob_re.match(glob_str): # normal file
+                       sel_func = self.glob_get_filename_sf(glob_str, include)
+               else: sel_func = self.glob_get_normal_sf(glob_str, include)
+
+               sel_func.exclude = not include
+               sel_func.name = "Command-line %s glob: %s" % \
+                                               (include and "include" or 
"exclude", glob_str)
+               return sel_func
+
+       def glob_get_filename_sf(self, filename, include):
+               """Get a selection function given a normal filename
+
+               Some of the parsing is better explained in
+               filelist_parse_line.  The reason this is split from normal
+               globbing is things are a lot less complicated if no special
+               globbing characters are used.
+
+               """
+               if not filename.startswith(self.prefix):
+                       raise FilePrefixError(filename)
+               index = tuple(filter(lambda x: x,
+                                                        
filename[len(self.prefix):].split("/")))
+               return self.glob_get_tuple_sf(index, include)
+
+       def glob_get_tuple_sf(self, tuple, include):
+               """Return selection function based on tuple"""
+               def include_sel_func(rp):
+                       if (rp.index == tuple[:len(rp.index)] or
+                               rp.index[:len(tuple)] == tuple):
+                               return 1 # /foo/bar implicitly matches /foo, 
vice-versa
+                       else: return None
+
+               def exclude_sel_func(rp):
+                       if rp.index[:len(tuple)] == tuple:
+                               return 0 # /foo excludes /foo/bar, not 
vice-versa
+                       else: return None
+
+               if include == 1: sel_func = include_sel_func
+               elif include == 0: sel_func = exclude_sel_func
+               sel_func.exclude = not include
+               sel_func.name = "Tuple select %s" % (tuple,)
+               return sel_func
+
+       def glob_get_normal_sf(self, glob_str, include):
+               """Return selection function based on glob_str
+
+               The basic idea is to turn glob_str into a regular expression,
+               and just use the normal regular expression.  There is a
+               complication because the selection function should return '2'
+               (scan) for directories which may contain a file which matches
+               the glob_str.  So we break up the glob string into parts, and
+               any file which matches an initial sequence of glob parts gets
+               scanned.
+
+               Thanks to Donovan Baarda who provided some code which did some
+               things similar to this.
+
+               """
+               if glob_str.lower().startswith("ignorecase:"):
+                       re_comp = lambda r: re.compile(r, re.I | re.S)
+                       glob_str = glob_str[len("ignorecase:"):]
+               else: re_comp = lambda r: re.compile(r, re.S)
+
+               # matches what glob matches and any files in directory
+               glob_comp_re = re_comp("^%s($|/)" % self.glob_to_re(glob_str))
+
+               if glob_str.find("**") != -1:
+                       glob_str = glob_str[:glob_str.find("**")+2] # truncate 
after **
+
+               scan_comp_re = re_comp("^(%s)$" %
+                                                          
"|".join(self.glob_get_prefix_res(glob_str)))
+
+               def include_sel_func(rp):
+                       if glob_comp_re.match(rp.path): return 1
+                       elif scan_comp_re.match(rp.path): return 2
+                       else: return None
+
+               def exclude_sel_func(rp):
+                       if glob_comp_re.match(rp.path): return 0
+                       else: return None
+
+               # Check to make sure prefix is ok
+               if not include_sel_func(self.rpath): raise 
FilePrefixError(glob_str)
+               
+               if include: return include_sel_func
+               else: return exclude_sel_func
+
+       def glob_get_prefix_res(self, glob_str):
+               """Return list of regexps equivalent to prefixes of glob_str"""
+               glob_parts = glob_str.split("/")
+               if "" in glob_parts[1:-1]: # "" OK if comes first or last, as 
in /foo/
+                       raise GlobbingError("Consecutive '/'s found in globbing 
string "
+                                                               + glob_str)
+
+               prefixes = map(lambda i: "/".join(glob_parts[:i+1]),
+                                          range(len(glob_parts)))
+               # we must make exception for root "/", only dir to end in slash
+               if prefixes[0] == "": prefixes[0] = "/"
+               return map(self.glob_to_re, prefixes)
+
+       def glob_to_re(self, pat):
+               """Returned regular expression equivalent to shell glob pat
+
+               Currently only the ?, *, [], and ** expressions are supported.
+               Ranges like [a-z] are also currently unsupported.  There is no
+               way to quote these special characters.
+
+               This function taken with minor modifications from efnmatch.py
+               by Donovan Baarda.
+
+               """
+               i, n, res = 0, len(pat), ''
+               while i < n:
+                       c, s = pat[i], pat[i:i+2]
+                       i = i+1
+                       if s == '**':
+                               res = res + '.*'
+                               i = i + 1
+                       elif c == '*': res = res + '[^/]*'
+                       elif c == '?': res = res + '[^/]'
+                       elif c == '[':
+                               j = i
+                               if j < n and pat[j] in '!^': j = j+1
+                               if j < n and pat[j] == ']': j = j+1
+                               while j < n and pat[j] != ']': j = j+1
+                               if j >= n: res = res + '\\[' # interpret the [ 
literally
+                               else: # Deal with inside of [..]
+                                       stuff = pat[i:j].replace('\\','\\\\')
+                                       i = j+1
+                                       if stuff[0] in '!^': stuff = '^' + 
stuff[1:]
+                                       res = res + '[' + stuff + ']'
+                       else: res = res + re.escape(c)
+               return res
+
+
+class FilterIter:
+       """Filter rorp_iter using a Select object, removing excluded rorps"""
+       def __init__(self, select, rorp_iter):
+               """Constructor
+
+               Input is the Select object to use and the iter of rorps to be
+               filtered.  The rorps will be converted to rps using the Select
+               base.
+
+               """
+               self.rorp_iter = rorp_iter
+               self.base_rp = select.rpath
+               self.stored_rorps = []
+               self.ITR = rorpiter.IterTreeReducer(FilterIterITRB,
+                                                                               
        [select.Select, self.stored_rorps])
+               self.itr_finished = 0
+
+       def __iter__(self): return self
+
+       def next(self):
+               """Return next object, or StopIteration"""
+               while not self.stored_rorps:
+                       try: next_rorp = self.rorp_iter.next()
+                       except StopIteration:
+                               if self.itr_finished: raise
+                               else:
+                                       self.ITR.Finish()
+                                       self.itr_finished = 1
+                       else:
+                               next_rp = rpath.RPath(self.base_rp.conn, 
self.base_rp.base,
+                                                                         
next_rorp.index, next_rorp.data)
+                               self.ITR(next_rorp.index, next_rp, next_rorp)
+               return self.stored_rorps.pop(0)
+
+class FilterIterITRB(rorpiter.ITRBranch):
+       """ITRBranch used in above FilterIter class
+
+       The reason this is necessary is because for directories sometimes
+       we don't know whether a rorp is excluded until we see what is in
+       the directory.
+
+       """
+       def __init__(self, select, rorp_cache):
+               """Initialize FilterIterITRB.  Called by IterTreeReducer.
+
+               select should be the relevant Select object used to test the
+               rps.  rorp_cache is the list rps should be appended to if they
+               aren't excluded.
+
+               """
+               self.select, self.rorp_cache = select, rorp_cache
+               self.branch_excluded = None
+               self.base_queue = None # holds branch base while examining 
contents
+
+       def can_fast_process(self, index, next_rp, next_rorp):
+               return not next_rp.isdir()
+
+       def fast_process(self, index, next_rp, next_rorp):
+               """For ordinary files, just append if select is positive"""
+               if self.branch_excluded: return
+               s = self.select(next_rp)
+               if s == 1:
+                       if self.base_queue:
+                               self.rorp_cache.append(self.base_queue)
+                               self.base_queue = None
+                       self.rorp_cache.append(next_rorp)
+               else: assert s == 0, "Unexpected select value %s" % (s,)
+
+       def start_process(self, index, next_rp, next_rorp):
+               s = self.select(next_rp)
+               if s == 0: self.branch_excluded = 1
+               elif s == 1: self.rorp_cache.append(next_rorp)
+               else:
+                       assert s == 2, s
+                       self.base_queue = next_rorp
+
Index: rdiff-backup/testing/selectiontest.py
diff -u /dev/null rdiff-backup/testing/selectiontest.py:1.15.2.1
--- /dev/null   Tue Jan 10 06:15:42 2006
+++ rdiff-backup/testing/selectiontest.py       Tue Jan 10 06:15:42 2006
@@ -0,0 +1,474 @@
+from __future__ import generators
+import re, StringIO, unittest, types
+from commontest import *
+from rdiff_backup.selection import *
+from rdiff_backup import Globals, rpath, lazy
+
+
+class MatchingTest(unittest.TestCase):
+       """Test matching of file names against various selection functions"""
+       def makerp(self, path): return rpath.RPath(Globals.local_connection, 
path)
+       def makeext(self, path): return 
self.root.new_index(tuple(path.split("/")))
+
+       def setUp(self):
+               self.root = rpath.RPath(Globals.local_connection, 
"testfiles/select")
+               self.Select = Select(self.root)
+
+       def testRegexp(self):
+               """Test regular expression selection func"""
+               sf1 = self.Select.regexp_get_sf(".*\.py", 1)
+               assert sf1(self.makeext("1.py")) == 1
+               assert sf1(self.makeext("usr/foo.py")) == 1
+               assert sf1(self.root.append("1.doc")) == None
+
+               sf2 = self.Select.regexp_get_sf("hello", 0)
+               assert sf2(self.makerp("hello")) == 0
+               assert sf2(self.makerp("foohello_there")) == 0
+               assert sf2(self.makerp("foo")) == None
+
+       def testTupleInclude(self):
+               """Test include selection function made from a regular 
filename"""
+               self.assertRaises(FilePrefixError,
+                                                 
self.Select.glob_get_filename_sf, "foo", 1)
+
+               sf2 = 
self.Select.glob_get_sf("testfiles/select/usr/local/bin/", 1)
+               assert sf2(self.makeext("usr")) == 1
+               assert sf2(self.makeext("usr/local")) == 1
+               assert sf2(self.makeext("usr/local/bin")) == 1
+               assert sf2(self.makeext("usr/local/doc")) == None
+               assert sf2(self.makeext("usr/local/bin/gzip")) == 1
+               assert sf2(self.makeext("usr/local/bingzip")) == None
+
+       def testTupleExclude(self):
+               """Test exclude selection function made from a regular 
filename"""
+               self.assertRaises(FilePrefixError,
+                                                 
self.Select.glob_get_filename_sf, "foo", 0)
+
+               sf2 = 
self.Select.glob_get_sf("testfiles/select/usr/local/bin/", 0)
+               assert sf2(self.makeext("usr")) == None
+               assert sf2(self.makeext("usr/local")) == None
+               assert sf2(self.makeext("usr/local/bin")) == 0
+               assert sf2(self.makeext("usr/local/doc")) == None
+               assert sf2(self.makeext("usr/local/bin/gzip")) == 0
+               assert sf2(self.makeext("usr/local/bingzip")) == None
+
+       def testGlobStarInclude(self):
+               """Test a few globbing patterns, including **"""
+               sf1 = self.Select.glob_get_sf("**", 1)
+               assert sf1(self.makeext("foo")) == 1
+               assert sf1(self.makeext("")) == 1
+
+               sf2 = self.Select.glob_get_sf("**.py", 1)
+               assert sf2(self.makeext("foo")) == 2
+               assert sf2(self.makeext("usr/local/bin")) == 2
+               assert sf2(self.makeext("what/ever.py")) == 1
+               assert sf2(self.makeext("what/ever.py/foo")) == 1
+
+       def testGlobStarExclude(self):
+               """Test a few glob excludes, including **"""
+               sf1 = self.Select.glob_get_sf("**", 0)
+               assert sf1(self.makeext("/usr/local/bin")) == 0
+
+               sf2 = self.Select.glob_get_sf("**.py", 0)
+               assert sf2(self.makeext("foo")) == None, 
sf2(self.makeext("foo"))
+               assert sf2(self.makeext("usr/local/bin")) == None
+               assert sf2(self.makeext("what/ever.py")) == 0
+               assert sf2(self.makeext("what/ever.py/foo")) == 0
+
+       def testFilelistInclude(self):
+               """Test included filelist"""
+               fp = StringIO.StringIO("""
+testfiles/select/1/2
+testfiles/select/1
+testfiles/select/1/2/3
+testfiles/select/3/3/2""")
+               sf = self.Select.filelist_get_sf(fp, 1, "test")
+               assert sf(self.root) == 1
+               assert sf(self.makeext("1")) == 1
+               assert sf(self.makeext("1/1")) == None
+               assert sf(self.makeext("1/2/3")) == 1
+               assert sf(self.makeext("2/2")) == None
+               assert sf(self.makeext("3")) == 1
+               assert sf(self.makeext("3/3")) == 1
+               assert sf(self.makeext("3/3/3")) == None
+
+       def testFilelistWhitespaceInclude(self):
+               """Test included filelist, with some whitespace"""
+               fp = StringIO.StringIO("""
++ testfiles/select/1  
+- testfiles/select/2  
+testfiles/select/3\t""")
+               sf = self.Select.filelist_get_sf(fp, 1, "test")
+               assert sf(self.root) == 1
+               assert sf(self.makeext("1  ")) == 1
+               assert sf(self.makeext("2  ")) == 0
+               assert sf(self.makeext("3\t")) == 1
+               assert sf(self.makeext("4")) == None
+
+       def testFilelistIncludeNullSep(self):
+               """Test included filelist but with null_separator set"""
+               fp = 
StringIO.StringIO("""\0testfiles/select/1/2\0testfiles/select/1\0testfiles/select/1/2/3\0testfiles/select/3/3/2\0testfiles/select/hello\nthere\0""")
+               Globals.null_separator = 1
+               sf = self.Select.filelist_get_sf(fp, 1, "test")
+               assert sf(self.root) == 1
+               assert sf(self.makeext("1")) == 1
+               assert sf(self.makeext("1/1")) == None
+               assert sf(self.makeext("1/2/3")) == 1
+               assert sf(self.makeext("2/2")) == None
+               assert sf(self.makeext("3")) == 1
+               assert sf(self.makeext("3/3")) == 1
+               assert sf(self.makeext("3/3/3")) == None
+               assert sf(self.makeext("hello\nthere")) == 1
+               Globals.null_separator = 0
+
+       def testFilelistExclude(self):
+               """Test included filelist"""
+               fp = StringIO.StringIO("""
+testfiles/select/1/2
+testfiles/select/1
+this is a badly formed line which should be ignored
+
+testfiles/select/1/2/3
+testfiles/select/3/3/2""")
+               sf = self.Select.filelist_get_sf(fp, 0, "test")
+               assert sf(self.root) == None
+               assert sf(self.makeext("1")) == 0
+               assert sf(self.makeext("1/1")) == 0
+               assert sf(self.makeext("1/2/3")) == 0
+               assert sf(self.makeext("2/2")) == None
+               assert sf(self.makeext("3")) == None
+               assert sf(self.makeext("3/3/2")) == 0
+               assert sf(self.makeext("3/3/3")) == None
+
+       def testFilelistInclude2(self):
+               """testFilelistInclude2 - with modifiers"""
+               fp = StringIO.StringIO("""
+testfiles/select/1/1
+- testfiles/select/1/2
++ testfiles/select/1/3
+- testfiles/select/3""")
+               sf = self.Select.filelist_get_sf(fp, 1, "test1")
+               assert sf(self.makeext("1")) == 1
+               assert sf(self.makeext("1/1")) == 1
+               assert sf(self.makeext("1/1/2")) == None                        
         
+               assert sf(self.makeext("1/2")) == 0
+               assert sf(self.makeext("1/2/3")) == 0
+               assert sf(self.makeext("1/3")) == 1
+               assert sf(self.makeext("2")) == None
+               assert sf(self.makeext("3")) == 0
+
+       def testFilelistExclude2(self):
+               """testFilelistExclude2 - with modifiers"""
+               fp = StringIO.StringIO("""
+testfiles/select/1/1
+- testfiles/select/1/2
++ testfiles/select/1/3
+- testfiles/select/3""")
+               sf = self.Select.filelist_get_sf(fp, 0, "test1")
+               sf_val1 = sf(self.root)
+               assert sf_val1 == 1 or sf_val1 == None # either is OK
+               sf_val2 = sf(self.makeext("1"))
+               assert sf_val2 == 1 or sf_val2 == None
+               assert sf(self.makeext("1/1")) == 0
+               assert sf(self.makeext("1/1/2")) == 0
+               assert sf(self.makeext("1/2")) == 0
+               assert sf(self.makeext("1/2/3")) == 0
+               assert sf(self.makeext("1/3")) == 1
+               assert sf(self.makeext("2")) == None
+               assert sf(self.makeext("3")) == 0
+
+       def testGlobRE(self):
+               """testGlobRE - test translation of shell pattern to regular 
exp"""
+               assert self.Select.glob_to_re("hello") == "hello"
+               assert self.Select.glob_to_re(".e?ll**o") == "\\.e[^/]ll.*o"
+               r = self.Select.glob_to_re("[abc]el[^de][!fg]h")
+               assert r == "[abc]el[^de][^fg]h", r
+               r = self.Select.glob_to_re("/usr/*/bin/")
+               assert r == "\\/usr\\/[^/]*\\/bin\\/", r
+               assert self.Select.glob_to_re("[a.b/c]") == "[a.b/c]"
+               r = self.Select.glob_to_re("[a*b-c]e[!]]")
+               assert r == "[a*b-c]e[^]]", r
+
+       def testGlobSFException(self):
+               """testGlobSFException - see if globbing errors returned"""
+               self.assertRaises(GlobbingError, self.Select.glob_get_normal_sf,
+                                                 
"testfiles/select/hello//there", 1)
+               self.assertRaises(FilePrefixError,
+                                                 self.Select.glob_get_sf, 
"testfiles/whatever", 1)
+               self.assertRaises(FilePrefixError,
+                                                 self.Select.glob_get_sf, 
"testfiles/?hello", 0)
+               assert self.Select.glob_get_normal_sf("**", 1)
+
+       def testIgnoreCase(self):
+               """testIgnoreCase - try a few expressions with ignorecase:"""
+               sf = 
self.Select.glob_get_sf("ignorecase:testfiles/SeLect/foo/bar", 1)
+               assert sf(self.makeext("FOO/BAR")) == 1
+               assert sf(self.makeext("foo/bar")) == 1
+               assert sf(self.makeext("fOo/BaR")) == 1
+               self.assertRaises(FilePrefixError, self.Select.glob_get_sf,
+                                                 
"ignorecase:tesfiles/sect/foo/bar", 1)
+                                                 
+       def testDev(self):
+               """Test device and special file selection"""
+               dir = self.root.append("filetypes")
+               fifo = dir.append("fifo")
+               assert fifo.isfifo(), fifo
+               sym = dir.append("symlink")
+               assert sym.issym(), sym
+               reg = dir.append("regular_file")
+               assert reg.isreg(), reg
+               sock = dir.append("replace_with_socket")
+               if not sock.issock():
+                       assert sock.isreg(), sock
+                       sock.delete()
+                       sock.mksock()
+                       assert sock.issock()
+               dev = dir.append("ttyS1")
+               assert dev.isdev(), dev
+               
+               sf = self.Select.devfiles_get_sf(0)
+               assert sf(dir) == None
+               assert sf(dev) == 0
+               assert sf(sock) == None
+
+               sf2 = self.Select.special_get_sf(0)
+               assert sf2(dir) == None
+               assert sf2(reg) == None
+               assert sf2(dev) == 0
+               assert sf2(sock) == 0
+               assert sf2(fifo) == 0
+               assert sf2(sym) == 0
+
+               sf3 = self.Select.symlinks_get_sf(0)
+               assert sf3(dir) == None
+               assert sf3(reg) == None
+               assert sf3(dev) == None
+               assert sf3(sock) == None
+               assert sf3(fifo) == None
+               assert sf3(sym) == 0
+
+       def testRoot(self):
+               """testRoot - / may be a counterexample to several of these.."""
+               root = rpath.RPath(Globals.local_connection, "/")
+               select = Select(root)
+
+               assert select.glob_get_sf("/", 1)(root) == 1
+               assert select.glob_get_sf("/foo", 1)(root) == 1
+               assert select.glob_get_sf("/foo/bar", 1)(root) == 1
+               assert select.glob_get_sf("/", 0)(root) == 0
+               assert select.glob_get_sf("/foo", 0)(root) == None
+
+               assert select.glob_get_sf("**.py", 1)(root) == 2
+               assert select.glob_get_sf("**", 1)(root) == 1
+               assert select.glob_get_sf("ignorecase:/", 1)(root) == 1
+               assert select.glob_get_sf("**.py", 0)(root) == None
+               assert select.glob_get_sf("**", 0)(root) == 0
+               assert select.glob_get_sf("/foo/*", 0)(root) == None
+
+               select.filelist_get_sf(StringIO.StringIO("/"), 1, "test")(root) 
== 1
+               select.filelist_get_sf(StringIO.StringIO("/foo/bar"), 1,
+                                                          "test")(root) == 1
+               select.filelist_get_sf(StringIO.StringIO("/"), 0, "test")(root) 
== 0
+               select.filelist_get_sf(StringIO.StringIO("/foo/bar"), 0,
+                                                          "test")(root) == None
+
+       def testOtherFilesystems(self):
+               """Test to see if --exclude-other-filesystems works correctly"""
+               root = rpath.RPath(Globals.local_connection, "/")
+               select = Select(root)
+               sf = select.other_filesystems_get_sf(0)
+               assert sf(root) is None
+               assert sf(RPath(Globals.local_connection, "/usr/bin")) is None, 
\
+                          "Assumption: /usr/bin is on the same filesystem as /"
+               assert sf(RPath(Globals.local_connection, "/proc")) == 0, \
+                          "Assumption: /proc is on a different filesystem"
+               assert sf(RPath(Globals.local_connection, "/boot")) == 0, \
+                          "Assumption: /boot is on a different filesystem"
+
+class ParseArgsTest(unittest.TestCase):
+       """Test argument parsing as well as filelist globbing"""
+       root = None
+       def ParseTest(self, tuplelist, indicies, filelists = []):
+               """No error if running select on tuple goes over indicies"""
+               if not self.root:
+                       self.root = RPath(Globals.local_connection, 
"testfiles/select")
+               self.Select = Select(self.root)
+               self.Select.ParseArgs(tuplelist, 
self.remake_filelists(filelists))
+               self.Select.set_iter()
+               assert lazy.Iter.equal(lazy.Iter.map(lambda dsrp: dsrp.index,
+                                                                               
         self.Select),
+                                                          iter(indicies), 
verbose = 1)
+
+       def remake_filelists(self, filelist):
+               """Turn strings in filelist into fileobjs"""
+               new_filelists = []
+               for f in filelist:
+                       if type(f) is types.StringType:
+                               new_filelists.append(StringIO.StringIO(f))
+                       else: new_filelists.append(f)
+               return new_filelists
+
+       def testParse(self):
+               """Test just one include, all exclude"""
+               self.ParseTest([("--include", "testfiles/select/1/1"),
+                                               ("--exclude", "**")],
+                                          [(), ('1',), ("1", "1"), ("1", '1', 
'1'),
+                                                        ('1', '1', '2'), ('1', 
'1', '3')])
+               
+       def testParse2(self):
+               """Test three level include/exclude"""
+               self.ParseTest([("--exclude", "testfiles/select/1/1/1"),
+                                                          ("--include", 
"testfiles/select/1/1"),
+                                                          ("--exclude", 
"testfiles/select/1"),
+                                                          ("--exclude", "**")],
+                                          [(), ('1',), ('1', '1'), ('1', '1', 
'2'),
+                                               ('1', '1', '3')])
+
+       def test_globbing_filelist(self):
+               """Filelist glob test similar to above testParse2"""
+               self.ParseTest([("--include-globbing-filelist", "file")],
+                                          [(), ('1',), ('1', '1'), ('1', '1', 
'2'),
+                                               ('1', '1', '3')],
+                                          ["""
+- testfiles/select/1/1/1
+testfiles/select/1/1
+- testfiles/select/1
+- **
+"""])
+
+       def testGlob(self):
+               """Test globbing expression"""
+               self.ParseTest([("--exclude", "**[3-5]"),
+                                               ("--include", 
"testfiles/select/1"),
+                                               ("--exclude", "**")],
+                                          [(), ('1',), ('1', '1'),
+                                               ('1', '1', '1'), ('1', '1', 
'2'),
+                                               ('1', '2'), ('1', '2', '1'), 
('1', '2', '2')])
+               self.ParseTest([("--include", "testfiles/select**/2"),
+                                               ("--exclude", "**")],
+                                          [(), ('1',), ('1', '1'),
+                                               ('1', '1', '2'),
+                                               ('1', '2'),
+                                               ('1', '2', '1'), ('1', '2', 
'2'), ('1', '2', '3'),
+                                               ('1', '3'),
+                                               ('1', '3', '2'),
+                                               ('2',), ('2', '1'),
+                                               ('2', '1', '1'), ('2', '1', 
'2'), ('2', '1', '3'),
+                                               ('2', '2'),
+                                               ('2', '2', '1'), ('2', '2', 
'2'), ('2', '2', '3'),
+                                               ('2', '3'),
+                                               ('2', '3', '1'), ('2', '3', 
'2'), ('2', '3', '3'),
+                                               ('3',), ('3', '1'),
+                                               ('3', '1', '2'),
+                                               ('3', '2'),
+                                               ('3', '2', '1'), ('3', '2', 
'2'), ('3', '2', '3'),
+                                               ('3', '3'),
+                                               ('3', '3', '2')])
+
+       def test_globbing_filelist2(self):
+               """Filelist glob test similar to above testGlob"""
+               self.ParseTest([("--exclude-globbing-filelist", "asoeuth")],
+                                          [(), ('1',), ('1', '1'),
+                                               ('1', '1', '1'), ('1', '1', 
'2'),
+                                               ('1', '2'), ('1', '2', '1'), 
('1', '2', '2')],
+                                          ["""
+**[3-5]
++ testfiles/select/1
+**
+"""])
+               self.ParseTest([("--include-globbing-filelist", "file")],
+                                          [(), ('1',), ('1', '1'),
+                                               ('1', '1', '2'),
+                                               ('1', '2'),
+                                               ('1', '2', '1'), ('1', '2', 
'2'), ('1', '2', '3'),
+                                               ('1', '3'),
+                                               ('1', '3', '2'),
+                                               ('2',), ('2', '1'),
+                                               ('2', '1', '1'), ('2', '1', 
'2'), ('2', '1', '3'),
+                                               ('2', '2'),
+                                               ('2', '2', '1'), ('2', '2', 
'2'), ('2', '2', '3'),
+                                               ('2', '3'),
+                                               ('2', '3', '1'), ('2', '3', 
'2'), ('2', '3', '3'),
+                                               ('3',), ('3', '1'),
+                                               ('3', '1', '2'),
+                                               ('3', '2'),
+                                               ('3', '2', '1'), ('3', '2', 
'2'), ('3', '2', '3'),
+                                               ('3', '3'),
+                                               ('3', '3', '2')],
+                                          ["""
+testfiles/select**/2
+- **
+"""])
+                                          
+       def testGlob2(self):
+               """Test more globbing functions"""
+               self.ParseTest([("--include", "testfiles/select/*foo*/p*"),
+                                               ("--exclude", "**")],
+                                          [(), ('efools',), ('efools', 'ping'),
+                                               ('foobar',), ('foobar', 
'pong')])
+               self.ParseTest([("--exclude", "testfiles/select/1/1/*"),
+                                               ("--exclude", 
"testfiles/select/1/2/**"),
+                                               ("--exclude", 
"testfiles/select/1/3**"),
+                                               ("--include", 
"testfiles/select/1"),
+                                               ("--exclude", "**")],
+                                          [(), ('1',), ('1', '1'), ('1', '2')])
+
+       def testGlob3(self):
+               """Test for bug when **is in front"""
+               self.ParseTest([("--include", "**NOTEXIST"),
+                                               ("--exclude", 
"**NOTEXISTEITHER"),
+                                               ("--include", 
"testfiles/select/efools"),
+                                               ("--exclude", "**")],
+                                          [(), ('efools',), ('efools', 
'ping')])
+
+       def testAlternateRoot(self):
+               """Test select with different root"""
+               self.root = rpath.RPath(Globals.local_connection, 
"testfiles/select/1")
+               self.ParseTest([("--exclude", "testfiles/select/1/[23]")],
+                                          [(), ('1',), ('1', '1'), ('1', '2'), 
('1', '3')])
+
+               self.root = rpath.RPath(Globals.local_connection, "/")
+               self.ParseTest([("--exclude", "/home/*"),
+                                               ("--include", "/home"),
+                                               ("--exclude", "/")],
+                                          [(), ("home",)])
+
+#      def testParseStartingFrom(self):
+#              """Test parse, this time starting from inside"""
+#              self.root = rpath.RPath(Globals.local_connection, 
"testfiles/select")
+#              self.Select = Select(self.root)
+#              self.Select.ParseArgs([("--include", "testfiles/select/1/1"),
+#                                                         ("--exclude", 
"**")], [])
+#              self.Select.set_iter(('1', '1'))
+#              assert lazy.Iter.equal(lazy.Iter.map(lambda dsrp: dsrp.index,
+#                                                                              
         self.Select),
+#                                                iter([("1", '1', '1'),
+#                                                              ('1', '1', '2'),
+#                                                              ('1', '1', 
'3')]),
+#                                                verbose = 1)
+
+
+class CommandTest(unittest.TestCase):
+       """Test rdiff-backup on actual directories"""
+       def testEmptyDirInclude(self):
+               """Make sure empty directories are included with **xx exps
+
+               This checks for a bug present in 1.0.3/1.1.5 and similar.
+
+               """
+               outrp = MakeOutputDir()
+               selrp = rpath.RPath(Globals.local_connection, 
'testfiles/seltest')
+               re_init_dir(selrp)
+               emptydir = selrp.append('emptydir')
+               emptydir.mkdir()
+
+               rdiff_backup(1, 1, selrp.path, outrp.path,
+                                        extra_options = ("--include **XX "
+                                                                         
"--exclude testfiles/seltest/YYYY"))
+
+               outempty = outrp.append('emptydir')
+               assert outempty.isdir(), outempty
+
+
+
+if __name__ == "__main__": unittest.main()




reply via email to

[Prev in Thread] Current Thread [Next in Thread]