Hi,

> > > Perhaps the one thing that might not be obvious is the explanation of
> > > ABSOLUTE_LOCATION. Someone reading it won't necessarily know what
> > > self.location or "the base directory" is. But now I'm being really
> > > difficult :-)
> > 
> > I would see three possible solutions here:
> > 
> > 1) improving the comments
> > 
> > 2) providing "self.location_relative" and "self.location_absolute" 
> > 
> > 3) making "self.location" always relative to the base directory of the local
> > working copy - but since all RCS except for "git" use absolute paths, this
> > would bloat the code a slightly bit
> > 
> > any opinions?
> > 
> 
> I'm happy with 1 - but even 0 (doing nothing) wouldn't bother me
> much :-)

after some thinking, I decided to take choose the second one. It should be
easier to understand for others and it makes ABSOLUTE_LOCATION obsolete.

The new patch (it includes the former one) additionally fixes the
following issues:
 - the darcs implementation (as discussed with Miklos)
 - the docstring improvement (as suggested by Friedel)
 - raise NotImplementedError for missing functions (also suggested by Friedel)

The basic tests with "getcleanfile" work for all supported systems.

Any opinions?

regards,
Lars


Index: versioncontrol.py
===================================================================
--- versioncontrol.py	(Revision 6410)
+++ versioncontrol.py	(Arbeitskopie)
@@ -21,8 +21,8 @@
 
 """This module manages interaction with version control systems.
 
-To implement support for a new version control system, override the class
-GenericVersionControlSystem. 
+To implement support for a new version control system, inherit the class
+GenericRevisionControlSystem. 
 
 TODO:
     * move to the translate toolkit and split into different files
@@ -31,14 +31,14 @@
 import re
 import os
 
-# The subprocess module allows to use cross-platform command execution without 
-# using the shell (which increases security).
-# p = subprocess.Popen(shell=False, close_fds=True, stdin=subprocess.PIPE,
-#       stdout=subprocess.PIPE, stderr=subprocess.PIPE, args = command)
-# This is only available since python 2.4, so we won't rely on it yet.
+# use either 'popen2' or 'subprocess' for command execution
 try:
+    # available for python >= 2.4
     import subprocess
 
+    # The subprocess module allows to use cross-platform command execution
+    # without using the shell (increases security).
+
     def pipe(command):
         """Runs a command (array of program name and arguments) and returns the
         exitcode, the output and the error as a tuple.
@@ -76,36 +76,155 @@
     return re.sub(r'(\W)', r'\\\1', path)
 
 
-class GenericVersionControlSystem:
-    """The super class for all version control classes."""
+class GenericRevisionControlSystem:
+    """The super class for all version control classes.
 
-    # as long as noone overrides this, the test below always succeeds
-    MARKER_DIR = os.path.curdir
+    Always inherit this class, if you want to implement yet another RC
+    interface.
 
+    At least the two attributes "RCS_METADIR" and "SCAN_PARENTS" must be 
+    overriden by any implementation, that derives from this class.
+
+    By default, all implementations can rely on the following attributes:
+        root_dir: the parent of the metadata directory of the working copy
+        location_abs: the absolute path of the RCS object
+        location_rel: the relative path of the RCS object based on 'root_dir'
+    """
+
+    RCS_METADIR = None
+    """ the name of the metadata directory of the RCS
+
+    e.g.: for Subversion -> ".svn"
+    """
+
+    SCAN_PARENTS = None
+    """whether to check the parent directories for the metadata directory of
+    the RCS working copy
+    
+    some revision control systems store their metadata directory only
+    in the base of the working copy (e.g. bzr, GIT and Darcs)
+    use "True" for these RCS
+
+    other RCS store a metadata directory in every single directory of
+    the working copy (e.g. Subversion and CVS)
+    use "False" for these RCS
+    """
+
+
     def __init__(self, location):
-        """Default version control checker: test if self.MARKER_DIR exists.
+        """find the relevant information about this RCS object
         
