Bumped to version 1.0.0
[be.git] / libbe / storage / vcs / bzr.py
index 03a64f823ea045abf92fa40658b09ed77c509f16..1862975ef801ce3826a30c7a9ded8aee61d158c0 100644 (file)
@@ -1,25 +1,27 @@
-# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc.
+# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
 #                         Ben Finney <benf@cybersource.com.au>
 #                         Gianluca Montecchi <gian@grys.it>
-#                         Marien Zwart <marienz@gentoo.org>
+#                         Marien Zwart <marien.zwart@gmail.com>
 #                         W. Trevor King <wking@drexel.edu>
 #
-# This program 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 2 of the License, or
-# (at your option) any later version.
+# This file is part of Bugs Everywhere.
 #
-# This program 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.
+# Bugs Everywhere 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 2 of the License, or (at your
+# option) any later version.
 #
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+# Bugs Everywhere 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 Bugs Everywhere.  If not, see <http://www.gnu.org/licenses/>.
 
-"""
-Bazaar (bzr) backend.
+"""Bazaar_ (bzr) backend.
+
+.. _Bazaar: http://bazaar.canonical.com/
 """
 
 try:
@@ -37,6 +39,7 @@ import re
 import shutil
 import StringIO
 import sys
+import types
 
 import libbe
 import base
@@ -50,6 +53,8 @@ def new():
     return Bzr()
 
 class Bzr(base.VCS):
+    """:class:`base.VCS` implementation for Bazaar.
+    """
     name = 'bzr'
     client = None # bzrlib module
 
@@ -63,14 +68,20 @@ class Bzr(base.VCS):
         return bzrlib.__version__
 
     def version_cmp(self, *args):
-        """
-        Compare the installed Bazaar version V_i with another version
-        V_o (given in *args).  Returns
-           1 if V_i > V_o,
-           0 if V_i == V_o, and
-          -1 if V_i < V_o
+        """Compare the installed Bazaar version `V_i` with another version
+        `V_o` (given in `*args`).  Returns
+
+           === ===============
+            1  if `V_i > V_o`
+            0  if `V_i == V_o`
+           -1  if `V_i < V_o`
+           === ===============
+
+        Examples
+        --------
+
         >>> b = Bzr(repo='.')
-        >>> b._vcs_version = lambda : "2.3.1 (release)"
+        >>> b._version = '2.3.1 (release)'
         >>> b.version_cmp(2,3,1)
         0
         >>> b.version_cmp(2,3,2)
@@ -79,25 +90,30 @@ class Bzr(base.VCS):
         1
         >>> b.version_cmp(3)
         -1
-        >>> b._vcs_version = lambda : "2.0.0pre2"
+        >>> b._version = '2.0.0pre2'
         >>> b._parsed_version = None
         >>> b.version_cmp(3)
+        -1
+        >>> b.version_cmp(2,0,1)
         Traceback (most recent call last):
           ...
-        NotImplementedError: Cannot parse "2.0.0pre2" portion of Bazaar version "2.0.0pre2"
-          invalid literal for int() with base 10: '0pre2'
+        NotImplementedError: Cannot parse non-integer portion "0pre2" of Bzr version "2.0.0pre2"
         """
         if not hasattr(self, '_parsed_version') \
                 or self._parsed_version == None:
-            num_part = self._vcs_version().split(' ')[0]
-            try:
-                self._parsed_version = [int(i) for i in num_part.split('.')]
-            except ValueError, e:
+            num_part = self.version().split(' ')[0]
+            self._parsed_version = []
+            for num in num_part.split('.'):
+                try:
+                    self._parsed_version.append(int(num))
+                except ValueError, e:
+                    self._parsed_version.append(num)
+        for current,other in zip(self._parsed_version, args):
+            if type(current) != types.IntType:
                 raise NotImplementedError(
-                    'Cannot parse "%s" portion of Bazaar version "%s"\n  %s'
-                    % (num_part, self._vcs_version(), str(e)))
-        cmps = [cmp(a,b) for a,b in zip(self._parsed_version, args)]
-        for c in cmps:
+                    'Cannot parse non-integer portion "%s" of Bzr version "%s"'
+                    % (current, self.version()))
+            c = cmp(current,other)
             if c != 0:
                 return c
         return 0
@@ -120,12 +136,14 @@ class Bzr(base.VCS):
         cmd = bzrlib.builtins.cmd_root()
         cmd.outf = StringIO.StringIO()
         cmd.run(filename=path)
+        cmd.cleanup_now()
         return cmd.outf.getvalue().rstrip('\n')
 
     def _vcs_init(self, path):
         cmd = bzrlib.builtins.cmd_init()
         cmd.outf = StringIO.StringIO()
         cmd.run(location=path)
+        cmd.cleanup_now()
 
     def _vcs_destroy(self):
         vcs_dir = os.path.join(self.repo, '.bzr')
@@ -137,6 +155,7 @@ class Bzr(base.VCS):
         cmd = bzrlib.builtins.cmd_add()
         cmd.outf = StringIO.StringIO()
         cmd.run(file_list=[path], file_ids_from=self.repo)
