Hi,
currently the versioncontrol.py file contains some duplicate code.
The attached patch improves this by moving the code to the base class of
the revision control interfaces.
Additionally the following issues are fixed:
- add the "--git-dir" parameter to every git call to allow the git repositories
to be in locations different from the current directory
- fix the relative path handling of bzr and darcs for the same reason
I tested the patch with pootle and subversion in a linux system.
Additionally I tested the operation "getcleanfile" for all supported revision
control systems.
I would be glad to receive some feedback regarding the way I implemented the
functions of the base class.
Is its interface easily understandable (e.g. for implementing other
RCS connectors)?
Is it generic enough for other revision control systems, that are not yet
implemented?
I am open to suggestion for improvements!
From my point of view, this would be the last necessary patch, before moving
the revision control interface to the translate toolkit, as it was suggested
before.
regards,
Lars
Index: versioncontrol.py
===================================================================
--- versioncontrol.py (Revision 6410)
+++ versioncontrol.py (Arbeitskopie)
@@ -79,33 +79,104 @@
class GenericVersionControlSystem:
"""The super class for all version control classes."""
- # as long as noone overrides this, the test below always succeeds
- MARKER_DIR = os.path.curdir
+ # the following three attributes _must_ be overriden by any implementation,
+ # that derives from GenericVersionControlSystem
+ # the name of the metadata directory of the RCS
+ # e.g.: Subversion -> ".svn"
+ RCS_METADIR = None
+
+ # 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
+ SCAN_PARENTS = None
+
+ # should 'self.location' be absolute or relative to the base directory
+ # of the working copy? "True" or "False" are possible.
+ ABSOLUTE_LOCATION = None
+
+
+ def _find_rcs_directory(self, rcs_obj):
+ """try to find the metadata directory of the RCS
+ """
+ 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, ...)
+ if self.ABSOLUTE_LOCATION:
+ return (rcs_obj_dir, os.path.abspath(rcs_obj))
+ else:
+ return (rcs_obj_dir, os.path.basename(os.path.abspath(rcs_obj)))
+ 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
+ if self.ABSOLUTE_LOCATION:
+ return (current_dir, os.path.realpath(rcs_obj))
+ else:
+ # remove the base directory from the path of the rcs_obj
+ rcs_dir = current_dir
+ realpath_rcs_obj = os.path.realpath(rcs_obj)
+ basedir = rcs_dir + os.path.sep
+ if realpath_rcs_obj.startswith(basedir):
+ # remove the base directory (including the trailing slash)
+ rel_location = realpath_rcs_obj.replace(basedir, "", 1)
+ # successfully finished
+ return (rcs_dir, rel_location)
+ else:
+ # this should never happen
+ return None
+
+
def __init__(self, location):
- """Default version control checker: test if self.MARKER_DIR exists.
+ """Default version control checker: test if self.RCS_METADIR exists.
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
+ directory thus you will most likely not need to touch this check.
+
+ The IOError exception indicates that the specified file 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)):
+ result = self._find_rcs_directory(location)
+ if result is None:
raise IOError("Could not find version control information: %s" % location)
+ else:
+ self.root_dir, self.location = result
class CVS(GenericVersionControlSystem):
"""Class to manage items under revision control of CVS."""
- MARKER_DIR = "CVS"
+ RCS_METADIR = "CVS"
+ SCAN_PARENTS = False
+ ABSOLUTE_LOCATION = True
- 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 _readfile(self, cvsroot, path, revision=None):
"""
Read a single file from the CVS repository without checking out a full
@@ -265,14 +336,10 @@
class SVN(GenericVersionControlSystem):
"""Class to manage items under revision control of Subversion."""
- MARKER_DIR = ".svn"
+ RCS_METADIR = ".svn"
+ SCAN_PARENTS = False
+ ABSOLUTE_LOCATION = True
- 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)
@@ -318,54 +385,10 @@
class DARCS(GenericVersionControlSystem):
"""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
+ ABSOLUTE_LOCATION = 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
@@ -419,63 +442,25 @@
class GIT(GenericVersionControlSystem):
"""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
+ ABSOLUTE_LOCATION = False
+
+ 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]
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,107 +469,55 @@
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]
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, 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, 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, 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]
+ 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, error))
+ return output
class BZR(GenericVersionControlSystem):
"""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
+ ABSOLUTE_LOCATION = 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
+ # bzr revert
command = ["bzr", "revert", self.location]
exitcode, output_revert, error = pipe(command)
if exitcode != 0:
raise IOError("[BZR] revert of '%s' failed: %s" \
% (self.location, error))
- # bazaar pull
+ # bzr pull
command = ["bzr", "pull"]
exitcode, output_pull, error = pipe(command)
if exitcode != 0:
-------------------------------------------------------------------------
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