-        Most version control systems depend on the existence of a specific 
-        directory thus you will most likely not need to touch this check - just 
-        call it. The IOError exception indicates that the specified file is not 
-        controlled by the given version control system.
+        The IOError exception indicates that the specified object (file or
+        directory) is not controlled by the given version control system.
         """
-        parent_dir = os.path.dirname(os.path.abspath(location))
-        if not os.path.isdir(os.path.join(parent_dir, self.MARKER_DIR)):
-            raise IOError("Could not find version control information: %s" % location)
+        # check if the implementation looks ok - otherwise raise IOError
+        self._self_check()
+        # search for the repository information
+        result = self._find_rcs_directory(location)
+        if result is None:
+            raise IOError("Could not find revision control information: %s" \
+                    % location)
+        else:
+            self.root_dir, self.location_abs, self.location_rel = result
 
+    def _find_rcs_directory(self, rcs_obj):
+        """try to find the metadata directory of the RCS
 
-class CVS(GenericVersionControlSystem):
-    """Class to manage items under revision control of CVS."""
+        returns a tuple:
+            the absolute path of the directory, that contains the metadata directory
+            the absolute path of the RCS object
+            the relative path of the RCS object based on the directory above
+        """
+        rcs_obj_dir = os.path.dirname(os.path.abspath(rcs_obj))
+        if os.path.isdir(os.path.join(rcs_obj_dir, self.RCS_METADIR)):
+            # is there a metadir next to the rcs_obj?
+            # (for Subversion, CVS, ...)
+            location_abs = os.path.abspath(rcs_obj)
+            location_rel = os.path.basename(location_abs)
+            return (rcs_obj_dir, location_abs, location_rel)
+        elif self.SCAN_PARENTS:
+            # scan for the metadir in parent directories
+            # (for bzr, GIT, Darcs, ...)
+            return self._find_rcs_in_parent_directories(rcs_obj)
+        else:
+            # no RCS metadata found
+            return None
+    
+    def _find_rcs_in_parent_directories(self, rcs_obj):
+        """try to find the metadata directory in all parent directories
+        """
+        # first: resolve possible symlinks
+        current_dir = os.path.dirname(os.path.realpath(rcs_obj))
+        # prevent infite loops
+        max_depth = 64
+        # stop as soon as we find the metadata directory
+        while not os.path.isdir(os.path.join(current_dir, self.RCS_METADIR)):
+            if os.path.dirname(current_dir) == current_dir:
+                # we reached the root directory - stop
+                return None
+            if max_depth <= 0:
+                # some kind of dead loop or a _very_ deep directory structure
+                return None
+            # go to the next higher level
+            current_dir = os.path.dirname(current_dir)
+        # the loop was finished successfully
+        # i.e.: we found the metadata directory
+        rcs_dir = current_dir
+        location_abs = os.path.realpath(rcs_obj)
+        # strip the base directory from the path of the rcs_obj
+        basedir = rcs_dir + os.path.sep
+        if location_abs.startswith(basedir):
+            # remove the base directory (including the trailing slash)
+            location_rel = location_abs.replace(basedir, "", 1)
+            # successfully finished
+            return (rcs_dir, location_abs, location_rel)
+        else:
+            # this should never happen
+            return None
+        
+    def _self_check(self):
+        """Check if all necessary attributes are defined
 
