Rev 4981: (John Whitley) implement ! and !! to exclude files from ignore rules. in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Thu Jan 21 17:55:03 GMT 2010


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 4981 [merge]
revision-id: pqm at pqm.ubuntu.com-20100121175458-17l0cvckbrf93eea
parent: pqm at pqm.ubuntu.com-20100121100042-77858a88k2zpec7b
parent: whitley at bangpath.org-20100111164402-9luag9p9ahpy4kmz
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Thu 2010-01-21 17:54:58 +0000
message:
  (John Whitley) implement ! and !! to exclude files from ignore rules.
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/globbing.py             glob.py-20061113075651-q63o2v35fm2ydk9x-1
  bzrlib/help_topics/en/patterns.txt patterns.txt-20080625070357-wx8qm46a19ejwfns-1
  bzrlib/tests/per_workingtree/test_is_ignored.py test_is_ignored.py-20060518083307-a5b383dd4d070083
  bzrlib/tests/test_globbing.py  test_glob.py-20061113075651-q63o2v35fm2ydk9x-2
  bzrlib/tests/test_ignores.py   test_ignores.py-20060712172354-vqq9ln0t8di27v53-1
  bzrlib/workingtree.py          workingtree.py-20050511021032-29b6ec0a681e02e3
=== modified file 'NEWS'
--- a/NEWS	2010-01-21 09:16:07 +0000
+++ b/NEWS	2010-01-21 17:54:58 +0000
@@ -17,6 +17,13 @@
 New Features
 ************
 