+        cmd.cleanup_now()
 
     def _vcs_exists(self, path, revision=None):
         manifest = self._vcs_listdir(
@@ -151,6 +170,7 @@ class Bzr(base.VCS):
         cmd = bzrlib.builtins.cmd_remove()
         cmd.outf = StringIO.StringIO()
         cmd.run(file_list=[path], file_deletion_strategy='force')
+        cmd.cleanup_now()
 
     def _vcs_update(self, path):
         pass
@@ -172,7 +192,7 @@ class Bzr(base.VCS):
         revision = self._parse_revision_string(revision)
         cmd = bzrlib.builtins.cmd_cat()
         cmd.outf = StringIO.StringIO()
-        if self.version_cmp(1,6,0) == -1:
+        if self.version_cmp(1,6,0) < 0:
             # old bzrlib cmd_cat uses sys.stdout not self.outf for output.
             stdout = sys.stdout
             sys.stdout = cmd.outf
@@ -183,9 +203,10 @@ class Bzr(base.VCS):
                 raise base.InvalidPath(path, root=self.repo, revision=revision)
             raise
         finally:
-            if self.version_cmp(2,0,0) == -1:
+            if self.version_cmp(2,0,0) < 0:
                 cmd.outf = sys.stdout
                 sys.stdout = stdout
+            cmd.cleanup_now()
         return cmd.outf.getvalue()
 
     def _vcs_path(self, id, revision):
@@ -210,15 +231,22 @@ class Bzr(base.VCS):
         try:
             if self.version_cmp(2,0,0) >= 0:
                 cmd.run(revision=revision, path=path, recursive=recursive)
-            else: # Pre-2.0 Bazaar
+            else:
+                # Pre-2.0 Bazaar (non_recursive)
+                # + working around broken non_recursive+path implementation
+                #   (https://bugs.launchpad.net/bzr/+bug/158690)
                 cmd.run(revision=revision, path=path,
-                        non_recursive=not recursive)
+                        non_recursive=False)
         except bzrlib.errors.BzrCommandError, e:
             if 'not present in revision' in str(e):
                 raise base.InvalidPath(path, root=self.repo, revision=revision)
             raise
+        finally:
+            cmd.cleanup_now()
         children = cmd.outf.getvalue().rstrip('\n').splitlines()
         children = [self._u_rel_path(c, path) for c in children]
+        if self.version_cmp(2,0,0) < 0 and recursive == False:
+            children = [c for c in children if os.path.sep not in c]
         return children
 
     def _vcs_commit(self, commitfile, allow_empty=False):
@@ -236,12 +264,14 @@ class Bzr(base.VCS):
             raise
         finally:
             os.chdir(cwd)
+            cmd.cleanup_now()
         return self._vcs_revision_id(-1)
 
     def _vcs_revision_id(self, index):
         cmd = bzrlib.builtins.cmd_revno()
         cmd.outf = StringIO.StringIO()
         cmd.run(location=self.repo)
+        cmd.cleanup_now()
         current_revision = int(cmd.outf.getvalue())
         if index > current_revision or index < -current_revision:
             return None
@@ -260,55 +290,59 @@ class Bzr(base.VCS):
             status = cmd.run(revision=revision, file_list=[self.repo])
         finally:
             sys.stdout = stdout
+            cmd.cleanup_now()
         assert status in [0,1], "Invalid status %d" % status
         return cmd.outf.getvalue()
 
     def _parse_diff(self, diff_text):
-        """
-        Example diff text:
-                
-        === modified file 'dir/changed'
-        --- dir/changed        2010-01-16 01:54:53 +0000
-        +++ dir/changed        2010-01-16 01:54:54 +0000
-        @@ -1,3 +1,3 @@
-         hi
-        -there
-        +everyone and
-         joe
-        
-        === removed file 'dir/deleted'
-        --- dir/deleted        2010-01-16 01:54:53 +0000
-        +++ dir/deleted        1970-01-01 00:00:00 +0000
-        @@ -1,3 +0,0 @@
-        -in
-        -the
-        -beginning
-        
-        === removed file 'dir/moved'
-        --- dir/moved  2010-01-16 01:54:53 +0000
-        +++ dir/moved  1970-01-01 00:00:00 +0000
-        @@ -1,4 +0,0 @@
-        -the
-        -ants
-        -go
-        -marching
-        
-        === added file 'dir/moved2'
-        --- dir/moved2 1970-01-01 00:00:00 +0000
-        +++ dir/moved2 2010-01-16 01:54:34 +0000
-        @@ -0,0 +1,4 @@
-        +the
-        +ants
-        +go
-        +marching
-        
-        === added file 'dir/new'
-        --- dir/new    1970-01-01 00:00:00 +0000
-        +++ dir/new    2010-01-16 01:54:54 +0000
-        @@ -0,0 +1,2 @@
-        +hello
-        +world
-        
+        """_parse_diff(diff_text) -> (new,modified,removed)
+
+        `new`, `modified`, and `removed` are lists of files.
+
+        Example diff text::
+
+          === modified file 'dir/changed'
+          --- dir/changed      2010-01-16 01:54:53 +0000
+          +++ dir/changed      2010-01-16 01:54:54 +0000
+          @@ -1,3 +1,3 @@
+           hi
+          -there
+          +everyone and
+           joe
+          
+          === removed file 'dir/deleted'
+          --- dir/deleted      2010-01-16 01:54:53 +0000
+          +++ dir/deleted      1970-01-01 00:00:00 +0000
+          @@ -1,3 +0,0 @@
+          -in
+          -the
+          -beginning
+          
+          === removed file 'dir/moved'
+          --- dir/moved        2010-01-16 01:54:53 +0000
+          +++ dir/moved        1970-01-01 00:00:00 +0000
+          @@ -1,4 +0,0 @@
+          -the
+          -ants
+          -go
+          -marching
+          
+          === added file 'dir/moved2'
+          --- dir/moved2       1970-01-01 00:00:00 +0000
+          +++ dir/moved2       2010-01-16 01:54:34 +0000
+          @@ -0,0 +1,4 @@
+          +the
+          +ants
+          +go
+          +marching
+          
+          === added file 'dir/new'
+          --- dir/new  1970-01-01 00:00:00 +0000
+          +++ dir/new  2010-01-16 01:54:54 +0000
+          @@ -0,0 +1,2 @@
+          +hello
+          +world
+          
         """
         new = []
         modified = []