-    MARKER_DIR = "CVS"
+        Useful to make sure, that a new implementation does not forget
+        something like "RCS_METADIR"
+        """
+        if self.RCS_METADIR is None:
+            raise IOError("Incomplete RCS interface implementation: " \
+                    + "self.RCS_METADIR is None")
+        if self.SCAN_PARENTS is None:
+            raise IOError("Incomplete RCS interface implementation: " \
+                    + "self.SCAN_PARENTS is None")
+        # we do not check for implemented functions - they raise
+        # NotImplementedError exceptions anyway
+        return True
+                    
+    def getcleanfile(self, revision=None):
+        """Dummy to be overridden by real implementations
+        """
+        raise NotImplementedError("Incomplete RCS interface implementation:" \
+                + " 'getcleanfile' is missing")
 
-    def __init__(self, location):
-        GenericVersionControlSystem.__init__(self, location)
-        self.cvsdir = os.path.join(os.path.dirname(os.path.abspath(location)),
-                self.MARKER_DIR)
-        self.location = os.path.abspath(location)
 
+    def commit(self, revision=None):
+        """Dummy to be overridden by real implementations
+        """
+        raise NotImplementedError("Incomplete RCS interface implementation:" \
+                + " 'commit' is missing")
+
+
+    def update(self, revision=None):
+        """Dummy to be overridden by real implementations
+        """
+        raise NotImplementedError("Incomplete RCS interface implementation:" \
+                + " 'update' is missing")
+
+
+class CVS(GenericRevisionControlSystem):
+    """Class to manage items under revision control of CVS."""
+
+    RCS_METADIR = "CVS"
+    SCAN_PARENTS = False
+
     def _readfile(self, cvsroot, path, revision=None):
         """
         Read a single file from the CVS repository without checking out a full 