+* New ignore patterns.  Patterns prefixed with '!' are exceptions to 
+  ignore patterns and take precedence over regular ignores.  Such 
+  exceptions are used to specify files that should be versioned which 
+  would otherwise be ignored.  Patterns prefixed with '!!' act as regular 
+  ignore patterns, but have highest precedence, even over the '!' 
+  exception patterns. (John Whitley, #428031)
+
 * Add bug information to log output when available.
   (Neil Martinsen-Burrell, Guillermo Gonzalez, #251729)
 

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2010-01-21 09:13:03 +0000
+++ b/bzrlib/builtins.py	2010-01-21 17:54:58 +0000
@@ -2609,6 +2609,13 @@
     After adding, editing or deleting that file either indirectly by
     using this command or directly by using an editor, be sure to commit
     it.
+    
+    Patterns prefixed with '!' are exceptions to ignore patterns and take
+    precedence over regular ignores.  Such exceptions are used to specify
+    files that should be versioned which would otherwise be ignored.
+    
+    Patterns prefixed with '!!' act as regular ignore patterns, but have
+    precedence over the '!' exception patterns.
 
     Note: ignore patterns containing shell wildcards must be quoted from
     the shell on Unix.
@@ -2618,10 +2625,14 @@
 
             bzr ignore ./Makefile
 
-        Ignore class files in all directories::
+        Ignore .class files in all directories...::
 
             bzr ignore "*.class"
 
+        ...but do not ignore "special.class"::
+
+            bzr ignore "!special.class"
+
         Ignore .o files under the lib directory::
 
             bzr ignore "lib/**/*.o"
@@ -2633,6 +2644,13 @@
         Ignore everything but the "debian" toplevel directory::
 
             bzr ignore "RE:(?!debian/).*"
+        
+        Ignore everything except the "local" toplevel directory,
+        but always ignore "*~" autosave files, even under local/::
+        
+            bzr ignore "*"
+            bzr ignore "!./local"
+            bzr ignore "!!*~"
     """
 
     _see_also = ['status', 'ignored', 'patterns']

=== modified file 'bzrlib/globbing.py'
--- a/bzrlib/globbing.py	2009-11-11 21:38:02 +0000
+++ b/bzrlib/globbing.py	2010-01-11 16:44:02 +0000
@@ -215,6 +215,40 @@
                 return patterns[match.lastindex -1]
         return None
 
+class ExceptionGlobster(object):
+    """A Globster that supports exception patterns.
+    
+    Exceptions are ignore patterns prefixed with '!'.  Exception
+    patterns take precedence over regular patterns and cause a 
+    matching filename to return None from the match() function.  
+    Patterns using a '!!' prefix are highest precedence, and act 
+    as regular ignores. '!!' patterns are useful to establish ignores
+    that apply under paths specified by '!' exception patterns.
+    """
+    
+    def __init__(self,patterns):
+        ignores = [[], [], []]
+        for p in patterns:
+            if p.startswith(u'!!'):
+                ignores[2].append(p[2:])
+            elif p.startswith(u'!'):
+                ignores[1].append(p[1:])
+            else:
+                ignores[0].append(p)
+        self._ignores = [Globster(i) for i in ignores]
+        
+    def match(self, filename):
+        """Searches for a pattern that matches the given filename.
+
+        :return A matching pattern or None if there is no matching pattern.
+        """
+        double_neg = self._ignores[2].match(filename)
+        if double_neg:
+            return "!!%s" % double_neg
+        elif self._ignores[1].match(filename):
+            return None
+        else:
+            return self._ignores[0].match(filename)
 
 class _OrderedGlobster(Globster):
     """A Globster that keeps pattern order."""
@@ -244,8 +278,7 @@
 
     Doesn't normalize regular expressions - they may contain escapes.
     """
-
-    if not pattern.startswith('RE:'):
+    if not (pattern.startswith('RE:') or pattern.startswith('!RE:')):
         pattern = _slashes.sub('/', pattern)
     if len(pattern) > 1:
         pattern = pattern.rstrip('/')

=== modified file 'bzrlib/help_topics/en/patterns.txt'
--- a/bzrlib/help_topics/en/patterns.txt	2008-06-25 07:17:14 +0000
+++ b/bzrlib/help_topics/en/patterns.txt	2010-01-02 07:36:36 +0000
@@ -23,3 +23,6 @@
 patterns are identified by a 'RE:' prefix followed by the regular
 expression.  Regular expression patterns may not include named or
 numbered groups.
+
+Ignore patterns may be prefixed with '!', which means that a filename
+matched by that pattern will not be ignored.

=== modified file 'bzrlib/tests/per_workingtree/test_is_ignored.py'
--- a/bzrlib/tests/per_workingtree/test_is_ignored.py	2009-07-10 07:14:02 +0000
+++ b/bzrlib/tests/per_workingtree/test_is_ignored.py	2010-01-01 21:26:25 +0000
@@ -31,11 +31,15 @@
             ('.bzrignore', './rootdir\n'
                            'randomfile*\n'
                            '*bar\n'
+                           '!bazbar\n'
                            '?foo\n'
                            '*.~*\n'
                            'dir1/*f1\n'
                            'dir1/?f2\n'
+                           'RE:dir2/.*\.wombat\n'
                            'path/from/ro?t\n'
+                           '**/piffle.py\n'
+                           '!b/piffle.py\n'
                            'unicode\xc2\xb5\n' # u'\xb5'.encode('utf8')
                            'dos\r\n'
                            '\n' # empty line
@@ -58,6 +62,12 @@
         self.assertEqual("path/from/ro?t", tree.is_ignored('path/from/root'))
         self.assertEqual("path/from/ro?t", tree.is_ignored('path/from/roat'))
         self.assertEqual(None, tree.is_ignored('roat'))
+        
+        self.assertEqual('**/piffle.py', tree.is_ignored('piffle.py'))
+        self.assertEqual('**/piffle.py', tree.is_ignored('a/piffle.py'))
+        self.assertEqual(None, tree.is_ignored('b/piffle.py')) # exclusion
+        self.assertEqual('**/piffle.py', tree.is_ignored('foo/bar/piffle.py'))
+        self.assertEqual(None, tree.is_ignored('p/iffle.py'))
 
         self.assertEqual(u'unicode\xb5', tree.is_ignored(u'unicode\xb5'))
         self.assertEqual(u'unicode\xb5', tree.is_ignored(u'subdir/unicode\xb5'))
@@ -72,6 +82,8 @@
         self.assertEqual('*bar', tree.is_ignored(r'foo\nbar'))
         self.assertEqual('*bar', tree.is_ignored('bar'))
         self.assertEqual('*bar', tree.is_ignored('.bar'))
+        
+        self.assertEqual(None, tree.is_ignored('bazbar')) # exclusion
 
         self.assertEqual('?foo', tree.is_ignored('afoo'))
         self.assertEqual('?foo', tree.is_ignored('.foo'))
@@ -84,6 +96,9 @@
 
         self.assertEqual('dir1/?f2', tree.is_ignored('dir1/ff2'))
         self.assertEqual('dir1/?f2', tree.is_ignored('dir1/.f2'))
+        
+        self.assertEqual('RE:dir2/.*\.wombat', tree.is_ignored('dir2/foo.wombat'))
+        self.assertEqual(None, tree.is_ignored('dir2/foo'))
 
         # Blank lines and comments should be ignored
         self.assertEqual(None, tree.is_ignored(''))

=== modified file 'bzrlib/tests/test_globbing.py'
--- a/bzrlib/tests/test_globbing.py	2009-11-11 21:38:02 +0000
+++ b/bzrlib/tests/test_globbing.py	2010-01-11 16:44:02 +0000
@@ -17,6 +17,7 @@
 
 from bzrlib.globbing import (
     Globster,
+    ExceptionGlobster,
     _OrderedGlobster,
     normalize_pattern
     )
@@ -307,6 +308,30 @@
             self.assertEqual(patterns[x],globster.match(filename))
         self.assertEqual(None,globster.match('foobar.300'))
 
+class TestExceptionGlobster(TestCase):
+
+    def test_exclusion_patterns(self):
+        """test that exception patterns are not matched"""
+        patterns = [ u'*', u'!./local', u'!./local/**/*', u'!RE:\.z.*',u'!!./.zcompdump' ]
+        globster = ExceptionGlobster(patterns)
+        self.assertEqual(u'*', globster.match('tmp/foo.txt'))
+        self.assertEqual(None, globster.match('local'))
+        self.assertEqual(None, globster.match('local/bin/wombat'))
+        self.assertEqual(None, globster.match('.zshrc'))
+        self.assertEqual(None, globster.match('.zfunctions/fiddle/flam'))
+        self.assertEqual(u'!!./.zcompdump', globster.match('.zcompdump'))
+
+    def test_exclusion_order(self):
+        """test that ordering of exclusion patterns does not matter"""
+        patterns = [ u'static/**/*.html', u'!static/**/versionable.html']
+        globster = ExceptionGlobster(patterns)
+        self.assertEqual(u'static/**/*.html', globster.match('static/foo.html'))
+        self.assertEqual(None, globster.match('static/versionable.html'))
+        self.assertEqual(None, globster.match('static/bar/versionable.html'))
+        globster = ExceptionGlobster(reversed(patterns))
+        self.assertEqual(u'static/**/*.html', globster.match('static/foo.html'))
+        self.assertEqual(None, globster.match('static/versionable.html'))
+        self.assertEqual(None, globster.match('static/bar/versionable.html'))
 
 class TestOrderedGlobster(TestCase):
 

=== modified file 'bzrlib/tests/test_ignores.py'
--- a/bzrlib/tests/test_ignores.py	2009-03-23 14:59:43 +0000
+++ b/bzrlib/tests/test_ignores.py	2010-01-05 19:29:07 +0000
@@ -34,6 +34,8 @@
                 '\n' # empty line
                 '#comment\n'
                 ' xx \n' # whitespace
+                '!RE:^\.z.*\n'
+                '!!./.zcompdump\n'
                 ))
         self.assertEqual(set(['./rootdir',
                           'randomfile*',
@@ -41,6 +43,8 @@
                           u'unicode\xb5',
                           'dos',
                           ' xx ',
+                          '!RE:^\.z.*',
+                          '!!./.zcompdump',
                          ]), ignored)
 
     def test_parse_empty(self):

=== modified file 'bzrlib/workingtree.py'
--- a/bzrlib/workingtree.py	2010-01-13 23:06:42 +0000
+++ b/bzrlib/workingtree.py	2010-01-21 17:54:58 +0000
@@ -1742,13 +1742,15 @@
         r"""Check whether the filename matches an ignore pattern.
 
         Patterns containing '/' or '\' need to match the whole path;
-        others match against only the last component.
+        others match against only the last component.  Patterns starting
+        with '!' are ignore exceptions.  Exceptions take precedence
+        over regular patterns and cause the filename to not be ignored.
 
         If the file is ignored, returns the pattern which caused it to
         be ignored, otherwise None.  So this can simply be used as a
         boolean if desired."""
         if getattr(self, '_ignoreglobster', None) is None:
-            self._ignoreglobster = globbing.Globster(self.get_ignore_list())
+            self._ignoreglobster = globbing.ExceptionGlobster(self.get_ignore_list())
         return self._ignoreglobster.match(filename)
 
     def kind(self, file_id):




More information about the bazaar-commits mailing list