@@ -115,11 +234,11 @@
         @param path: path to the file relative to cvs root
         @param revision: revision or tag to get (retrieves from HEAD if None)
         """
+        command = ["cvs", "-d", cvsroot, "-Q", "co", "-p"]
         if revision:
-            command = ["cvs", "-d", cvsroot, "-Q", "co", "-p" "-r",
-                    revision, path]
-        else:
-            command = ["cvs", "-d", cvsroot, "-Q", "co", "-p", path]
+            command.extend(["-r", revision])
+        # the path is the last argument
+        command.append(path)
         exitcode, output, error = pipe(command)
         if exitcode != 0:
             raise IOError("[CVS] Could not read '%s' from '%s': %s / %s" % \
@@ -128,11 +247,11 @@
 
     def getcleanfile(self, revision=None):
         """Get the content of the file for the given revision"""
-        parentdir = os.path.dirname(self.location)
+        parentdir = os.path.dirname(self.location_abs)
         cvsdir = os.path.join(parentdir, "CVS")
         cvsroot = open(os.path.join(cvsdir, "Root"), "r").read().strip()
         cvspath = open(os.path.join(cvsdir, "Repository"), "r").read().strip()
-        cvsfilename = os.path.join(cvspath, os.path.basename(self.location))
+        cvsfilename = os.path.join(cvspath, os.path.basename(self.location_abs))
         if revision is None:
             cvsentries = open(os.path.join(cvsdir, "Entries"), "r").readlines()
             revision = self._getcvstag(cvsentries)
@@ -143,8 +262,8 @@
 
     def update(self, revision=None):
         """Does a clean update of the given path"""
-        working_dir = os.path.dirname(self.location)
-        filename = os.path.basename(self.location)
+        working_dir = os.path.dirname(self.location_abs)
+        filename = os.path.basename(self.location_abs)
         filename_backup = filename + os.path.extsep + "bak"
         original_dir = os.getcwd()
         if working_dir:
@@ -171,14 +290,15 @@
                 pass
             raise IOError("[CVS] could not move the file '%s' to '%s': %s" % \
                     (filename, filename_backup, error))
+        command = ["cvs", "-Q", "update", "-C"]
         if revision:
-            command = ["cvs", "-Q", "update", "-C", "-r", revision, filename]
-        else:
-            command = ["cvs", "-Q", "update", "-C", filename]
+            command.extend(["-r", revision])
+        # the filename is the last argument
+        command.append(filename)
         exitcode, output, error = pipe(command)
         # restore backup in case of an error - remove backup for success
         try:
-            if error:
+            if exitcode != 0:
                 os.rename(filename_backup, filename)
             else:
                 os.remove(filename_backup)
@@ -197,8 +317,8 @@
 
     def commit(self, message=None):
         """Commits the file and supplies the given commit message if present"""
-        working_dir = os.path.dirname(self.location)
-        filename = os.path.basename(self.location)
+        working_dir = os.path.dirname(self.location_abs)
+        filename = os.path.basename(self.location_abs)
         original_dir = os.getcwd()
         if working_dir:
             try:
@@ -214,10 +334,11 @@
             except OSError, error:
                 raise IOError("[CVS] could not change to directory (%s): %s" \
                         % (working_dir, error))
+        command = ["cvs", "-Q", "commit"]
         if message:
-            command = ["cvs", "-Q", "commit", "-m", message, filename]
-        elif message is None:
-            command = ["cvs", "-Q", "commit", filename]
+            command.extend(["-m", message])
+        # the filename is the last argument
+        command.append(filename)
         exitcode, output, error = pipe(command)
         # always go back to the original directory
         try:
@@ -234,7 +355,7 @@
         """returns the revision number the file was checked out with by looking
         in the lines of cvsentries
         """
-        filename = os.path.basename(self.location)
+        filename = os.path.basename(self.location_abs)
         for cvsentry in cvsentries:
             # an entries line looks like the following:
             #  /README.TXT/1.19/Sun Dec 16 06:00:12 2001//
@@ -249,7 +370,7 @@
         """Returns the sticky tag the file was checked out with by looking in 
         the lines of cvsentries.
         """
-        filename = os.path.basename(self.location)
+        filename = os.path.basename(self.location_abs)
         for cvsentry in cvsentries:
             # an entries line looks like the following:
             #  /README.TXT/1.19/Sun Dec 16 06:00:12 2001//
@@ -262,42 +383,40 @@
         return None
 
 
-class SVN(GenericVersionControlSystem):
+class SVN(GenericRevisionControlSystem):
     """Class to manage items under revision control of Subversion."""
 
-    MARKER_DIR = ".svn"
+    RCS_METADIR = ".svn"
+    SCAN_PARENTS = False
 
-    def __init__(self, location):
-        GenericVersionControlSystem.__init__(self, location)
-        self.svndir = os.path.join(os.path.dirname(os.path.abspath(location)),
-                self.MARKER_DIR)
-        self.location = os.path.abspath(location)
-
     def update(self, revision=None):
         """update the working copy - remove local modifications if necessary"""
         # revert the local copy (remove local changes)
-        command = ["svn", "revert", self.location]
+        command = ["svn", "revert", self.location_abs]
         exitcode, output_revert, error = pipe(command)
-        # any error messages?
-        if error:
-            raise IOError("[SVN] Subversion error running '%s': %s" % (command, error))
+        # any errors?
+        if exitcode != 0:
+            raise IOError("[SVN] Subversion error running '%s': %s" \
+                    % (command, error))
         # update the working copy to the given revision
-        if revision is None:
-            command = ["svn", "update", self.location]
-        else:
-            command = ["svn", "update", "-r", revision, self.location]
+        command = ["svn", "update"]
+        if not revision is None:
+            command.extend(["-r", revision])
+        # the filename is the last argument
+        command.append(self.location_abs)
         exitcode, output_update, error = pipe(command)
         if exitcode != 0:
-            raise IOError("[SVN] Subversion error running '%s': %s" % (command, error))
+            raise IOError("[SVN] Subversion error running '%s': %s" \
+                    % (command, error))
         return output_revert + output_update
 
     def commit(self, message=None):
         """commit the file and return the given message if present"""
-        if message is None:
-            command = ["svn", "-q", "--non-interactive", "commit", self.location]
-        else:
-            command = ["svn", "-q", "--non-interactive", "commit", "-m",
-                    message, self.location]
+        command = ["svn", "-q", "--non-interactive", "commit"]
+        if message:
+            command.extend(["-m", message])
+        # the location is the last argument
+        command.append(self.location_abs)
         exitcode, output, error = pipe(command)
         if exitcode != 0:
             raise IOError("[SVN] Error running SVN command '%s': %s" % (command, error))
@@ -305,79 +424,36 @@
     
     def getcleanfile(self, revision=None):
         """return the content of the 'head' revision of the file"""
-        if revision is None:
-            command = ["svn", "cat", self.location]
-        else:
-            command = ["svn", "cat", "-r", revision, self.location]
+        command = ["svn", "cat"]
+        if not revision is None:
+            command.extend(["-r", revision])
+        # the filename is the last argument
+        command.append(self.location_abs)
         exitcode, output, error = pipe(command)
         if exitcode != 0:
             raise IOError("[SVN] Subversion error running '%s': %s" % (command, error))
         return output
 
 
-class DARCS(GenericVersionControlSystem):
+class DARCS(GenericRevisionControlSystem):
     """Class to manage items under revision control of darcs."""
 
-    # This assumes that the whole PO directory is stored in darcs so we need to 
-    # reach the _darcs dir from po/project/language. That results in this 
-    # relative path
-    MARKER_DIR = "_darcs"
+    RCS_METADIR = "_darcs"
+    SCAN_PARENTS = True
     
-    def __init__(self, location):
-        self.location = None
-        try:
-            # this works only, if the po file is in the root of the repository
-            GenericVersionControlSystem.__init__(self, location)
-            self.darcsdir = os.path.join(os.path.dirname(os.path.abspath(location)),
-                    self.MARKER_DIR)
-            self.location = os.path.abspath(location)
-            # we finished successfully
-        except IOError, err_msg:
-            # the following code scans all directories above the po file for the
-            # common '_darcs' directory
-            # first: resolve possible symlinks
-            current_dir = os.path.realpath(os.path.dirname(location))
-            # avoid any dead loops (could this happen?)
-            max_depth = 64
-            while not os.path.isdir(os.path.join(current_dir, self.MARKER_DIR)):
-                if os.path.dirname(current_dir) == current_dir:
-                    # we reached the root directory - stop
-                    break
-                if max_depth <= 0:
-                    # some kind of dead loop or a _very_ deep directory structure
-                    break
-                # go to the next higher level
-                current_dir = os.path.dirname(current_dir)
-            else:
-                # we found the MARKER_DIR
-                self.darcsdir = current_dir
-                # retrieve the relative path of the po file based on self.darcsdir
-                realpath_pofile = os.path.realpath(location)
-                basedir = self.darcsdir + os.path.sep
-                if realpath_pofile.startswith(basedir):
-                    # remove the base directory (including the trailing slash)
-                    self.location = realpath_pofile.replace(basedir, "", 1)
-                    # successfully finished
-                else:
-                    # this should never happen
-                    raise IOError("[Darcs] unexpected path names: '%s' and '%s'" \
-                            % (self.darcsdir, basedir))
-            if self.location is None:
-                # we did not find a '_darcs' directory
-                raise IOError(err_msg)
-                    
     def update(self, revision=None):
         """Does a clean update of the given path
+
         @param: revision: ignored for darcs
         """
-        # TODO: check if 'revert' and 'pull' work without specifying '--repodir'
         # revert local changes (avoids conflicts)
-        command = ["darcs", "revert", "-a", self.location]
+        command = ["darcs", "revert", "--repodir", self.root_dir, 
+                "-a", self.location_rel]
         exitcode, output_revert, error = pipe(command)
         if exitcode != 0:
             raise IOError("[Darcs] error running '%s': %s" % (command, error))
         # pull new patches
-        command = ["darcs", "pull", "-a"]
+        command = ["darcs", "pull", "--repodir", self.root_dir, "-a"]
         exitcode, output_pull, error = pipe(command)
         if exitcode != 0:
             raise IOError("[Darcs] error running '%s': %s" % (command, error))
@@ -388,14 +464,14 @@
         if message is None:
             message = ""
         # set change message
-        command = ["darcs", "record", "-a", "--skip-long-comment", "-m",
-                message, self.location]
+        command = ["darcs", "record", "-a", "--repodir", self.root_dir,
+                "--skip-long-comment", "-m", message, self.location_rel]
         exitcode, output_record, error = pipe(command)
         if exitcode != 0:
             raise IOError("[Darcs] Error running darcs command '%s': %s" \
                     % (command, error))
         # push changes
-        command = ["darcs", "push", "-a"]
+        command = ["darcs", "push", "-a", "--repodir", self.root_dir]
         exitcode, output_push, error = pipe(command)
         if exitcode != 0:
             raise IOError("[Darcs] Error running darcs command '%s': %s" \
@@ -404,9 +480,11 @@
 
     def getcleanfile(self, revision=None):
         """Get a clean version of a file from the darcs repository
+
         @param: revision: ignored for darcs
         """
-        filename = os.path.join('_darcs', 'pristine', self.location)
+        filename = os.path.join(self.root_dir, self.RCS_METADIR, 'pristine',
+                self.location_rel)
         try:
             darcs_file = open(filename)
             output = darcs_file.read()
@@ -416,66 +494,27 @@
                     (filename, error))
         return output
 
-class GIT(GenericVersionControlSystem):
+class GIT(GenericRevisionControlSystem):
     """Class to manage items under revision control of git."""
 
-    # This assumes that the whole PO directory is stored in git so we need to 
-    # reach the .git dir from po/project/language. That results in this 
-    # relative path
-    MARKER_DIR = ".git"
+    RCS_METADIR = ".git"
+    SCAN_PARENTS = True
+
+    def _get_git_dir(self):
+        """git requires the git metadata directory for every operation
+        """
+        return os.path.join(self.root_dir, self.RCS_METADIR)
     
-    def __init__(self, location):
-        self.location = None
-        try:
-            # this works only, if the po file is in the root of the repository
-            GenericVersionControlSystem.__init__(self, location)
-            self.gitdir = os.path.join(os.path.dirname(os.path.abspath(location)),
-                    self.MARKER_DIR)
-            self.location = os.path.abspath(location)
-            # we finished successfully
-        except IOError, err_msg:
-            # the following code scans all directories above the po file for the
-            # common '.git' directory
-            # first: resolve possible symlinks
-            current_dir = os.path.realpath(os.path.dirname(location))
-            # avoid any dead loops (could this happen?)
-            max_depth = 64
-            while not os.path.isdir(os.path.join(current_dir, self.MARKER_DIR)):
-                if os.path.dirname(current_dir) == current_dir:
-                    # we reached the root directory - stop
-                    break
-                if max_depth <= 0:
-                    # some kind of dead loop or a _very_ deep directory structure
-                    break
-                # go to the next higher level
-                current_dir = os.path.dirname(current_dir)
-            else:
-                # we found the MARKER_DIR
-                self.gitdir = current_dir
-                # retrieve the relative path of the po file based on self.gitdir
-                realpath_pofile = os.path.realpath(location)
-                basedir = self.gitdir + os.path.sep
-                if realpath_pofile.startswith(basedir):
-                    # remove the base directory (including the trailing slash)
-                    self.location = realpath_pofile.replace(basedir, "", 1)
-                    # successfully finished
-                else:
-                    # this should never happen
-                    raise IOError("[GIT] unexpected path names: '%s' and '%s'" \
-                            % (self.gitdir, basedir))
-            if self.location is None:
-                # we did not find a '.git' directory
-                raise IOError(err_msg)
-                    
     def update(self, revision=None):
         """Does a clean update of the given path"""
         # git checkout
-        command = ["git", "checkout", self.location]
+        command = ["git", "--git-dir", self._get_git_dir(),
+                "checkout", self.location_rel]
         exitcode, output_checkout, error = pipe(command)
         if exitcode != 0:
             raise IOError("[GIT] checkout failed (%s): %s" % (command, error))
         # pull changes
-        command = ["git", "pull"]
+        command = ["git", "--git-dir", self._get_git_dir(), "pull"]
         exitcode, output_pull, error = pipe(command)
         if exitcode != 0:
             raise IOError("[GIT] pull failed (%s): %s" % (command, error))
@@ -484,141 +523,89 @@
     def commit(self, message=None):
         """Commits the file and supplies the given commit message if present"""
         # add the file
-        command = ["git", "add", self.location]
+        command = ["git", "--git-dir", self._get_git_dir(),
+                "add", self.location_rel]
         exitcode, output_add, error = pipe(command)
         if exitcode != 0:
-            raise IOError("[GIT] add of '%s' failed: %s" % (self.location, error))
+            raise IOError("[GIT] add of ('%s', '%s') failed: %s" \
+                    % (self.root_dir, self.location_rel, error))
         # commit file
-        if message is None:
-            command = ["git", "commit"]
-        else:
-            command = ["git", "commit", "-m", message]
+        command = ["git", "--git-dir", self._get_git_dir(), "commit"]
+        if message:
+            command.extend(["-m", message])
         exitcode, output_commit, error = pipe(command)
         if exitcode != 0:
-            raise IOError("[GIT] commit of '%s' failed: %s" % (self.location, error))
+            raise IOError("[GIT] commit of ('%s', '%s') failed: %s" \
+                    % (self.root_dir, self.location_rel, error))
         # push changes
-        command = ["git", "push"]
+        command = ["git", "--git-dir", self._get_git_dir(), "push"]
         exitcode, output_push, error = pipe(command)
         if exitcode != 0:
-            raise IOError("[GIT] push of '%s' failed: %s" % (self.location, error))
+            raise IOError("[GIT] push of ('%s', '%s') failed: %s" \
+                    % (self.root_dir, self.location_rel, error))
         return output_add + output_commit + output_push
 
     def getcleanfile(self, revision=None):
         """Get a clean version of a file from the git repository"""
-        # get ls-tree HEAD
-        command = ["git", "ls-tree", "HEAD", self.location]
-        exitcode, output_ls, error = pipe(command)
+        # run git-show
+        command = ["git", "--git-dir", self._get_git_dir(), "show",
+                "HEAD:%s" % self.location_rel]
+        exitcode, output, error = pipe(command)
         if exitcode != 0:
-            raise IOError("[GIT] ls-tree failed for '%s': %s" \
-                    % self.location, error)
-        # determine the id
-        match = re.search(" ([a-f0-9]{40})\t", output_ls)
-        if not match:
-            raise IOError("[GIT] failed to get git id for '%s'" % self.location)
-        # remove whitespace around
-        git_id = match.groups()[0].strip()
-        # run cat-file
-        command = ["git", "cat-file", "blob", git_id]
-        exitcode, output_cat, error = pipe(command)
-        if exitcode != 0:
-            raise IOError("[GIT] cat-file failed for ('%s', %s): %s" \
-                    % (self.location, git_id, error))
-        return output_ls + output_cat
+            raise IOError("[GIT] 'show' failed for ('%s', %s): %s" \
+                    % (self.root_dir, self.location_rel, error))
+        return output
 
-class BZR(GenericVersionControlSystem):
+class BZR(GenericRevisionControlSystem):
     """Class to manage items under revision control of bzr."""
 
-    # This assumes that the whole PO directory is stored in bzr so we need to 
-    # reach the .git dir from po/project/language. That results in this 
-    # relative path
-    MARKER_DIR = ".bzr"
+    RCS_METADIR = ".bzr"
+    SCAN_PARENTS = True
     
-    def __init__(self, location):
-        self.location = None
-        try:
-            # this works only, if the po file is in the root of the repository
-            GenericVersionControlSystem.__init__(self, location)
-            self.bzrdir = os.path.join(os.path.dirname(os.path.abspath(location)),
-                    self.MARKER_DIR)
-            self.location = os.path.abspath(location)
-            # we finished successfully
-        except IOError, err_msg:
-            # the following code scans all directories above the po file for the
-            # common '.bzr' directory
-            # first: resolve possible symlinks
-            current_dir = os.path.realpath(os.path.dirname(location))
-            # avoid any dead loops (could this happen?)
-            max_depth = 64
-            while not os.path.isdir(os.path.join(current_dir, self.MARKER_DIR)):
-                if os.path.dirname(current_dir) == current_dir:
-                    # we reached the root directory - stop
-                    break
-                if max_depth <= 0:
-                    # some kind of dead loop or a _very_ deep directory structure
-                    break
-                # go to the next higher level
-                current_dir = os.path.dirname(current_dir)
-            else:
-                # we found the MARKER_DIR
-                self.bzrdir = current_dir
-                # retrieve the relative path of the po file based on self.bzrdir
-                realpath_pofile = os.path.realpath(location)
-                basedir = self.bzrdir + os.path.sep
-                if realpath_pofile.startswith(basedir):
-                    # remove the base directory (including the trailing slash)
-                    self.location = realpath_pofile.replace(basedir, "", 1)
-                    # successfully finished
-                else:
-                    # this should never happen
-                    raise IOError("[BZR] unexpected path names: '%s' and '%s'" \
-                            % (self.bzrdir, basedir))
-            if self.location is None:
-                # we did not find a '.bzr' directory
-                raise IOError(err_msg)
-                    
     def update(self, revision=None):
         """Does a clean update of the given path"""
-        # bazaar revert
-        command = ["bzr", "revert", self.location]
+        # bzr revert
+        command = ["bzr", "revert", self.location_abs]
         exitcode, output_revert, error = pipe(command)
         if exitcode != 0:
             raise IOError("[BZR] revert of '%s' failed: %s" \
-                    % (self.location, error))
-        # bazaar pull
+                    % (self.location_abs, error))
+        # bzr pull
         command = ["bzr", "pull"]
         exitcode, output_pull, error = pipe(command)
         if exitcode != 0:
             raise IOError("[BZR] pull of '%s' failed: %s" \
-                    % (self.location, error))
+                    % (self.location_abs, error))
         return output_revert + output_pull
 
     def commit(self, message=None):
         """Commits the file and supplies the given commit message if present"""
         # bzr commit
-        if message is None:
-            command = ["bzr", "commit", self.location]
-        else:
-            command = ["bzr", "commit", "-m", message, self.location]
+        command = ["bzw", "commit"]
+        if message:
+            command.extend(["-m", message])
+        # the filename is the last argument
+        command.append(self.location_abs)
         exitcode, output_commit, error = pipe(command)
         if exitcode != 0:
             raise IOError("[BZR] commit of '%s' failed: %s" \
-                    % (self.location, error))
+                    % (self.location_abs, error))
         # bzr push
         command = ["bzr", "push"]
         exitcode, output_push, error = pipe(command)
         if exitcode != 0:
             raise IOError("[BZR] push of '%s' failed: %s" \
-                    % (self.location, error))
+                    % (self.location_abs, error))
         return output_commit + output_push
 
     def getcleanfile(self, revision=None):
         """Get a clean version of a file from the bzr repository"""
         # bzr cat
-        command = ["bzr", "cat", self.location]
+        command = ["bzr", "cat", self.location_abs]
         exitcode, output, error = pipe(command)
         if exitcode != 0:
             raise IOError("[BZR] cat failed for '%s': %s" \
-                    % (self.location, error))
+                    % (self.location_abs, error))
         return output
 
 # which versioning systems are supported by default?
@@ -666,5 +653,6 @@
     filenames = sys.argv[1:]
     for filename in filenames:
         contents = getcleanfile(filename)
+        sys.stdout.write("\n\n******** %s ********\n\n" % filename)
         sys.stdout.write(contents)
 
-------------------------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc.
Still grepping through log files to find problems?  Stop.
Now Search log events and configuration files using AJAX and a browser.
Download your FREE copy of Splunk now >>  http://get.splunk.com/
_______________________________________________
Translate-pootle mailing list
Translate-pootle@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/translate-pootle

Reply via email to