From 49a7771336ce09f6d42c7699ef32aecea0e83182 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 7 Dec 2009 20:07:55 -0500 Subject: Initial directory restructuring to clarify dependencies --- libbe/storage/vcs/base.py | 941 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 941 insertions(+) create mode 100644 libbe/storage/vcs/base.py (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py new file mode 100644 index 0000000..44643a4 --- /dev/null +++ b/libbe/storage/vcs/base.py @@ -0,0 +1,941 @@ +# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# Alexander Belchenko +# Ben Finney +# Chris Ball +# Gianluca Montecchi +# W. Trevor King +# +# 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 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. +# +# 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. + +""" +Define the base VCS (Version Control System) class, which should be +subclassed by other Version Control System backends. The base class +implements a "do not version" VCS. +""" + +import codecs +import os +import os.path +import re +from socket import gethostname +import shutil +import sys +import tempfile + +import libbe +from utility import Dir, search_parent_directories +from subproc import CommandError, invoke +from plugin import get_plugin + +if libbe.TESTING == True: + import unittest + import doctest + + +# List VCS modules in order of preference. +# Don't list this module, it is implicitly last. +VCS_ORDER = ['arch', 'bzr', 'darcs', 'git', 'hg'] + +def set_preferred_vcs(name): + global VCS_ORDER + assert name in VCS_ORDER, \ + 'unrecognized VCS %s not in\n %s' % (name, VCS_ORDER) + VCS_ORDER.remove(name) + VCS_ORDER.insert(0, name) + +def _get_matching_vcs(matchfn): + """Return the first module for which matchfn(VCS_instance) is true""" + for submodname in VCS_ORDER: + module = get_plugin('libbe', submodname) + vcs = module.new() + if matchfn(vcs) == True: + return vcs + vcs.cleanup() + return VCS() + +def vcs_by_name(vcs_name): + """Return the module for the VCS with the given name""" + return _get_matching_vcs(lambda vcs: vcs.name == vcs_name) + +def detect_vcs(dir): + """Return an VCS instance for the vcs being used in this directory""" + return _get_matching_vcs(lambda vcs: vcs.detect(dir)) + +def installed_vcs(): + """Return an instance of an installed VCS""" + return _get_matching_vcs(lambda vcs: vcs.installed()) + + + +class SettingIDnotSupported(NotImplementedError): + pass + +class VCSnotRooted(Exception): + def __init__(self): + msg = "VCS not rooted" + Exception.__init__(self, msg) + +class PathNotInRoot(Exception): + def __init__(self, path, root): + msg = "Path '%s' not in root '%s'" % (path, root) + Exception.__init__(self, msg) + self.path = path + self.root = root + +class NoSuchFile(Exception): + def __init__(self, pathname, root="."): + path = os.path.abspath(os.path.join(root, pathname)) + Exception.__init__(self, "No such file: %s" % path) + +class EmptyCommit(Exception): + def __init__(self): + Exception.__init__(self, "No changes to commit") + + +def new(): + return VCS() + +class VCS(object): + """ + This class implements a 'no-vcs' interface. + + Support for other VCSs can be added by subclassing this class, and + overriding methods _vcs_*() with code appropriate for your VCS. + + The methods _u_*() are utility methods available to the _vcs_*() + methods. + """ + name = "None" + client = "" # command-line tool for _u_invoke_client + versioned = False + def __init__(self, paranoid=False, encoding=sys.getdefaultencoding()): + self.paranoid = paranoid + self.verboseInvoke = False + self.rootdir = None + self._duplicateBasedir = None + self._duplicateDirname = None + self.encoding = encoding + def __str__(self): + return "<%s %s>" % (self.__class__.__name__, id(self)) + def __repr__(self): + return str(self) + def _vcs_version(self): + """ + Return the VCS version string. + """ + return "0.0" + def _vcs_detect(self, path=None): + """ + Detect whether a directory is revision controlled with this VCS. + """ + return True + def _vcs_root(self, path): + """ + Get the VCS root. This is the default working directory for + future invocations. You would normally set this to the root + directory for your VCS. + """ + if os.path.isdir(path)==False: + path = os.path.dirname(path) + if path == "": + path = os.path.abspath(".") + return path + def _vcs_init(self, path): + """ + Begin versioning the tree based at path. + """ + pass + def _vcs_cleanup(self): + """ + Remove any cruft that _vcs_init() created outside of the + versioned tree. + """ + pass + def _vcs_get_user_id(self): + """ + Get the VCS's suggested user id (e.g. "John Doe "). + If the VCS has not been configured with a username, return None. + """ + return None + def _vcs_set_user_id(self, value): + """ + Set the VCS's suggested user id (e.g "John Doe "). + This is run if the VCS has not been configured with a usename, so + that commits will have a reasonable FROM value. + """ + raise SettingIDnotSupported + def _vcs_add(self, path): + """ + Add the already created file at path to version control. + """ + pass + def _vcs_remove(self, path): + """ + Remove the file at path from version control. Optionally + remove the file from the filesystem as well. + """ + pass + def _vcs_update(self, path): + """ + Notify the versioning system of changes to the versioned file + at path. + """ + pass + def _vcs_get_file_contents(self, path, revision=None, binary=False): + """ + Get the file contents as they were in a given revision. + Revision==None specifies the current revision. + """ + assert revision == None, \ + "The %s VCS does not support revision specifiers" % self.name + if binary == False: + f = codecs.open(os.path.join(self.rootdir, path), "r", self.encoding) + else: + f = open(os.path.join(self.rootdir, path), "rb") + contents = f.read() + f.close() + return contents + def _vcs_duplicate_repo(self, directory, revision=None): + """ + Get the repository as it was in a given revision. + revision==None specifies the current revision. + dir specifies a directory to create the duplicate in. + """ + shutil.copytree(self.rootdir, directory, True) + def _vcs_commit(self, commitfile, allow_empty=False): + """ + Commit the current working directory, using the contents of + commitfile as the comment. Return the name of the old + revision (or None if commits are not supported). + + If allow_empty == False, raise EmptyCommit if there are no + changes to commit. + """ + return None + def _vcs_revision_id(self, index): + """ + Return the name of the th revision. Index will be an + integer (possibly <= 0). The choice of which branch to follow + when crossing branches/merges is not defined. + + Return None if revision IDs are not supported, or if the + specified revision does not exist. + """ + return None + def version(self): + """Cache version string for efficiency.""" + if not hasattr(self, '_version'): + self._version = self._get_version() + return self._version + def _get_version(self): + try: + ret = self._vcs_version() + return ret + except OSError, e: + if e.errno == errno.ENOENT: + return None + else: + raise OSError, e + except CommandError: + return None + def installed(self): + if self.version() != None: + return True + return False + def detect(self, path="."): + """ + Detect whether a directory is revision controlled with this VCS. + """ + return self._vcs_detect(path) + def root(self, path): + """ + Set the root directory to the path's VCS root. This is the + default working directory for future invocations. + """ + self.rootdir = self._vcs_root(path) + def init(self, path): + """ + Begin versioning the tree based at path. + Also roots the vcs at path. + """ + if os.path.isdir(path)==False: + path = os.path.dirname(path) + self._vcs_init(path) + self.root(path) + def cleanup(self): + self._vcs_cleanup() + def get_user_id(self): + """ + Get the VCS's suggested user id (e.g. "John Doe "). + If the VCS has not been configured with a username, return the user's + id. You can override the automatic lookup procedure by setting the + VCS.user_id attribute to a string of your choice. + """ + if hasattr(self, "user_id"): + if self.user_id != None: + return self.user_id + id = self._vcs_get_user_id() + if id == None: + name = self._u_get_fallback_username() + email = self._u_get_fallback_email() + id = self._u_create_id(name, email) + print >> sys.stderr, "Guessing id '%s'" % id + try: + self.set_user_id(id) + except SettingIDnotSupported: + pass + return id + def set_user_id(self, value): + """ + Set the VCS's suggested user id (e.g "John Doe "). + This is run if the VCS has not been configured with a usename, so + that commits will have a reasonable FROM value. + """ + self._vcs_set_user_id(value) + def add(self, path): + """ + Add the already created file at path to version control. + """ + self._vcs_add(self._u_rel_path(path)) + def remove(self, path): + """ + Remove a file from both version control and the filesystem. + """ + self._vcs_remove(self._u_rel_path(path)) + if os.path.exists(path): + os.remove(path) + def recursive_remove(self, dirname): + """ + Remove a file/directory and all its decendents from both + version control and the filesystem. + """ + if not os.path.exists(dirname): + raise NoSuchFile(dirname) + for dirpath,dirnames,filenames in os.walk(dirname, topdown=False): + filenames.extend(dirnames) + for path in filenames: + fullpath = os.path.join(dirpath, path) + if os.path.exists(fullpath) == False: + continue + self._vcs_remove(self._u_rel_path(fullpath)) + if os.path.exists(dirname): + shutil.rmtree(dirname) + def update(self, path): + """ + Notify the versioning system of changes to the versioned file + at path. + """ + self._vcs_update(self._u_rel_path(path)) + def get_file_contents(self, path, revision=None, allow_no_vcs=False, binary=False): + """ + Get the file as it was in a given revision. + Revision==None specifies the current revision. + + allow_no_vcs==True allows direct access to files through + codecs.open() or open() if the vcs decides it can't handle the + given path. + """ + if not os.path.exists(path): + raise NoSuchFile(path) + if self._use_vcs(path, allow_no_vcs): + relpath = self._u_rel_path(path) + contents = self._vcs_get_file_contents(relpath,revision,binary=binary) + else: + if binary == True: + f = codecs.open(path, "r", self.encoding) + else: + f = open(path, "rb") + contents = f.read() + f.close() + return contents + def set_file_contents(self, path, contents, allow_no_vcs=False, binary=False): + """ + Set the file contents under version control. + """ + add = not os.path.exists(path) + if binary == False: + f = codecs.open(path, "w", self.encoding) + else: + f = open(path, "wb") + f.write(contents) + f.close() + + if self._use_vcs(path, allow_no_vcs): + if add: + self.add(path) + else: + self.update(path) + def mkdir(self, path, allow_no_vcs=False, check_parents=True): + """ + Create (if neccessary) a directory at path under version + control. + """ + if check_parents == True: + parent = os.path.dirname(path) + if not os.path.exists(parent): # recurse through parents + self.mkdir(parent, allow_no_vcs, check_parents) + if not os.path.exists(path): + os.mkdir(path) + if self._use_vcs(path, allow_no_vcs): + self.add(path) + else: + assert os.path.isdir(path) + if self._use_vcs(path, allow_no_vcs): + #self.update(path)# Don't update directories. Changing files + pass # underneath them should be sufficient. + + def duplicate_repo(self, revision=None): + """ + Get the repository as it was in a given revision. + revision==None specifies the current revision. + Return the path to the arbitrary directory at the base of the new repo. + """ + # Dirname in Basedir to protect against simlink attacks. + if self._duplicateBasedir == None: + self._duplicateBasedir = tempfile.mkdtemp(prefix='BEvcs') + self._duplicateDirname = \ + os.path.join(self._duplicateBasedir, "duplicate") + self._vcs_duplicate_repo(directory=self._duplicateDirname, + revision=revision) + return self._duplicateDirname + def remove_duplicate_repo(self): + """ + Clean up a duplicate repo created with duplicate_repo(). + """ + if self._duplicateBasedir != None: + shutil.rmtree(self._duplicateBasedir) + self._duplicateBasedir = None + self._duplicateDirname = None + def commit(self, summary, body=None, allow_empty=False): + """ + Commit the current working directory, with a commit message + string summary and body. Return the name of the old revision + (or None if versioning is not supported). + + If allow_empty == False (the default), raise EmptyCommit if + there are no changes to commit. + """ + summary = summary.strip()+'\n' + if body is not None: + summary += '\n' + body.strip() + '\n' + descriptor, filename = tempfile.mkstemp() + revision = None + try: + temp_file = os.fdopen(descriptor, 'wb') + temp_file.write(summary) + temp_file.flush() + self.precommit() + revision = self._vcs_commit(filename, allow_empty=allow_empty) + temp_file.close() + self.postcommit() + finally: + os.remove(filename) + return revision + def precommit(self): + """ + Executed before all attempted commits. + """ + pass + def postcommit(self): + """ + Only executed after successful commits. + """ + pass + def revision_id(self, index=None): + """ + Return the name of the th revision. The choice of + which branch to follow when crossing branches/merges is not + defined. + + Return None if index==None, revision IDs are not supported, or + if the specified revision does not exist. + """ + if index == None: + return None + return self._vcs_revision_id(index) + def _u_any_in_string(self, list, string): + """ + Return True if any of the strings in list are in string. + Otherwise return False. + """ + for list_string in list: + if list_string in string: + return True + return False + def _u_invoke(self, *args, **kwargs): + if 'cwd' not in kwargs: + kwargs['cwd'] = self.rootdir + if 'verbose' not in kwargs: + kwargs['verbose'] = self.verboseInvoke + if 'encoding' not in kwargs: + kwargs['encoding'] = self.encoding + return invoke(*args, **kwargs) + def _u_invoke_client(self, *args, **kwargs): + cl_args = [self.client] + cl_args.extend(args) + return self._u_invoke(cl_args, **kwargs) + def _u_search_parent_directories(self, path, filename): + """ + Find the file (or directory) named filename in path or in any + of path's parents. + + e.g. + search_parent_directories("/a/b/c", ".be") + will return the path to the first existing file from + /a/b/c/.be + /a/b/.be + /a/.be + /.be + or None if none of those files exist. + """ + return search_parent_directories(path, filename) + def _use_vcs(self, path, allow_no_vcs): + """ + Try and decide if _vcs_add/update/mkdir/etc calls will + succeed. Returns True is we think the vcs_call would + succeeed, and False otherwise. + """ + use_vcs = True + exception = None + if self.rootdir != None: + if self.path_in_root(path) == False: + use_vcs = False + exception = PathNotInRoot(path, self.rootdir) + else: + use_vcs = False + exception = VCSnotRooted + if use_vcs == False and allow_no_vcs==False: + raise exception + return use_vcs + def path_in_root(self, path, root=None): + """ + Return the relative path to path from root. + >>> vcs = new() + >>> vcs.path_in_root("/a.b/c/.be", "/a.b/c") + True + >>> vcs.path_in_root("/a.b/.be", "/a.b/c") + False + """ + if root == None: + if self.rootdir == None: + raise VCSnotRooted + root = self.rootdir + path = os.path.abspath(path) + absRoot = os.path.abspath(root) + absRootSlashedDir = os.path.join(absRoot,"") + if not path.startswith(absRootSlashedDir): + return False + return True + def _u_rel_path(self, path, root=None): + """ + Return the relative path to path from root. + >>> vcs = new() + >>> vcs._u_rel_path("/a.b/c/.be", "/a.b/c") + '.be' + """ + if root == None: + if self.rootdir == None: + raise VCSnotRooted + root = self.rootdir + path = os.path.abspath(path) + absRoot = os.path.abspath(root) + absRootSlashedDir = os.path.join(absRoot,"") + if not path.startswith(absRootSlashedDir): + raise PathNotInRoot(path, absRootSlashedDir) + assert path != absRootSlashedDir, \ + "file %s == root directory %s" % (path, absRootSlashedDir) + relpath = path[len(absRootSlashedDir):] + return relpath + def _u_abspath(self, path, root=None): + """ + Return the absolute path from a path realtive to root. + >>> vcs = new() + >>> vcs._u_abspath(".be", "/a.b/c") + '/a.b/c/.be' + """ + if root == None: + assert self.rootdir != None, "VCS not rooted" + root = self.rootdir + return os.path.abspath(os.path.join(root, path)) + def _u_create_id(self, name, email=None): + """ + >>> vcs = new() + >>> vcs._u_create_id("John Doe", "jdoe@example.com") + 'John Doe ' + >>> vcs._u_create_id("John Doe") + 'John Doe' + """ + assert len(name) > 0 + if email == None or len(email) == 0: + return name + else: + return "%s <%s>" % (name, email) + def _u_parse_id(self, value): + """ + >>> vcs = new() + >>> vcs._u_parse_id("John Doe ") + ('John Doe', 'jdoe@example.com') + >>> vcs._u_parse_id("John Doe") + ('John Doe', None) + >>> try: + ... vcs._u_parse_id("John Doe ") + ... except AssertionError: + ... print "Invalid match" + Invalid match + """ + emailexp = re.compile("(.*) <([^>]*)>(.*)") + match = emailexp.search(value) + if match == None: + email = None + name = value + else: + assert len(match.groups()) == 3 + assert match.groups()[2] == "", match.groups() + email = match.groups()[1] + name = match.groups()[0] + assert name != None + assert len(name) > 0 + return (name, email) + def _u_get_fallback_username(self): + name = None + for envariable in ["LOGNAME", "USERNAME"]: + if os.environ.has_key(envariable): + name = os.environ[envariable] + break + assert name != None + return name + def _u_get_fallback_email(self): + hostname = gethostname() + name = self._u_get_fallback_username() + return "%s@%s" % (name, hostname) + def _u_parse_commitfile(self, commitfile): + """ + Split the commitfile created in self.commit() back into + summary and header lines. + """ + f = codecs.open(commitfile, "r", self.encoding) + summary = f.readline() + body = f.read() + body.lstrip('\n') + if len(body) == 0: + body = None + f.close() + return (summary, body) + + +if libbe.TESTING == True: + def setup_vcs_test_fixtures(testcase): + """Set up test fixtures for VCS test case.""" + testcase.vcs = testcase.Class() + testcase.dir = Dir() + testcase.dirname = testcase.dir.path + + vcs_not_supporting_uninitialized_user_id = [] + vcs_not_supporting_set_user_id = ["None", "hg"] + testcase.vcs_supports_uninitialized_user_id = ( + testcase.vcs.name not in vcs_not_supporting_uninitialized_user_id) + testcase.vcs_supports_set_user_id = ( + testcase.vcs.name not in vcs_not_supporting_set_user_id) + + if not testcase.vcs.installed(): + testcase.fail( + "%(name)s VCS not found" % vars(testcase.Class)) + + if testcase.Class.name != "None": + testcase.failIf( + testcase.vcs.detect(testcase.dirname), + "Detected %(name)s VCS before initialising" + % vars(testcase.Class)) + + testcase.vcs.init(testcase.dirname) + + class VCSTestCase(unittest.TestCase): + """Test cases for base VCS class.""" + + Class = VCS + + def __init__(self, *args, **kwargs): + super(VCSTestCase, self).__init__(*args, **kwargs) + self.dirname = None + + def setUp(self): + super(VCSTestCase, self).setUp() + setup_vcs_test_fixtures(self) + + def tearDown(self): + self.vcs.cleanup() + self.dir.cleanup() + super(VCSTestCase, self).tearDown() + + def full_path(self, rel_path): + return os.path.join(self.dirname, rel_path) + + + class VCS_init_TestCase(VCSTestCase): + """Test cases for VCS.init method.""" + + def test_detect_should_succeed_after_init(self): + """Should detect VCS in directory after initialization.""" + self.failUnless( + self.vcs.detect(self.dirname), + "Did not detect %(name)s VCS after initialising" + % vars(self.Class)) + + def test_vcs_rootdir_in_specified_root_path(self): + """VCS root directory should be in specified root path.""" + rp = os.path.realpath(self.vcs.rootdir) + dp = os.path.realpath(self.dirname) + vcs_name = self.Class.name + self.failUnless( + dp == rp or rp == None, + "%(vcs_name)s VCS root in wrong dir (%(dp)s %(rp)s)" % vars()) + + + class VCS_get_user_id_TestCase(VCSTestCase): + """Test cases for VCS.get_user_id method.""" + + def test_gets_existing_user_id(self): + """Should get the existing user ID.""" + if not self.vcs_supports_uninitialized_user_id: + return + + user_id = self.vcs.get_user_id() + self.failUnless( + user_id is not None, + "unable to get a user id") + + + class VCS_set_user_id_TestCase(VCSTestCase): + """Test cases for VCS.set_user_id method.""" + + def setUp(self): + super(VCS_set_user_id_TestCase, self).setUp() + + if self.vcs_supports_uninitialized_user_id: + self.prev_user_id = self.vcs.get_user_id() + else: + self.prev_user_id = "Uninitialized identity " + + if self.vcs_supports_set_user_id: + self.test_new_user_id = "John Doe " + self.vcs.set_user_id(self.test_new_user_id) + + def tearDown(self): + if self.vcs_supports_set_user_id: + self.vcs.set_user_id(self.prev_user_id) + super(VCS_set_user_id_TestCase, self).tearDown() + + def test_raises_error_in_unsupported_vcs(self): + """Should raise an error in a VCS that doesn't support it.""" + if self.vcs_supports_set_user_id: + return + self.assertRaises( + SettingIDnotSupported, + self.vcs.set_user_id, "foo") + + def test_updates_user_id_in_supporting_vcs(self): + """Should update the user ID in an VCS that supports it.""" + if not self.vcs_supports_set_user_id: + return + user_id = self.vcs.get_user_id() + self.failUnlessEqual( + self.test_new_user_id, user_id, + "user id not set correctly (expected %s, got %s)" + % (self.test_new_user_id, user_id)) + + + def setup_vcs_revision_test_fixtures(testcase): + """Set up revision test fixtures for VCS test case.""" + testcase.test_dirs = ['a', 'a/b', 'c'] + for path in testcase.test_dirs: + testcase.vcs.mkdir(testcase.full_path(path)) + + testcase.test_files = ['a/text', 'a/b/text'] + + testcase.test_contents = { + 'rev_1': "Lorem ipsum", + 'uncommitted': "dolor sit amet", + } + + + class VCS_mkdir_TestCase(VCSTestCase): + """Test cases for VCS.mkdir method.""" + + def setUp(self): + super(VCS_mkdir_TestCase, self).setUp() + setup_vcs_revision_test_fixtures(self) + + def tearDown(self): + for path in reversed(sorted(self.test_dirs)): + self.vcs.recursive_remove(self.full_path(path)) + super(VCS_mkdir_TestCase, self).tearDown() + + def test_mkdir_creates_directory(self): + """Should create specified directory in filesystem.""" + for path in self.test_dirs: + full_path = self.full_path(path) + self.failUnless( + os.path.exists(full_path), + "path %(full_path)s does not exist" % vars()) + + + class VCS_commit_TestCase(VCSTestCase): + """Test cases for VCS.commit method.""" + + def setUp(self): + super(VCS_commit_TestCase, self).setUp() + setup_vcs_revision_test_fixtures(self) + + def tearDown(self): + for path in reversed(sorted(self.test_dirs)): + self.vcs.recursive_remove(self.full_path(path)) + super(VCS_commit_TestCase, self).tearDown() + + def test_file_contents_as_specified(self): + """Should set file contents as specified.""" + test_contents = self.test_contents['rev_1'] + for path in self.test_files: + full_path = self.full_path(path) + self.vcs.set_file_contents(full_path, test_contents) + current_contents = self.vcs.get_file_contents(full_path) + self.failUnlessEqual(test_contents, current_contents) + + def test_file_contents_as_committed(self): + """Should have file contents as specified after commit.""" + test_contents = self.test_contents['rev_1'] + for path in self.test_files: + full_path = self.full_path(path) + self.vcs.set_file_contents(full_path, test_contents) + revision = self.vcs.commit("Initial file contents.") + current_contents = self.vcs.get_file_contents(full_path) + self.failUnlessEqual(test_contents, current_contents) + + def test_file_contents_as_set_when_uncommitted(self): + """Should set file contents as specified after commit.""" + if not self.vcs.versioned: + return + for path in self.test_files: + full_path = self.full_path(path) + self.vcs.set_file_contents( + full_path, self.test_contents['rev_1']) + revision = self.vcs.commit("Initial file contents.") + self.vcs.set_file_contents( + full_path, self.test_contents['uncommitted']) + current_contents = self.vcs.get_file_contents(full_path) + self.failUnlessEqual( + self.test_contents['uncommitted'], current_contents) + + def test_revision_file_contents_as_committed(self): + """Should get file contents as committed to specified revision.""" + if not self.vcs.versioned: + return + for path in self.test_files: + full_path = self.full_path(path) + self.vcs.set_file_contents( + full_path, self.test_contents['rev_1']) + revision = self.vcs.commit("Initial file contents.") + self.vcs.set_file_contents( + full_path, self.test_contents['uncommitted']) + committed_contents = self.vcs.get_file_contents( + full_path, revision) + self.failUnlessEqual( + self.test_contents['rev_1'], committed_contents) + + def test_revision_id_as_committed(self): + """Check for compatibility between .commit() and .revision_id()""" + if not self.vcs.versioned: + self.failUnlessEqual(self.vcs.revision_id(5), None) + return + committed_revisions = [] + for path in self.test_files: + full_path = self.full_path(path) + self.vcs.set_file_contents( + full_path, self.test_contents['rev_1']) + revision = self.vcs.commit("Initial %s contents." % path) + committed_revisions.append(revision) + self.vcs.set_file_contents( + full_path, self.test_contents['uncommitted']) + revision = self.vcs.commit("Altered %s contents." % path) + committed_revisions.append(revision) + for i,revision in enumerate(committed_revisions): + self.failUnlessEqual(self.vcs.revision_id(i), revision) + i += -len(committed_revisions) # check negative indices + self.failUnlessEqual(self.vcs.revision_id(i), revision) + i = len(committed_revisions) + self.failUnlessEqual(self.vcs.revision_id(i), None) + self.failUnlessEqual(self.vcs.revision_id(-i-1), None) + + def test_revision_id_as_committed(self): + """Check revision id before first commit""" + if not self.vcs.versioned: + self.failUnlessEqual(self.vcs.revision_id(5), None) + return + committed_revisions = [] + for path in self.test_files: + self.failUnlessEqual(self.vcs.revision_id(0), None) + + + class VCS_duplicate_repo_TestCase(VCSTestCase): + """Test cases for VCS.duplicate_repo method.""" + + def setUp(self): + super(VCS_duplicate_repo_TestCase, self).setUp() + setup_vcs_revision_test_fixtures(self) + + def tearDown(self): + self.vcs.remove_duplicate_repo() + for path in reversed(sorted(self.test_dirs)): + self.vcs.recursive_remove(self.full_path(path)) + super(VCS_duplicate_repo_TestCase, self).tearDown() + + def test_revision_file_contents_as_committed(self): + """Should match file contents as committed to specified revision. + """ + if not self.vcs.versioned: + return + for path in self.test_files: + full_path = self.full_path(path) + self.vcs.set_file_contents( + full_path, self.test_contents['rev_1']) + revision = self.vcs.commit("Commit current status") + self.vcs.set_file_contents( + full_path, self.test_contents['uncommitted']) + dup_repo_path = self.vcs.duplicate_repo(revision) + dup_file_path = os.path.join(dup_repo_path, path) + dup_file_contents = file(dup_file_path, 'rb').read() + self.failUnlessEqual( + self.test_contents['rev_1'], dup_file_contents) + self.vcs.remove_duplicate_repo() + + + def make_vcs_testcase_subclasses(vcs_class, namespace): + """Make VCSTestCase subclasses for vcs_class in the namespace.""" + vcs_testcase_classes = [ + c for c in ( + ob for ob in globals().values() if isinstance(ob, type)) + if issubclass(c, VCSTestCase)] + + for base_class in vcs_testcase_classes: + testcase_class_name = vcs_class.__name__ + base_class.__name__ + testcase_class_bases = (base_class,) + testcase_class_dict = dict(base_class.__dict__) + testcase_class_dict['Class'] = vcs_class + testcase_class = type( + testcase_class_name, testcase_class_bases, testcase_class_dict) + setattr(namespace, testcase_class_name, testcase_class) + + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) -- cgit From 44b4e3f8b6405d0e1e0ebf6cb526ab62cdbbdb25 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 8 Dec 2009 08:54:50 -0500 Subject: Transitioned bugdir.py to new storage format. --- libbe/storage/vcs/base.py | 156 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 44643a4..abc7a01 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -116,6 +116,134 @@ class VCS(object): The methods _u_*() are utility methods available to the _vcs_*() methods. + + Sink to existing root + ====================== + + Consider the following usage case: + You have a bug directory rooted in + /path/to/source + by which I mean the '.be' directory is at + /path/to/source/.be + However, you're of in some subdirectory like + /path/to/source/GUI/testing + and you want to comment on a bug. Setting sink_to_root=True wen + you initialize your BugDir will cause it to search for the '.be' + file in the ancestors of the path you passed in as 'root'. + /path/to/source/GUI/testing/.be miss + /path/to/source/GUI/.be miss + /path/to/source/.be hit! + So it still roots itself appropriately without much work for you. + + File-system access + ================== + + BugDirs live completely in memory when .sync_with_disk is False. + This is the default configuration setup by BugDir(from_disk=False). + If .sync_with_disk == True (e.g. BugDir(from_disk=True)), then + any changes to the BugDir will be immediately written to disk. + + If you want to change .sync_with_disk, we suggest you use + .set_sync_with_disk(), which propogates the new setting through to + all bugs/comments/etc. that have been loaded into memory. If + you've been living in memory and want to move to + .sync_with_disk==True, but you're not sure if anything has been + changed in memory, a call to .save() immediately before the + .set_sync_with_disk(True) call is a safe move. + + Regardless of .sync_with_disk, a call to .save() will write out + all the contents that the BugDir instance has loaded into memory. + If sync_with_disk has been True over the course of all interesting + changes, this .save() call will be a waste of time. + + The BugDir will only load information from the file system when it + loads new settings/bugs/comments that it doesn't already have in + memory and .sync_with_disk == True. + + Allow storage initialization + ======================== + + This one is for testing purposes. Setting it to True allows the + BugDir to search for an installed Storage backend and initialize + it in the root directory. This is a convenience option for + supporting tests of versioning functionality + (e.g. .duplicate_bugdir). + + Disable encoding manipulation + ============================= + + This one is for testing purposed. You might have non-ASCII + Unicode in your bugs, comments, files, etc. BugDir instances try + and support your preferred encoding scheme (e.g. "utf-8") when + dealing with stream and file input/output. For stream output, + this involves replacing sys.stdout and sys.stderr + (libbe.encode.set_IO_stream_encodings). However this messes up + doctest's output catching. In order to support doctest tests + using BugDirs, set manipulate_encodings=False, and stick to ASCII + in your tests. + + if root == None: + root = os.getcwd() + if sink_to_existing_root == True: + self.root = self._find_root(root) + else: + if not os.path.exists(root): + self.root = None + raise NoRootEntry(root) + self.root = root + # get a temporary storage until we've loaded settings + self.sync_with_disk = False + self.storage = self._guess_storage() + + if assert_new_BugDir == True: + if os.path.exists(self.get_path()): + raise AlreadyInitialized, self.get_path() + if storage == None: + storage = self._guess_storage(allow_storage_init) + self.storage = storage + self._setup_user_id(self.user_id) + + + # methods for getting the BugDir situated in the filesystem + + def _find_root(self, path): + """ + Search for an existing bug database dir and it's ancestors and + return a BugDir rooted there. Only called by __init__, and + then only if sink_to_existing_root == True. + """ + if not os.path.exists(path): + self.root = None + raise NoRootEntry(path) + versionfile=utility.search_parent_directories(path, + os.path.join(".be", "version")) + if versionfile != None: + beroot = os.path.dirname(versionfile) + root = os.path.dirname(beroot) + return root + else: + beroot = utility.search_parent_directories(path, ".be") + if beroot == None: + self.root = None + raise NoBugDir(path) + return beroot + + def _guess_storage(self, allow_storage_init=False): + """ + Only called by __init__. + """ + deepdir = self.get_path() + if not os.path.exists(deepdir): + deepdir = os.path.dirname(deepdir) + new_storage = storage.detect_storage(deepdir) + install = False + if new_storage.name == "None": + if allow_storage_init == True: + new_storage = storage.installed_storage() + new_storage.init(self.root) + return new_storage + +os.listdir(self.get_path("bugs")): """ name = "None" client = "" # command-line tool for _u_invoke_client @@ -633,6 +761,34 @@ class VCS(object): body = None f.close() return (summary, body) + + def check_disk_version(self): + version = self.get_version() + if version != upgrade.BUGDIR_DISK_VERSION: + upgrade.upgrade(self.root, version) + + def disk_version(self, path=None, use_none_vcs=False, + for_duplicate_bugdir=False): + """ + Requires disk access. + """ + if path == None: + path = self.get_path("version") + allow_no_vcs = not VCS.path_in_root(path) + if allow_no_vcs == True: + assert for_duplicate_bugdir == True + return self.get(path, allow_no_vcs=allow_no_vcs).rstrip("\n") + + def set_disk_version(self): + """ + Requires disk access. + """ + if self.sync_with_disk == False: + raise DiskAccessRequired("set version") + self.vcs.mkdir(self.get_path()) + self.vcs.set_file_contents(self.get_path("version"), + upgrade.BUGDIR_DISK_VERSION+"\n") + if libbe.TESTING == True: -- cgit From f8a498f76d7bbcb42cf7bbc80164d98bfe57f8ab Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 12 Dec 2009 01:43:20 -0500 Subject: Added libbe.ui.util.user for managing user ids. --- libbe/storage/vcs/base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index abc7a01..f8b0727 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -207,11 +207,11 @@ class VCS(object): # methods for getting the BugDir situated in the filesystem def _find_root(self, path): - """ + ''' Search for an existing bug database dir and it's ancestors and return a BugDir rooted there. Only called by __init__, and then only if sink_to_existing_root == True. - """ + ''' if not os.path.exists(path): self.root = None raise NoRootEntry(path) @@ -229,9 +229,9 @@ class VCS(object): return beroot def _guess_storage(self, allow_storage_init=False): - """ + ''' Only called by __init__. - """ + ''' deepdir = self.get_path() if not os.path.exists(deepdir): deepdir = os.path.dirname(deepdir) -- cgit From 4d057dab603f42ec40b911dbee6792dcf107bd14 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 06:19:23 -0500 Subject: Converted libbe.storage.vcs.base to new Storage format. --- libbe/storage/vcs/base.py | 1105 +++++++++++++++++++++------------------------ 1 file changed, 511 insertions(+), 594 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index f8b0727..8c0ecf5 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -29,20 +29,23 @@ import codecs import os import os.path import re -from socket import gethostname import shutil import sys import tempfile import libbe -from utility import Dir, search_parent_directories -from subproc import CommandError, invoke -from plugin import get_plugin +import libbe.storage.base +import libbe.util.encoding +from libbe.util.utility import Dir, search_parent_directories +from libbe.util.subproc import CommandError, invoke +from libbe.util.plugin import import_by_name +#import libbe.storage.util.upgrade as upgrade if libbe.TESTING == True: import unittest import doctest + import libbe.ui.util.user # List VCS modules in order of preference. # Don't list this module, it is implicitly last. @@ -58,62 +61,243 @@ def set_preferred_vcs(name): def _get_matching_vcs(matchfn): """Return the first module for which matchfn(VCS_instance) is true""" for submodname in VCS_ORDER: - module = get_plugin('libbe', submodname) + module = import_by_name('libbe.storage.vcs.%s' % submodname) vcs = module.new() if matchfn(vcs) == True: return vcs vcs.cleanup() return VCS() - + def vcs_by_name(vcs_name): """Return the module for the VCS with the given name""" + if vcs_name == VCS.name: + return new() return _get_matching_vcs(lambda vcs: vcs.name == vcs_name) def detect_vcs(dir): """Return an VCS instance for the vcs being used in this directory""" - return _get_matching_vcs(lambda vcs: vcs.detect(dir)) + return _get_matching_vcs(lambda vcs: vcs._detect(dir)) def installed_vcs(): """Return an instance of an installed VCS""" return _get_matching_vcs(lambda vcs: vcs.installed()) - -class SettingIDnotSupported(NotImplementedError): - pass - -class VCSnotRooted(Exception): +class VCSnotRooted (libbe.storage.base.ConnectionError): def __init__(self): - msg = "VCS not rooted" - Exception.__init__(self, msg) - -class PathNotInRoot(Exception): - def __init__(self, path, root): - msg = "Path '%s' not in root '%s'" % (path, root) - Exception.__init__(self, msg) + msg = 'VCS not rooted' + libbe.storage.base.ConnectionError.__init__(self, msg) + +class InvalidPath (libbe.storage.base.InvalidID): + def __init__(self, path, root, msg=None): + if msg == None: + msg = 'Path "%s" not in root "%s"' % (path, root) + libbe.storage.base.InvalidID.__init__(self, msg) self.path = path self.root = root -class NoSuchFile(Exception): - def __init__(self, pathname, root="."): +class SpacerCollision (InvalidPath): + def __init__(self, path, spacer): + msg = 'Path "%s" collides with spacer directory "%s"' % (path, spacer) + InvalidPath.__init__(self, path, root=None, msg=msg) + self.spacer = spacer + +class NoSuchFile (libbe.storage.base.InvalidID): + def __init__(self, pathname, root='.'): path = os.path.abspath(os.path.join(root, pathname)) - Exception.__init__(self, "No such file: %s" % path) + libbe.storage.base.InvalidID.__init__(self, 'No such file: %s' % path) -class EmptyCommit(Exception): - def __init__(self): - Exception.__init__(self, "No changes to commit") + +class CachedPathID (object): + """ + Storage ID <-> path policy. + .../.be/BUGDIR/bugs/BUG/comments/COMMENT + ^-- root path + + >>> dir = Dir() + >>> os.mkdir(os.path.join(dir.path, '.be')) + >>> os.mkdir(os.path.join(dir.path, '.be', 'abc')) + >>> os.mkdir(os.path.join(dir.path, '.be', 'abc', 'bugs')) + >>> os.mkdir(os.path.join(dir.path, '.be', 'abc', 'bugs', '123')) + >>> os.mkdir(os.path.join(dir.path, '.be', 'abc', 'bugs', '123', 'comments')) + >>> os.mkdir(os.path.join(dir.path, '.be', 'abc', 'bugs', '123', 'comments', 'def')) + >>> os.mkdir(os.path.join(dir.path, '.be', 'abc', 'bugs', '456')) + >>> file(os.path.join(dir.path, '.be', 'abc', 'values'), + ... 'w').close() + >>> file(os.path.join(dir.path, '.be', 'abc', 'bugs', '123', 'values'), + ... 'w').close() + >>> file(os.path.join(dir.path, '.be', 'abc', 'bugs', '123', 'comments', 'def', 'values'), + ... 'w').close() + >>> c = CachedPathID() + >>> c.root(dir.path) + >>> c.id(os.path.join(dir.path, '.be', 'abc', 'bugs', '123', 'comments', 'def', 'values')) + 'def/values' + >>> c.init() + >>> sorted(os.listdir(os.path.join(c._root, '.be'))) + ['abc', 'id-cache'] + >>> c.connect() + >>> c.path('123/values') # doctest: +ELLIPSIS + u'.../.be/abc/bugs/123/values' + >>> c.disconnect() + >>> c.destroy() + >>> sorted(os.listdir(os.path.join(c._root, '.be'))) + ['abc'] + >>> c.connect() # demonstrate auto init + >>> sorted(os.listdir(os.path.join(c._root, '.be'))) + ['abc', 'id-cache'] + >>> c.add_id(u'xyz', parent=None) # doctest: +ELLIPSIS + u'.../.be/xyz' + >>> c.add_id('xyz/def', parent='xyz') # doctest: +ELLIPSIS + u'.../.be/xyz/def' + >>> c.add_id('qrs', parent='123') # doctest: +ELLIPSIS + u'.../.be/abc/bugs/123/comments/qrs' + >>> c.disconnect() + >>> c.connect() + >>> c.path('qrs') # doctest: +ELLIPSIS + u'.../.be/abc/bugs/123/comments/qrs' + >>> c.remove_id('qrs') + >>> c.path('qrs') + Traceback (most recent call last): + ... + InvalidID: 'qrs' + >>> c.disconnect() + >>> c.destroy() + >>> dir.cleanup() + """ + def __init__(self, encoding=None): + self.encoding = libbe.util.encoding.get_filesystem_encoding() + self._spacer_dirs = ['.be', 'bugs', 'comments'] + + def root(self, path): + self._root = os.path.abspath(path).rstrip(os.path.sep) + self._cache_path = os.path.join( + self._root, self._spacer_dirs[0], 'id-cache') + + def init(self): + """ + Create cache file for an existing .be directory. + File if multiple lines of the form: + UUID\tPATH + """ + self._cache = {} + spaced_root = os.path.join(self._root, self._spacer_dirs[0]) + for dirpath, dirnames, filenames in os.walk(spaced_root): + if dirpath == spaced_root: + continue + try: + id = self.id(dirpath) + relpath = dirpath[len(self._root)+1:] + if id.count('/') == 0: + self._cache[id] = relpath + except InvalidPath: + pass + self._changed = True + self.disconnect() + + def destroy(self): + os.remove(self._cache_path) + + def connect(self): + if not os.path.exists(self._cache_path): + try: + self.init() + except IOError: + raise libbe.storage.base.ConnectionError + self._cache = {} # key: uuid, value: path + self._changed = False + f = codecs.open(self._cache_path, 'r', self.encoding) + for line in f: + fields = line.rstrip('\n').split('\t') + self._cache[fields[0]] = fields[1] + f.close() + + def disconnect(self): + if self._changed == True: + f = codecs.open(self._cache_path, 'w', self.encoding) + for uuid,path in self._cache.items(): + f.write('%s\t%s\n' % (uuid, path)) + f.close() + self._cache = {} + + def path(self, id, relpath=False): + fields = id.split('/', 1) + uuid = fields[0] + if len(fields) == 1: + extra = [] + else: + extra = fields[1:] + if uuid not in self._cache: + raise libbe.storage.base.InvalidID(uuid) + if relpath == True: + return os.path.join(self._cache[uuid], *extra) + return os.path.join(self._root, self._cache[uuid], *extra) + + def add_id(self, id, parent=None): + if id.count('/') > 0: + # not a UUID-level path + assert id.startswith(parent), \ + 'Strange ID: "%s" should start with "%s"' % (id, parent) + path = self.path(id) + elif id in self._cache: + # already added + path = self.path(id) + else: + if parent == None: + parent_path = '' + spacer = self._spacer_dirs[0] + else: + assert parent.count('/') == 0, \ + 'Strange parent ID: "%s" should be UUID' % parent + parent_path = self.path(parent, relpath=True) + parent_spacer = parent_path.split(os.path.sep)[-2] + i = self._spacer_dirs.index(parent_spacer) + spacer = self._spacer_dirs[i+1] + path = os.path.join(parent_path, spacer, id) + self._cache[id] = path + self._changed = True + path = os.path.join(self._root, path) + return path + + def remove_id(self, id): + if id.count('/') > 0: + return # not a UUID-level path + self._cache.pop(id) + self._changed = True + + def id(self, path): + path = os.path.abspath(path) + if not path.startswith(self._root + os.path.sep): + raise InvalidPath('Path %s not in root %s' % (path, self._root)) + path = path[len(self._root)+1:] + orig_path = path + if not path.startswith(self._spacer_dirs[0] + os.path.sep): + raise InvalidPath(path, self._spacer_dirs[0]) + for spacer in self._spacer_dirs: + if not path.startswith(spacer + os.path.sep): + break + id = path[len(spacer)+1:] + fields = path[len(spacer)+1:].split(os.path.sep,1) + if len(fields) == 1: + break + path = fields[1] + for spacer in self._spacer_dirs: + if id.endswith(os.path.sep + spacer): + raise SpacerCollision(orig_path, spacer) + if os.path.sep != '/': + id = id.replace(os.path.sep, '/') + return id def new(): return VCS() -class VCS(object): +class VCS (libbe.storage.base.VersionedStorage): """ This class implements a 'no-vcs' interface. Support for other VCSs can be added by subclassing this class, and overriding methods _vcs_*() with code appropriate for your VCS. - + The methods _u_*() are utility methods available to the _vcs_*() methods. @@ -127,7 +311,7 @@ class VCS(object): /path/to/source/.be However, you're of in some subdirectory like /path/to/source/GUI/testing - and you want to comment on a bug. Setting sink_to_root=True wen + and you want to comment on a bug. Setting sink_to_root=True when you initialize your BugDir will cause it to search for the '.be' file in the ancestors of the path you passed in as 'root'. /path/to/source/GUI/testing/.be miss @@ -245,113 +429,110 @@ class VCS(object): os.listdir(self.get_path("bugs")): """ - name = "None" - client = "" # command-line tool for _u_invoke_client - versioned = False - def __init__(self, paranoid=False, encoding=sys.getdefaultencoding()): - self.paranoid = paranoid - self.verboseInvoke = False - self.rootdir = None - self._duplicateBasedir = None - self._duplicateDirname = None - self.encoding = encoding - def __str__(self): - return "<%s %s>" % (self.__class__.__name__, id(self)) - def __repr__(self): - return str(self) + name = 'None' + client = 'false' # command-line tool for _u_invoke_client + + def __init__(self, *args, **kwargs): + if 'encoding' not in kwargs: + kwargs['encoding'] = libbe.util.encoding.get_filesystem_encoding() + libbe.storage.base.VersionedStorage.__init__(self, *args, **kwargs) + self.versioned = False + self.verbose_invoke = False + self._cached_path_id = CachedPathID() + def _vcs_version(self): """ Return the VCS version string. """ - return "0.0" + return '0' + + def _vcs_get_user_id(self): + """ + Get the VCS's suggested user id (e.g. "John Doe "). + If the VCS has not been configured with a username, return None. + """ + return None + def _vcs_detect(self, path=None): """ Detect whether a directory is revision controlled with this VCS. """ return True + def _vcs_root(self, path): """ Get the VCS root. This is the default working directory for future invocations. You would normally set this to the root directory for your VCS. """ - if os.path.isdir(path)==False: + if os.path.isdir(path) == False: path = os.path.dirname(path) - if path == "": - path = os.path.abspath(".") + if path == '': + path = os.path.abspath('.') return path - def _vcs_init(self, path): + + def _vcs_init(self): """ - Begin versioning the tree based at path. + Begin versioning the tree based at self.repo. """ pass - def _vcs_cleanup(self): + + def _vcs_destroy(self): """ - Remove any cruft that _vcs_init() created outside of the - versioned tree. + Remove any files used in versioning (e.g. whatever _vcs_init() + created). """ pass - def _vcs_get_user_id(self): - """ - Get the VCS's suggested user id (e.g. "John Doe "). - If the VCS has not been configured with a username, return None. - """ - return None - def _vcs_set_user_id(self, value): - """ - Set the VCS's suggested user id (e.g "John Doe "). - This is run if the VCS has not been configured with a usename, so - that commits will have a reasonable FROM value. - """ - raise SettingIDnotSupported + def _vcs_add(self, path): """ Add the already created file at path to version control. """ pass + def _vcs_remove(self, path): """ Remove the file at path from version control. Optionally remove the file from the filesystem as well. """ pass + def _vcs_update(self, path): """ Notify the versioning system of changes to the versioned file at path. """ pass - def _vcs_get_file_contents(self, path, revision=None, binary=False): + + def _vcs_get_file_contents(self, path, revision=None): """ Get the file contents as they were in a given revision. Revision==None specifies the current revision. """ - assert revision == None, \ - "The %s VCS does not support revision specifiers" % self.name - if binary == False: - f = codecs.open(os.path.join(self.rootdir, path), "r", self.encoding) - else: - f = open(os.path.join(self.rootdir, path), "rb") + if revision != None: + raise libbe.storage.base.InvalidRevision( + 'The %s VCS does not support revision specifiers' % self.name) + path = os.path.join(self.repo, path) + if not os.path.exists(path): + return libbe.util.InvalidObject + if os.path.isdir(path): + return libbe.storage.base.InvalidDirectory + f = open(path, 'rb') contents = f.read() f.close() return contents - def _vcs_duplicate_repo(self, directory, revision=None): - """ - Get the repository as it was in a given revision. - revision==None specifies the current revision. - dir specifies a directory to create the duplicate in. - """ - shutil.copytree(self.rootdir, directory, True) + def _vcs_commit(self, commitfile, allow_empty=False): """ Commit the current working directory, using the contents of commitfile as the comment. Return the name of the old revision (or None if commits are not supported). - + If allow_empty == False, raise EmptyCommit if there are no changes to commit. """ return None + def _vcs_revision_id(self, index): """ Return the name of the th revision. Index will be an @@ -362,11 +543,13 @@ os.listdir(self.get_path("bugs")): specified revision does not exist. """ return None + def version(self): - """Cache version string for efficiency.""" + # Cache version string for efficiency. if not hasattr(self, '_version'): self._version = self._get_version() return self._version + def _get_version(self): try: ret = self._vcs_version() @@ -378,183 +561,166 @@ os.listdir(self.get_path("bugs")): raise OSError, e except CommandError: return None + def installed(self): if self.version() != None: return True return False - def detect(self, path="."): - """ - Detect whether a directory is revision controlled with this VCS. - """ - return self._vcs_detect(path) - def root(self, path): - """ - Set the root directory to the path's VCS root. This is the - default working directory for future invocations. - """ - self.rootdir = self._vcs_root(path) - def init(self, path): - """ - Begin versioning the tree based at path. - Also roots the vcs at path. - """ - if os.path.isdir(path)==False: - path = os.path.dirname(path) - self._vcs_init(path) - self.root(path) - def cleanup(self): - self._vcs_cleanup() + def get_user_id(self): """ Get the VCS's suggested user id (e.g. "John Doe "). - If the VCS has not been configured with a username, return the user's - id. You can override the automatic lookup procedure by setting the + If the VCS has not been configured with a username, return None. + You can override the automatic lookup procedure by setting the VCS.user_id attribute to a string of your choice. """ - if hasattr(self, "user_id"): - if self.user_id != None: - return self.user_id - id = self._vcs_get_user_id() - if id == None: - name = self._u_get_fallback_username() - email = self._u_get_fallback_email() - id = self._u_create_id(name, email) - print >> sys.stderr, "Guessing id '%s'" % id - try: - self.set_user_id(id) - except SettingIDnotSupported: - pass - return id - def set_user_id(self, value): + if not hasattr(self, 'user_id'): + self.user_id = self._vcs_get_user_id() + return self.user_id + + def _detect(self, path='.'): """ - Set the VCS's suggested user id (e.g "John Doe "). - This is run if the VCS has not been configured with a usename, so - that commits will have a reasonable FROM value. + Detect whether a directory is revision controlled with this VCS. """ - self._vcs_set_user_id(value) - def add(self, path): + return self._vcs_detect(path) + + def root(self): """ - Add the already created file at path to version control. + Set the root directory to the path's VCS root. This is the + default working directory for future invocations. """ - self._vcs_add(self._u_rel_path(path)) - def remove(self, path): + self.repo = os.path.abspath(self._vcs_root(self.repo)) + if os.path.isdir(self.repo) == False: + self.repo = os.path.dirname(self.repo) + self.be_dir = os.path.join( + self.repo, self._cached_path_id._spacer_dirs[0]) + self._cached_path_id.root(self.repo) + + def _init(self): """ - Remove a file from both version control and the filesystem. + Begin versioning the tree based at self.repo. + Also roots the vcs at path. """ - self._vcs_remove(self._u_rel_path(path)) + self.root() + self._vcs_init() + os.mkdir(self.be_dir) + self._vcs_add(self._u_rel_path(self.be_dir)) + self._cached_path_id.init() + + def _destroy(self): + self._vcs_destroy() + self._cached_path_id.destroy() + shutil.rmtree(self.be_dir) + + def _connect(self): + self.root() + self._cached_path_id.connect() + self.check_disk_version() + + def disconnect(self): + self._cached_path_id.disconnect() + + def _add(self, id, parent=None, directory=False): + path = self._cached_path_id.add_id(id, parent) + relpath = self._u_rel_path(path) + reldirs = relpath.split(os.path.sep) + if directory == False: + reldirs = reldirs[:-1] + dir = self.repo + for reldir in reldirs: + dir = os.path.join(dir, reldir) + if not os.path.exists(dir): + os.mkdir(dir) + self._vcs_add(self._u_rel_path(dir)) + elif not os.path.isdir(dir): + raise libbe.storage.base.InvalidDirectory + if directory == False: + if not os.path.exists(path): + open(path, 'w').close() + self._vcs_add(self._u_rel_path(path)) + + def _remove(self, id): + path = self._cached_path_id.path(id) if os.path.exists(path): - os.remove(path) - def recursive_remove(self, dirname): - """ - Remove a file/directory and all its decendents from both - version control and the filesystem. - """ - if not os.path.exists(dirname): - raise NoSuchFile(dirname) - for dirpath,dirnames,filenames in os.walk(dirname, topdown=False): + if os.path.isdir(path) and len(self.children(id)) > 0: + raise libbe.storage.base.DirectoryNotEmpty(id) + self._vcs_remove(self._u_rel_path(path)) + if os.path.exists(path): + if os.path.isdir(path): + os.rmdir(path) + else: + os.remove(path) + self._cached_path_id.remove_id(id) + + def _recursive_remove(self, id): + path = self._cached_path_id.path(id) + for dirpath,dirnames,filenames in os.walk(path, topdown=False): filenames.extend(dirnames) - for path in filenames: - fullpath = os.path.join(dirpath, path) + for f in filenames: + fullpath = os.path.join(dirpath, f) if os.path.exists(fullpath) == False: continue self._vcs_remove(self._u_rel_path(fullpath)) - if os.path.exists(dirname): - shutil.rmtree(dirname) - def update(self, path): - """ - Notify the versioning system of changes to the versioned file - at path. - """ - self._vcs_update(self._u_rel_path(path)) - def get_file_contents(self, path, revision=None, allow_no_vcs=False, binary=False): - """ - Get the file as it was in a given revision. - Revision==None specifies the current revision. - - allow_no_vcs==True allows direct access to files through - codecs.open() or open() if the vcs decides it can't handle the - given path. - """ - if not os.path.exists(path): - raise NoSuchFile(path) - if self._use_vcs(path, allow_no_vcs): - relpath = self._u_rel_path(path) - contents = self._vcs_get_file_contents(relpath,revision,binary=binary) + if os.path.exists(path): + shutil.rmtree(path) + path = self._cached_path_id.path(id, relpath=True) + for id,p in self._cached_path_id._cache.items(): + if p.startswith(path): + self._cached_path_id.remove_id(id) + + def _children(self, id=None, revision=None): + if id==None: + path = self.be_dir else: - if binary == True: - f = codecs.open(path, "r", self.encoding) - else: - f = open(path, "rb") - contents = f.read() - f.close() + path = self._cached_path_id.path(id) + if os.path.isdir(path) == False: + return [] + children = os.listdir(path) + for i,c in enumerate(children): + if c in self._cached_path_id._spacer_dirs: + children[i] = None + children.extend([os.path.join(c, c2) for c2 in + os.listdir(os.path.join(path, c))]) + elif c == 'id-cache': + children[i] = None + for i,c in enumerate(children): + if c == None: continue + cpath = os.path.join(path, c) + children[i] = self._cached_path_id.id(cpath) + return [c for c in children if c != None] + + def _get(self, id, default=libbe.util.InvalidObject, revision=None): + try: + path = self._cached_path_id.path(id) + except libbe.storage.base.InvalidID, e: + if default == libbe.util.InvalidObject: + raise e + return default + relpath = self._u_rel_path(path) + contents = self._vcs_get_file_contents(relpath,revision) + if contents == libbe.storage.base.InvalidDirectory: + raise libbe.storage.base.InvalidDirectory(id) + elif contents == libbe.util.InvalidObject: + raise libbe.storage.base.InvalidID(id) + elif len(contents) == 0: + return None return contents - def set_file_contents(self, path, contents, allow_no_vcs=False, binary=False): - """ - Set the file contents under version control. - """ - add = not os.path.exists(path) - if binary == False: - f = codecs.open(path, "w", self.encoding) - else: - f = open(path, "wb") - f.write(contents) - f.close() - - if self._use_vcs(path, allow_no_vcs): - if add: - self.add(path) - else: - self.update(path) - def mkdir(self, path, allow_no_vcs=False, check_parents=True): - """ - Create (if neccessary) a directory at path under version - control. - """ - if check_parents == True: - parent = os.path.dirname(path) - if not os.path.exists(parent): # recurse through parents - self.mkdir(parent, allow_no_vcs, check_parents) + + def _set(self, id, value): + try: + path = self._cached_path_id.path(id) + except libbe.storage.base.InvalidID, e: + raise e if not os.path.exists(path): - os.mkdir(path) - if self._use_vcs(path, allow_no_vcs): - self.add(path) - else: - assert os.path.isdir(path) - if self._use_vcs(path, allow_no_vcs): - #self.update(path)# Don't update directories. Changing files - pass # underneath them should be sufficient. - - def duplicate_repo(self, revision=None): - """ - Get the repository as it was in a given revision. - revision==None specifies the current revision. - Return the path to the arbitrary directory at the base of the new repo. - """ - # Dirname in Basedir to protect against simlink attacks. - if self._duplicateBasedir == None: - self._duplicateBasedir = tempfile.mkdtemp(prefix='BEvcs') - self._duplicateDirname = \ - os.path.join(self._duplicateBasedir, "duplicate") - self._vcs_duplicate_repo(directory=self._duplicateDirname, - revision=revision) - return self._duplicateDirname - def remove_duplicate_repo(self): - """ - Clean up a duplicate repo created with duplicate_repo(). - """ - if self._duplicateBasedir != None: - shutil.rmtree(self._duplicateBasedir) - self._duplicateBasedir = None - self._duplicateDirname = None - def commit(self, summary, body=None, allow_empty=False): - """ - Commit the current working directory, with a commit message - string summary and body. Return the name of the old revision - (or None if versioning is not supported). - - If allow_empty == False (the default), raise EmptyCommit if - there are no changes to commit. - """ + raise libbe.storage.base.InvalidID(id) + if os.path.isdir(path): + raise libbe.storage.base.InvalidDirectory(id) + f = open(path, "wb") + f.write(value) + f.close() + self._vcs_update(self._u_rel_path(path)) + + def _commit(self, summary, body=None, allow_empty=False): summary = summary.strip()+'\n' if body is not None: summary += '\n' + body.strip() + '\n' @@ -564,35 +730,20 @@ os.listdir(self.get_path("bugs")): temp_file = os.fdopen(descriptor, 'wb') temp_file.write(summary) temp_file.flush() - self.precommit() revision = self._vcs_commit(filename, allow_empty=allow_empty) temp_file.close() - self.postcommit() finally: os.remove(filename) return revision - def precommit(self): - """ - Executed before all attempted commits. - """ - pass - def postcommit(self): - """ - Only executed after successful commits. - """ - pass - def revision_id(self, index=None): - """ - Return the name of the th revision. The choice of - which branch to follow when crossing branches/merges is not - defined. - Return None if index==None, revision IDs are not supported, or - if the specified revision does not exist. - """ + def revision_id(self, index=None): if index == None: return None - return self._vcs_revision_id(index) + revid = self._vcs_revision_id(index) + if revid == None: + raise libbe.storage.base.InvalidRevision(index) + return revid + def _u_any_in_string(self, list, string): """ Return True if any of the strings in list are in string. @@ -602,23 +753,26 @@ os.listdir(self.get_path("bugs")): if list_string in string: return True return False + def _u_invoke(self, *args, **kwargs): if 'cwd' not in kwargs: - kwargs['cwd'] = self.rootdir + kwargs['cwd'] = self.repo if 'verbose' not in kwargs: - kwargs['verbose'] = self.verboseInvoke + kwargs['verbose'] = self.verbose_invoke if 'encoding' not in kwargs: kwargs['encoding'] = self.encoding return invoke(*args, **kwargs) + def _u_invoke_client(self, *args, **kwargs): cl_args = [self.client] cl_args.extend(args) return self._u_invoke(cl_args, **kwargs) + def _u_search_parent_directories(self, path, filename): """ Find the file (or directory) named filename in path or in any of path's parents. - + e.g. search_parent_directories("/a/b/c", ".be") will return the path to the first existing file from @@ -629,6 +783,7 @@ os.listdir(self.get_path("bugs")): or None if none of those files exist. """ return search_parent_directories(path, filename) + def _use_vcs(self, path, allow_no_vcs): """ Try and decide if _vcs_add/update/mkdir/etc calls will @@ -637,16 +792,17 @@ os.listdir(self.get_path("bugs")): """ use_vcs = True exception = None - if self.rootdir != None: + if self.repo != None: if self.path_in_root(path) == False: use_vcs = False - exception = PathNotInRoot(path, self.rootdir) + exception = InvalidPath(path, self.repo) else: use_vcs = False exception = VCSnotRooted if use_vcs == False and allow_no_vcs==False: raise exception return use_vcs + def path_in_root(self, path, root=None): """ Return the relative path to path from root. @@ -657,15 +813,16 @@ os.listdir(self.get_path("bugs")): False """ if root == None: - if self.rootdir == None: + if self.repo == None: raise VCSnotRooted - root = self.rootdir + root = self.repo path = os.path.abspath(path) absRoot = os.path.abspath(root) absRootSlashedDir = os.path.join(absRoot,"") if not path.startswith(absRootSlashedDir): return False return True + def _u_rel_path(self, path, root=None): """ Return the relative path to path from root. @@ -674,18 +831,19 @@ os.listdir(self.get_path("bugs")): '.be' """ if root == None: - if self.rootdir == None: + if self.repo == None: raise VCSnotRooted - root = self.rootdir + root = self.repo path = os.path.abspath(path) absRoot = os.path.abspath(root) absRootSlashedDir = os.path.join(absRoot,"") if not path.startswith(absRootSlashedDir): - raise PathNotInRoot(path, absRootSlashedDir) + raise InvalidPath(path, absRootSlashedDir) assert path != absRootSlashedDir, \ "file %s == root directory %s" % (path, absRootSlashedDir) relpath = path[len(absRootSlashedDir):] return relpath + def _u_abspath(self, path, root=None): """ Return the absolute path from a path realtive to root. @@ -694,60 +852,10 @@ os.listdir(self.get_path("bugs")): '/a.b/c/.be' """ if root == None: - assert self.rootdir != None, "VCS not rooted" - root = self.rootdir + assert self.repo != None, "VCS not rooted" + root = self.repo return os.path.abspath(os.path.join(root, path)) - def _u_create_id(self, name, email=None): - """ - >>> vcs = new() - >>> vcs._u_create_id("John Doe", "jdoe@example.com") - 'John Doe ' - >>> vcs._u_create_id("John Doe") - 'John Doe' - """ - assert len(name) > 0 - if email == None or len(email) == 0: - return name - else: - return "%s <%s>" % (name, email) - def _u_parse_id(self, value): - """ - >>> vcs = new() - >>> vcs._u_parse_id("John Doe ") - ('John Doe', 'jdoe@example.com') - >>> vcs._u_parse_id("John Doe") - ('John Doe', None) - >>> try: - ... vcs._u_parse_id("John Doe ") - ... except AssertionError: - ... print "Invalid match" - Invalid match - """ - emailexp = re.compile("(.*) <([^>]*)>(.*)") - match = emailexp.search(value) - if match == None: - email = None - name = value - else: - assert len(match.groups()) == 3 - assert match.groups()[2] == "", match.groups() - email = match.groups()[1] - name = match.groups()[0] - assert name != None - assert len(name) > 0 - return (name, email) - def _u_get_fallback_username(self): - name = None - for envariable in ["LOGNAME", "USERNAME"]: - if os.environ.has_key(envariable): - name = os.environ[envariable] - break - assert name != None - return name - def _u_get_fallback_email(self): - hostname = gethostname() - name = self._u_get_fallback_username() - return "%s@%s" % (name, hostname) + def _u_parse_commitfile(self, commitfile): """ Split the commitfile created in self.commit() back into @@ -763,9 +871,9 @@ os.listdir(self.get_path("bugs")): return (summary, body) def check_disk_version(self): - version = self.get_version() - if version != upgrade.BUGDIR_DISK_VERSION: - upgrade.upgrade(self.root, version) + version = self.version() + #if version != upgrade.BUGDIR_DISK_VERSION: + # upgrade.upgrade(self.repo, version) def disk_version(self, path=None, use_none_vcs=False, for_duplicate_bugdir=False): @@ -786,39 +894,17 @@ os.listdir(self.get_path("bugs")): if self.sync_with_disk == False: raise DiskAccessRequired("set version") self.vcs.mkdir(self.get_path()) - self.vcs.set_file_contents(self.get_path("version"), - upgrade.BUGDIR_DISK_VERSION+"\n") + #self.vcs.set_file_contents(self.get_path("version"), + # upgrade.BUGDIR_DISK_VERSION+"\n") + - if libbe.TESTING == True: - def setup_vcs_test_fixtures(testcase): - """Set up test fixtures for VCS test case.""" - testcase.vcs = testcase.Class() - testcase.dir = Dir() - testcase.dirname = testcase.dir.path - - vcs_not_supporting_uninitialized_user_id = [] - vcs_not_supporting_set_user_id = ["None", "hg"] - testcase.vcs_supports_uninitialized_user_id = ( - testcase.vcs.name not in vcs_not_supporting_uninitialized_user_id) - testcase.vcs_supports_set_user_id = ( - testcase.vcs.name not in vcs_not_supporting_set_user_id) - - if not testcase.vcs.installed(): - testcase.fail( - "%(name)s VCS not found" % vars(testcase.Class)) - - if testcase.Class.name != "None": - testcase.failIf( - testcase.vcs.detect(testcase.dirname), - "Detected %(name)s VCS before initialising" - % vars(testcase.Class)) - - testcase.vcs.init(testcase.dirname) - - class VCSTestCase(unittest.TestCase): - """Test cases for base VCS class.""" + class VCSTestCase (unittest.TestCase): + """ + Test cases for base VCS class (in addition to the Storage test + cases). + """ Class = VCS @@ -827,271 +913,102 @@ if libbe.TESTING == True: self.dirname = None def setUp(self): + """Set up test fixtures for Storage test case.""" super(VCSTestCase, self).setUp() - setup_vcs_test_fixtures(self) + self.dir = Dir() + self.dirname = self.dir.path + self.s = self.Class(repo=self.dirname) + if self.s.installed() == True: + self.s.init() + self.s.connect() def tearDown(self): - self.vcs.cleanup() - self.dir.cleanup() super(VCSTestCase, self).tearDown() + if self.s.installed() == True: + self.s.disconnect() + self.s.destroy() + self.dir.cleanup() - def full_path(self, rel_path): - return os.path.join(self.dirname, rel_path) - + def test_installed(self): + """ + See if the VCS is installed. + """ + self.failUnless(self.s.installed() == True, + '%(name)s VCS not found' % vars(self.Class)) - class VCS_init_TestCase(VCSTestCase): - """Test cases for VCS.init method.""" - def test_detect_should_succeed_after_init(self): - """Should detect VCS in directory after initialization.""" - self.failUnless( - self.vcs.detect(self.dirname), - "Did not detect %(name)s VCS after initialising" + class VCS_detection_TestCase (VCSTestCase): + def test_detection(self): + """ + See if the VCS detects its installed repository + """ + if self.s.installed(): + self.s.disconnect() + self.failUnless(self.s._detect(self.dirname) == True, + 'Did not detected %(name)s VCS after initialising' % vars(self.Class)) + self.s.connect() - def test_vcs_rootdir_in_specified_root_path(self): + def test_no_detection(self): + """ + See if the VCS detects its installed repository + """ + if self.s.installed() and self.Class.name != 'None': + self.s.disconnect() + self.s.destroy() + self.failUnless(self.s._detect(self.dirname) == False, + 'Detected %(name)s VCS before initialising' + % self.Class) + self.s.init() + self.s.connect() + + def test_vcs_repo_in_specified_root_path(self): """VCS root directory should be in specified root path.""" - rp = os.path.realpath(self.vcs.rootdir) + rp = os.path.realpath(self.s.repo) dp = os.path.realpath(self.dirname) vcs_name = self.Class.name self.failUnless( dp == rp or rp == None, "%(vcs_name)s VCS root in wrong dir (%(dp)s %(rp)s)" % vars()) - class VCS_get_user_id_TestCase(VCSTestCase): """Test cases for VCS.get_user_id method.""" def test_gets_existing_user_id(self): """Should get the existing user ID.""" - if not self.vcs_supports_uninitialized_user_id: - return - - user_id = self.vcs.get_user_id() - self.failUnless( - user_id is not None, - "unable to get a user id") - - - class VCS_set_user_id_TestCase(VCSTestCase): - """Test cases for VCS.set_user_id method.""" - - def setUp(self): - super(VCS_set_user_id_TestCase, self).setUp() - - if self.vcs_supports_uninitialized_user_id: - self.prev_user_id = self.vcs.get_user_id() - else: - self.prev_user_id = "Uninitialized identity " - - if self.vcs_supports_set_user_id: - self.test_new_user_id = "John Doe " - self.vcs.set_user_id(self.test_new_user_id) - - def tearDown(self): - if self.vcs_supports_set_user_id: - self.vcs.set_user_id(self.prev_user_id) - super(VCS_set_user_id_TestCase, self).tearDown() - - def test_raises_error_in_unsupported_vcs(self): - """Should raise an error in a VCS that doesn't support it.""" - if self.vcs_supports_set_user_id: + user_id = self.s.get_user_id() + if user_id == None: return - self.assertRaises( - SettingIDnotSupported, - self.vcs.set_user_id, "foo") - - def test_updates_user_id_in_supporting_vcs(self): - """Should update the user ID in an VCS that supports it.""" - if not self.vcs_supports_set_user_id: - return - user_id = self.vcs.get_user_id() - self.failUnlessEqual( - self.test_new_user_id, user_id, - "user id not set correctly (expected %s, got %s)" - % (self.test_new_user_id, user_id)) - - - def setup_vcs_revision_test_fixtures(testcase): - """Set up revision test fixtures for VCS test case.""" - testcase.test_dirs = ['a', 'a/b', 'c'] - for path in testcase.test_dirs: - testcase.vcs.mkdir(testcase.full_path(path)) - - testcase.test_files = ['a/text', 'a/b/text'] - - testcase.test_contents = { - 'rev_1': "Lorem ipsum", - 'uncommitted': "dolor sit amet", - } - - - class VCS_mkdir_TestCase(VCSTestCase): - """Test cases for VCS.mkdir method.""" - - def setUp(self): - super(VCS_mkdir_TestCase, self).setUp() - setup_vcs_revision_test_fixtures(self) - - def tearDown(self): - for path in reversed(sorted(self.test_dirs)): - self.vcs.recursive_remove(self.full_path(path)) - super(VCS_mkdir_TestCase, self).tearDown() - - def test_mkdir_creates_directory(self): - """Should create specified directory in filesystem.""" - for path in self.test_dirs: - full_path = self.full_path(path) - self.failUnless( - os.path.exists(full_path), - "path %(full_path)s does not exist" % vars()) - - - class VCS_commit_TestCase(VCSTestCase): - """Test cases for VCS.commit method.""" - - def setUp(self): - super(VCS_commit_TestCase, self).setUp() - setup_vcs_revision_test_fixtures(self) - - def tearDown(self): - for path in reversed(sorted(self.test_dirs)): - self.vcs.recursive_remove(self.full_path(path)) - super(VCS_commit_TestCase, self).tearDown() - - def test_file_contents_as_specified(self): - """Should set file contents as specified.""" - test_contents = self.test_contents['rev_1'] - for path in self.test_files: - full_path = self.full_path(path) - self.vcs.set_file_contents(full_path, test_contents) - current_contents = self.vcs.get_file_contents(full_path) - self.failUnlessEqual(test_contents, current_contents) - - def test_file_contents_as_committed(self): - """Should have file contents as specified after commit.""" - test_contents = self.test_contents['rev_1'] - for path in self.test_files: - full_path = self.full_path(path) - self.vcs.set_file_contents(full_path, test_contents) - revision = self.vcs.commit("Initial file contents.") - current_contents = self.vcs.get_file_contents(full_path) - self.failUnlessEqual(test_contents, current_contents) - - def test_file_contents_as_set_when_uncommitted(self): - """Should set file contents as specified after commit.""" - if not self.vcs.versioned: - return - for path in self.test_files: - full_path = self.full_path(path) - self.vcs.set_file_contents( - full_path, self.test_contents['rev_1']) - revision = self.vcs.commit("Initial file contents.") - self.vcs.set_file_contents( - full_path, self.test_contents['uncommitted']) - current_contents = self.vcs.get_file_contents(full_path) - self.failUnlessEqual( - self.test_contents['uncommitted'], current_contents) - - def test_revision_file_contents_as_committed(self): - """Should get file contents as committed to specified revision.""" - if not self.vcs.versioned: - return - for path in self.test_files: - full_path = self.full_path(path) - self.vcs.set_file_contents( - full_path, self.test_contents['rev_1']) - revision = self.vcs.commit("Initial file contents.") - self.vcs.set_file_contents( - full_path, self.test_contents['uncommitted']) - committed_contents = self.vcs.get_file_contents( - full_path, revision) - self.failUnlessEqual( - self.test_contents['rev_1'], committed_contents) - - def test_revision_id_as_committed(self): - """Check for compatibility between .commit() and .revision_id()""" - if not self.vcs.versioned: - self.failUnlessEqual(self.vcs.revision_id(5), None) - return - committed_revisions = [] - for path in self.test_files: - full_path = self.full_path(path) - self.vcs.set_file_contents( - full_path, self.test_contents['rev_1']) - revision = self.vcs.commit("Initial %s contents." % path) - committed_revisions.append(revision) - self.vcs.set_file_contents( - full_path, self.test_contents['uncommitted']) - revision = self.vcs.commit("Altered %s contents." % path) - committed_revisions.append(revision) - for i,revision in enumerate(committed_revisions): - self.failUnlessEqual(self.vcs.revision_id(i), revision) - i += -len(committed_revisions) # check negative indices - self.failUnlessEqual(self.vcs.revision_id(i), revision) - i = len(committed_revisions) - self.failUnlessEqual(self.vcs.revision_id(i), None) - self.failUnlessEqual(self.vcs.revision_id(-i-1), None) - - def test_revision_id_as_committed(self): - """Check revision id before first commit""" - if not self.vcs.versioned: - self.failUnlessEqual(self.vcs.revision_id(5), None) - return - committed_revisions = [] - for path in self.test_files: - self.failUnlessEqual(self.vcs.revision_id(0), None) - - - class VCS_duplicate_repo_TestCase(VCSTestCase): - """Test cases for VCS.duplicate_repo method.""" - - def setUp(self): - super(VCS_duplicate_repo_TestCase, self).setUp() - setup_vcs_revision_test_fixtures(self) - - def tearDown(self): - self.vcs.remove_duplicate_repo() - for path in reversed(sorted(self.test_dirs)): - self.vcs.recursive_remove(self.full_path(path)) - super(VCS_duplicate_repo_TestCase, self).tearDown() - - def test_revision_file_contents_as_committed(self): - """Should match file contents as committed to specified revision. - """ - if not self.vcs.versioned: - return - for path in self.test_files: - full_path = self.full_path(path) - self.vcs.set_file_contents( - full_path, self.test_contents['rev_1']) - revision = self.vcs.commit("Commit current status") - self.vcs.set_file_contents( - full_path, self.test_contents['uncommitted']) - dup_repo_path = self.vcs.duplicate_repo(revision) - dup_file_path = os.path.join(dup_repo_path, path) - dup_file_contents = file(dup_file_path, 'rb').read() - self.failUnlessEqual( - self.test_contents['rev_1'], dup_file_contents) - self.vcs.remove_duplicate_repo() - - - def make_vcs_testcase_subclasses(vcs_class, namespace): - """Make VCSTestCase subclasses for vcs_class in the namespace.""" - vcs_testcase_classes = [ - c for c in ( - ob for ob in globals().values() if isinstance(ob, type)) - if issubclass(c, VCSTestCase)] - - for base_class in vcs_testcase_classes: - testcase_class_name = vcs_class.__name__ + base_class.__name__ - testcase_class_bases = (base_class,) - testcase_class_dict = dict(base_class.__dict__) - testcase_class_dict['Class'] = vcs_class - testcase_class = type( - testcase_class_name, testcase_class_bases, testcase_class_dict) - setattr(namespace, testcase_class_name, testcase_class) - + name,email = libbe.ui.util.user.parse_user_id(user_id) + if email != None: + self.failUnless('@' in email, email) + + def make_vcs_testcase_subclasses(storage_class, namespace): + c = storage_class() + if c.versioned == True: + libbe.storage.base.make_versioned_storage_testcase_subclasses( + storage_class, namespace) + else: + libbe.storage.base.make_storage_testcase_subclasses( + storage_class, namespace) + + if namespace != sys.modules[__name__]: + # Make VCSTestCase subclasses for vcs_class in the namespace. + vcs_testcase_classes = [ + c for c in ( + ob for ob in globals().values() if isinstance(ob, type)) + if issubclass(c, VCSTestCase)] + + for base_class in vcs_testcase_classes: + testcase_class_name = vcs_class.__name__ + base_class.__name__ + testcase_class_bases = (base_class,) + testcase_class_dict = dict(base_class.__dict__) + testcase_class_dict['Class'] = vcs_class + testcase_class = type( + testcase_class_name, testcase_class_bases, testcase_class_dict) + setattr(namespace, testcase_class_name, testcase_class) + + make_vcs_testcase_subclasses(VCS, sys.modules[__name__]) unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) -- cgit From 6f23fccbde4ede1121d5baf35c81932b7c8aa7bb Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 07:20:31 -0500 Subject: Converted libbe.storage.vcs.hg to new Storage format. --- libbe/storage/vcs/base.py | 57 +++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 22 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 8c0ecf5..faa891a 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -83,10 +83,17 @@ def installed_vcs(): return _get_matching_vcs(lambda vcs: vcs.installed()) -class VCSnotRooted (libbe.storage.base.ConnectionError): - def __init__(self): +class VCSNotRooted (libbe.storage.base.ConnectionError): + def __init__(self, vcs): msg = 'VCS not rooted' libbe.storage.base.ConnectionError.__init__(self, msg) + self.vcs = vcs + +class VCSUnableToRoot (libbe.storage.base.ConnectionError): + def __init__(self, vcs): + msg = 'VCS unable to root' + libbe.storage.base.ConnectionError.__init__(self, msg) + self.vcs = vcs class InvalidPath (libbe.storage.base.InvalidID): def __init__(self, path, root, msg=None): @@ -589,7 +596,10 @@ os.listdir(self.get_path("bugs")): Set the root directory to the path's VCS root. This is the default working directory for future invocations. """ - self.repo = os.path.abspath(self._vcs_root(self.repo)) + root = self._vcs_root(self.repo) + if root == None: + raise VCSUnableToRoot(self) + self.repo = os.path.abspath(root) if os.path.isdir(self.repo) == False: self.repo = os.path.dirname(self.repo) self.be_dir = os.path.join( @@ -601,8 +611,8 @@ os.listdir(self.get_path("bugs")): Begin versioning the tree based at self.repo. Also roots the vcs at path. """ + self._vcs_init(self.repo) self.root() - self._vcs_init() os.mkdir(self.be_dir) self._vcs_add(self._u_rel_path(self.be_dir)) self._cached_path_id.init() @@ -798,7 +808,7 @@ os.listdir(self.get_path("bugs")): exception = InvalidPath(path, self.repo) else: use_vcs = False - exception = VCSnotRooted + exception = VCSNotRooted(self) if use_vcs == False and allow_no_vcs==False: raise exception return use_vcs @@ -814,7 +824,7 @@ os.listdir(self.get_path("bugs")): """ if root == None: if self.repo == None: - raise VCSnotRooted + raise VCSNotRooted(self) root = self.repo path = os.path.abspath(path) absRoot = os.path.abspath(root) @@ -832,7 +842,7 @@ os.listdir(self.get_path("bugs")): """ if root == None: if self.repo == None: - raise VCSnotRooted + raise VCSNotRooted(self) root = self.repo path = os.path.abspath(path) absRoot = os.path.abspath(root) @@ -929,6 +939,7 @@ if libbe.TESTING == True: self.s.destroy() self.dir.cleanup() + class VCS_installed_TestCase (VCSTestCase): def test_installed(self): """ See if the VCS is installed. @@ -976,21 +987,23 @@ if libbe.TESTING == True: def test_gets_existing_user_id(self): """Should get the existing user ID.""" - user_id = self.s.get_user_id() - if user_id == None: - return - name,email = libbe.ui.util.user.parse_user_id(user_id) - if email != None: - self.failUnless('@' in email, email) - - def make_vcs_testcase_subclasses(storage_class, namespace): - c = storage_class() - if c.versioned == True: - libbe.storage.base.make_versioned_storage_testcase_subclasses( - storage_class, namespace) - else: - libbe.storage.base.make_storage_testcase_subclasses( - storage_class, namespace) + if self.s.installed(): + user_id = self.s.get_user_id() + if user_id == None: + return + name,email = libbe.ui.util.user.parse_user_id(user_id) + if email != None: + self.failUnless('@' in email, email) + + def make_vcs_testcase_subclasses(vcs_class, namespace): + c = vcs_class() + if c.installed(): + if c.versioned == True: + libbe.storage.base.make_versioned_storage_testcase_subclasses( + vcs_class, namespace) + else: + libbe.storage.base.make_storage_testcase_subclasses( + vcs_class, namespace) if namespace != sys.modules[__name__]: # Make VCSTestCase subclasses for vcs_class in the namespace. -- cgit From 85aa14161c125f996f0d146b93f9cb06a0b3928f Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 07:26:37 -0500 Subject: Fixes to get libbe.storage.vcs.hg passing tests. --- libbe/storage/vcs/base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index faa891a..5d7be7d 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -202,7 +202,8 @@ class CachedPathID (object): self.disconnect() def destroy(self): - os.remove(self._cache_path) + if os.path.exists(self._cache_path): + os.remove(self._cache_path) def connect(self): if not os.path.exists(self._cache_path): @@ -969,7 +970,7 @@ if libbe.TESTING == True: self.s.destroy() self.failUnless(self.s._detect(self.dirname) == False, 'Detected %(name)s VCS before initialising' - % self.Class) + % vars(self.Class)) self.s.init() self.s.connect() -- cgit From 55e0abfa27b693768f495044d10444d9d92e4fca Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 07:39:55 -0500 Subject: More fixes for libbe.storage.vcs.hg + .git transition. --- libbe/storage/vcs/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 5d7be7d..5308813 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -621,7 +621,8 @@ os.listdir(self.get_path("bugs")): def _destroy(self): self._vcs_destroy() self._cached_path_id.destroy() - shutil.rmtree(self.be_dir) + if os.path.exists(self.be_dir): + shutil.rmtree(self.be_dir) def _connect(self): self.root() -- cgit From 9147ab9e77cd5730c1b2d5a76c92f87564f4af8e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 07:53:17 -0500 Subject: Use detect rather than catching errors in _vcs_root(). --- libbe/storage/vcs/base.py | 60 ++++++++--------------------------------------- 1 file changed, 10 insertions(+), 50 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 5308813..7d17b56 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -447,6 +447,7 @@ os.listdir(self.get_path("bugs")): self.versioned = False self.verbose_invoke = False self._cached_path_id = CachedPathID() + self._rooted = False def _vcs_version(self): """ @@ -597,15 +598,16 @@ os.listdir(self.get_path("bugs")): Set the root directory to the path's VCS root. This is the default working directory for future invocations. """ - root = self._vcs_root(self.repo) - if root == None: + if self._detect(self.repo) == False: raise VCSUnableToRoot(self) + root = self._vcs_root(self.repo) self.repo = os.path.abspath(root) if os.path.isdir(self.repo) == False: self.repo = os.path.dirname(self.repo) self.be_dir = os.path.join( self.repo, self._cached_path_id._spacer_dirs[0]) self._cached_path_id.root(self.repo) + self._rooted == True def _init(self): """ @@ -625,7 +627,8 @@ os.listdir(self.get_path("bugs")): shutil.rmtree(self.be_dir) def _connect(self): - self.root() + if self._rooted == False: + self.root() self._cached_path_id.connect() self.check_disk_version() @@ -796,45 +799,6 @@ os.listdir(self.get_path("bugs")): """ return search_parent_directories(path, filename) - def _use_vcs(self, path, allow_no_vcs): - """ - Try and decide if _vcs_add/update/mkdir/etc calls will - succeed. Returns True is we think the vcs_call would - succeeed, and False otherwise. - """ - use_vcs = True - exception = None - if self.repo != None: - if self.path_in_root(path) == False: - use_vcs = False - exception = InvalidPath(path, self.repo) - else: - use_vcs = False - exception = VCSNotRooted(self) - if use_vcs == False and allow_no_vcs==False: - raise exception - return use_vcs - - def path_in_root(self, path, root=None): - """ - Return the relative path to path from root. - >>> vcs = new() - >>> vcs.path_in_root("/a.b/c/.be", "/a.b/c") - True - >>> vcs.path_in_root("/a.b/.be", "/a.b/c") - False - """ - if root == None: - if self.repo == None: - raise VCSNotRooted(self) - root = self.repo - path = os.path.abspath(path) - absRoot = os.path.abspath(root) - absRootSlashedDir = os.path.join(absRoot,"") - if not path.startswith(absRootSlashedDir): - return False - return True - def _u_rel_path(self, path, root=None): """ Return the relative path to path from root. @@ -887,24 +851,20 @@ os.listdir(self.get_path("bugs")): #if version != upgrade.BUGDIR_DISK_VERSION: # upgrade.upgrade(self.repo, version) - def disk_version(self, path=None, use_none_vcs=False, - for_duplicate_bugdir=False): + def disk_version(self, path=None): """ Requires disk access. """ if path == None: - path = self.get_path("version") - allow_no_vcs = not VCS.path_in_root(path) - if allow_no_vcs == True: - assert for_duplicate_bugdir == True - return self.get(path, allow_no_vcs=allow_no_vcs).rstrip("\n") + path = self.get_path('version') + return self.get(path).rstrip('\n') def set_disk_version(self): """ Requires disk access. """ if self.sync_with_disk == False: - raise DiskAccessRequired("set version") + raise DiskAccessRequired('set version') self.vcs.mkdir(self.get_path()) #self.vcs.set_file_contents(self.get_path("version"), # upgrade.BUGDIR_DISK_VERSION+"\n") -- cgit From 1cd02476257a7673668f1bdcdeac2902f3f21adb Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 08:03:58 -0500 Subject: Adjust Hg._vcs_revision_id to bail cleanly on non-int revids --- libbe/storage/vcs/base.py | 1 + 1 file changed, 1 insertion(+) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 7d17b56..fffabf8 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -36,6 +36,7 @@ import tempfile import libbe import libbe.storage.base import libbe.util.encoding +from libbe.storage.base import EmptyCommit, InvalidRevision from libbe.util.utility import Dir, search_parent_directories from libbe.util.subproc import CommandError, invoke from libbe.util.plugin import import_by_name -- cgit From 9fa977a31982a2eda08c2c18ade73bd114f477b1 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 08:12:47 -0500 Subject: Handle non-int args to VCS.revision_id at the VCS level. --- libbe/storage/vcs/base.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index fffabf8..fc3427a 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -755,6 +755,11 @@ os.listdir(self.get_path("bugs")): def revision_id(self, index=None): if index == None: return None + try: + if int(index) != index: + raise InvalidRevision(index) + except ValueError: + raise InvalidRevision(index) revid = self._vcs_revision_id(index) if revid == None: raise libbe.storage.base.InvalidRevision(index) -- cgit From c83e48bb2e8ae304f629d7d6ae47fb97b5b325ff Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 20:35:14 -0500 Subject: Check for repo existence before initializing VCS --- libbe/storage/vcs/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index fc3427a..662fc30 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -481,9 +481,9 @@ os.listdir(self.get_path("bugs")): path = os.path.abspath('.') return path - def _vcs_init(self): + def _vcs_init(self, path): """ - Begin versioning the tree based at self.repo. + Begin versioning the tree based at path. """ pass @@ -615,6 +615,8 @@ os.listdir(self.get_path("bugs")): Begin versioning the tree based at self.repo. Also roots the vcs at path. """ + if not os.path.exists(self.repo) or not os.path.isdir(self.repo): + raise VCSUnableToRoot(self) self._vcs_init(self.repo) self.root() os.mkdir(self.be_dir) -- cgit From b1ddf38a32c49f7e90168014c9ec1efce86cf1fb Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 23:14:06 -0500 Subject: Moved Arch over to Storage format --- libbe/storage/vcs/base.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 662fc30..69e412e 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -446,6 +446,7 @@ os.listdir(self.get_path("bugs")): kwargs['encoding'] = libbe.util.encoding.get_filesystem_encoding() libbe.storage.base.VersionedStorage.__init__(self, *args, **kwargs) self.versioned = False + self.interspersed_vcs_files = False self.verbose_invoke = False self._cached_path_id = CachedPathID() self._rooted = False @@ -514,6 +515,18 @@ os.listdir(self.get_path("bugs")): """ pass + def _vcs_is_versioned(self, path): + """ + Return true if a path is under version control, False + otherwise. You only need to set this if the VCS goes about + dumping VCS-specific files into the .be directory. + + If you do need to implement this method (e.g. Arch), set + self.interspersed_vcs_files = True + """ + assert self.interspersed_vcs_files == False + raise NotImplementedError + def _vcs_get_file_contents(self, path, revision=None): """ Get the file contents as they were in a given revision. @@ -704,7 +717,11 @@ os.listdir(self.get_path("bugs")): for i,c in enumerate(children): if c == None: continue cpath = os.path.join(path, c) - children[i] = self._cached_path_id.id(cpath) + if self.interspersed_vcs_files == True \ + and self._vcs_is_versioned(cpath) == False: + children[i] = None + else: + children[i] = self._cached_path_id.id(cpath) return [c for c in children if c != None] def _get(self, id, default=libbe.util.InvalidObject, revision=None): -- cgit From 7addcc4aff8d391765b22d8f095afba739489511 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 23:25:07 -0500 Subject: The VCS storage backends are all mostly working now. Running python test.py libbe.storage.vcs yields some EmptyCommit problems, an issue with bzr revision ids, and some trouble with git's remove(), but nothing too critical. On the bright side, now ./be list Detects and uses the bzr backend :). Onwards to moving over the remaining commands... --- libbe/storage/vcs/base.py | 1 - 1 file changed, 1 deletion(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 69e412e..a765a80 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -66,7 +66,6 @@ def _get_matching_vcs(matchfn): vcs = module.new() if matchfn(vcs) == True: return vcs - vcs.cleanup() return VCS() def vcs_by_name(vcs_name): -- cgit From 2f0ceedba5b6619faf476cd1aa67e826e91d5c7c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 03:29:20 -0500 Subject: Transitioned init to Command format --- libbe/storage/vcs/base.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index a765a80..9ea38d3 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -629,7 +629,8 @@ os.listdir(self.get_path("bugs")): """ if not os.path.exists(self.repo) or not os.path.isdir(self.repo): raise VCSUnableToRoot(self) - self._vcs_init(self.repo) + if self._vcs_detect(self.repo) == False: + self._vcs_init(self.repo) self.root() os.mkdir(self.be_dir) self._vcs_add(self._u_rel_path(self.be_dir)) @@ -644,6 +645,8 @@ os.listdir(self.get_path("bugs")): def _connect(self): if self._rooted == False: self.root() + if not os.path.isdir(self.be_dir): + raise libbe.storage.base.ConnectionError(self) self._cached_path_id.connect() self.check_disk_version() @@ -732,9 +735,8 @@ os.listdir(self.get_path("bugs")): return default relpath = self._u_rel_path(path) contents = self._vcs_get_file_contents(relpath,revision) - if contents == libbe.storage.base.InvalidDirectory: - raise libbe.storage.base.InvalidDirectory(id) - elif contents == libbe.util.InvalidObject: + if contents in [libbe.storage.base.InvalidDirectory, + libbe.util.InvalidObject]: raise libbe.storage.base.InvalidID(id) elif len(contents) == 0: return None -- cgit From 19fe0817ba7c2cd04caea3adfa82d4490288a548 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 07:37:51 -0500 Subject: Transitioned comment to Command format --- libbe/storage/vcs/base.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 9ea38d3..768a85f 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -620,7 +620,7 @@ os.listdir(self.get_path("bugs")): self.be_dir = os.path.join( self.repo, self._cached_path_id._spacer_dirs[0]) self._cached_path_id.root(self.repo) - self._rooted == True + self._rooted = True def _init(self): """ @@ -631,7 +631,8 @@ os.listdir(self.get_path("bugs")): raise VCSUnableToRoot(self) if self._vcs_detect(self.repo) == False: self._vcs_init(self.repo) - self.root() + if self._rooted == False: + self.root() os.mkdir(self.be_dir) self._vcs_add(self._u_rel_path(self.be_dir)) self._cached_path_id.init() @@ -863,7 +864,7 @@ os.listdir(self.get_path("bugs")): Split the commitfile created in self.commit() back into summary and header lines. """ - f = codecs.open(commitfile, "r", self.encoding) + f = codecs.open(commitfile, 'r', self.encoding) summary = f.readline() body = f.read() body.lstrip('\n') -- cgit From 89b7a1411e4658e831f5d635534b24355dbb941d Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 15 Dec 2009 06:44:20 -0500 Subject: Fixed libbe.command.diff + ugly BugDir.duplicate_bugdir implementation duplicate_bugdir() works, but for the vcs backends, it could require shelling out for _every_ file read. This could, and probably will, be horribly slow. Still it works ;). I'm not sure what a better implementation would be. The old implementation checked out the entire earlier state into a temporary directory pros: single shell out, simple upgrade implementation cons: wouldn't work well for HTTP backens I think a good solution would run along the lines of the currently commented out code in duplicate_bugdir(), where a VersionedStorage.changed_since(revision) call would give you a list of changed files. diff could work off of that directly, without the need to generate a whole duplicate bugdir. I'm stuck on how to handle upgrades though... Also removed trailing whitespace from all python files. --- libbe/storage/vcs/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 768a85f..3bdb4ac 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -195,6 +195,9 @@ class CachedPathID (object): id = self.id(dirpath) relpath = dirpath[len(self._root)+1:] if id.count('/') == 0: + if id in self._cache: + import sys + print >> sys.stderr, 'Multiple paths for %s: \n %s\n %s' % (id, self._cache[id], relpath) self._cache[id] = relpath except InvalidPath: pass @@ -521,7 +524,7 @@ os.listdir(self.get_path("bugs")): dumping VCS-specific files into the .be directory. If you do need to implement this method (e.g. Arch), set - self.interspersed_vcs_files = True + self.interspersed_vcs_files = True """ assert self.interspersed_vcs_files == False raise NotImplementedError -- cgit From 214c4317bb90684dcfdab4d2402daa66fbad2e77 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 27 Dec 2009 15:58:29 -0500 Subject: Fixed libbe.storage.util.upgrade Note that it only upgrades on-disk versions, so you can't use a non-VCS storage backend whose version isn't your command's current storage version. See #bea/110/bd1# for reasoning. To see the on-disk storage version, look at .be/version To see your command's supported storage version, look at be --full-version I added test_upgrade.sh to exercise the upgrade mechanism on BE's own repository. --- libbe/storage/vcs/base.py | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 3bdb4ac..a45f1fe 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -40,7 +40,7 @@ from libbe.storage.base import EmptyCommit, InvalidRevision from libbe.util.utility import Dir, search_parent_directories from libbe.util.subproc import CommandError, invoke from libbe.util.plugin import import_by_name -#import libbe.storage.util.upgrade as upgrade +import libbe.storage.util.upgrade as upgrade if libbe.TESTING == True: import unittest @@ -657,8 +657,7 @@ os.listdir(self.get_path("bugs")): def disconnect(self): self._cached_path_id.disconnect() - def _add(self, id, parent=None, directory=False): - path = self._cached_path_id.add_id(id, parent) + def _add_path(self, path, directory=False): relpath = self._u_rel_path(path) reldirs = relpath.split(os.path.sep) if directory == False: @@ -676,6 +675,10 @@ os.listdir(self.get_path("bugs")): open(path, 'w').close() self._vcs_add(self._u_rel_path(path)) + def _add(self, id, parent=None, **kwargs): + path = self._cached_path_id.add_id(id, parent) + self._add_path(path, **kwargs) + def _remove(self, id): path = self._cached_path_id.path(id) if os.path.exists(path): @@ -877,27 +880,17 @@ os.listdir(self.get_path("bugs")): return (summary, body) def check_disk_version(self): - version = self.version() - #if version != upgrade.BUGDIR_DISK_VERSION: - # upgrade.upgrade(self.repo, version) + version = self.disk_version() + if version != upgrade.BUGDIR_DISK_VERSION: + upgrade.upgrade(self.repo, version) def disk_version(self, path=None): """ Requires disk access. """ if path == None: - path = self.get_path('version') - return self.get(path).rstrip('\n') - - def set_disk_version(self): - """ - Requires disk access. - """ - if self.sync_with_disk == False: - raise DiskAccessRequired('set version') - self.vcs.mkdir(self.get_path()) - #self.vcs.set_file_contents(self.get_path("version"), - # upgrade.BUGDIR_DISK_VERSION+"\n") + path = os.path.join(self.repo, '.be', 'version') + return libbe.util.encoding.get_file_contents(path).rstrip('\n') -- cgit From dff704764d77bffbf6cc94c5ba4bb03309da45f8 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 27 Dec 2009 16:30:54 -0500 Subject: Added storage.Storage.storage_version() and command.InvalidStorageVersion. Now commands automatically check for storage version compatibility. --- libbe/storage/vcs/base.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index a45f1fe..1df08cf 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -34,6 +34,7 @@ import sys import tempfile import libbe +import libbe.storage import libbe.storage.base import libbe.util.encoding from libbe.storage.base import EmptyCommit, InvalidRevision @@ -652,7 +653,7 @@ os.listdir(self.get_path("bugs")): if not os.path.isdir(self.be_dir): raise libbe.storage.base.ConnectionError(self) self._cached_path_id.connect() - self.check_disk_version() + self.check_storage_version() def disconnect(self): self._cached_path_id.disconnect() @@ -879,18 +880,19 @@ os.listdir(self.get_path("bugs")): f.close() return (summary, body) - def check_disk_version(self): - version = self.disk_version() - if version != upgrade.BUGDIR_DISK_VERSION: + def check_storage_version(self): + version = self.storage_version() + if version != libbe.storage.STORAGE_VERSION: upgrade.upgrade(self.repo, version) - def disk_version(self, path=None): + def storage_version(self, path=None): """ Requires disk access. """ if path == None: path = os.path.join(self.repo, '.be', 'version') - return libbe.util.encoding.get_file_contents(path).rstrip('\n') + return libbe.util.encoding.get_file_contents( + path, decode=True).rstrip('\n') -- cgit From cfebc238cbda9b6338ec57d5c215c4cbf0246f8b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 27 Dec 2009 16:50:36 -0500 Subject: Moved InvalidStorageVersion from libbe.command to libbe.storage Also added ConnectionError pretty-print to ui.command_line, storage version checking to BugDir.duplicate_bugdir(), and optional revision argument to Storage.storage_version(). --- libbe/storage/vcs/base.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 1df08cf..e96b466 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -32,6 +32,7 @@ import re import shutil import sys import tempfile +import types import libbe import libbe.storage @@ -885,15 +886,19 @@ os.listdir(self.get_path("bugs")): if version != libbe.storage.STORAGE_VERSION: upgrade.upgrade(self.repo, version) - def storage_version(self, path=None): + def storage_version(self, revision=None, path=None): """ Requires disk access. """ if path == None: path = os.path.join(self.repo, '.be', 'version') - return libbe.util.encoding.get_file_contents( - path, decode=True).rstrip('\n') - + if revision == None: # don't require connection + return libbe.util.encoding.get_file_contents( + path, decode=True).rstrip('\n') + contents = self._vcs_get_file_contents(path, revision=revision) + if type(contents) != types.UnicodeType: + contents = unicode(contents, self.encoding) + return contents.strip() if libbe.TESTING == True: -- cgit From e0c58cc0577fbb1b692e051eabd8597ba35c886a Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 28 Dec 2009 10:06:40 -0500 Subject: libbe.storage.vcs.base.VCS._init() now creates the '.be/version' file. And python test.py libbe.storage.vcs.base passes again. --- libbe/storage/vcs/base.py | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index e96b466..cfb39a1 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -38,7 +38,7 @@ import libbe import libbe.storage import libbe.storage.base import libbe.util.encoding -from libbe.storage.base import EmptyCommit, InvalidRevision +from libbe.storage.base import EmptyCommit, InvalidRevision, InvalidID from libbe.util.utility import Dir, search_parent_directories from libbe.util.subproc import CommandError, invoke from libbe.util.plugin import import_by_name @@ -97,11 +97,11 @@ class VCSUnableToRoot (libbe.storage.base.ConnectionError): libbe.storage.base.ConnectionError.__init__(self, msg) self.vcs = vcs -class InvalidPath (libbe.storage.base.InvalidID): +class InvalidPath (InvalidID): def __init__(self, path, root, msg=None): if msg == None: msg = 'Path "%s" not in root "%s"' % (path, root) - libbe.storage.base.InvalidID.__init__(self, msg) + InvalidID.__init__(self, msg) self.path = path self.root = root @@ -111,10 +111,10 @@ class SpacerCollision (InvalidPath): InvalidPath.__init__(self, path, root=None, msg=msg) self.spacer = spacer -class NoSuchFile (libbe.storage.base.InvalidID): +class NoSuchFile (InvalidID): def __init__(self, pathname, root='.'): path = os.path.abspath(os.path.join(root, pathname)) - libbe.storage.base.InvalidID.__init__(self, 'No such file: %s' % path) + InvalidID.__init__(self, 'No such file: %s' % path) class CachedPathID (object): @@ -198,7 +198,6 @@ class CachedPathID (object): relpath = dirpath[len(self._root)+1:] if id.count('/') == 0: if id in self._cache: - import sys print >> sys.stderr, 'Multiple paths for %s: \n %s\n %s' % (id, self._cache[id], relpath) self._cache[id] = relpath except InvalidPath: @@ -240,7 +239,7 @@ class CachedPathID (object): else: extra = fields[1:] if uuid not in self._cache: - raise libbe.storage.base.InvalidID(uuid) + raise InvalidID(uuid) if relpath == True: return os.path.join(self._cache[uuid], *extra) return os.path.join(self._root, self._cache[uuid], *extra) @@ -640,6 +639,7 @@ os.listdir(self.get_path("bugs")): self.root() os.mkdir(self.be_dir) self._vcs_add(self._u_rel_path(self.be_dir)) + self._setup_storage_version() self._cached_path_id.init() def _destroy(self): @@ -723,7 +723,7 @@ os.listdir(self.get_path("bugs")): children[i] = None children.extend([os.path.join(c, c2) for c2 in os.listdir(os.path.join(path, c))]) - elif c == 'id-cache': + elif c in ['id-cache', 'version']: children[i] = None for i,c in enumerate(children): if c == None: continue @@ -738,15 +738,18 @@ os.listdir(self.get_path("bugs")): def _get(self, id, default=libbe.util.InvalidObject, revision=None): try: path = self._cached_path_id.path(id) - except libbe.storage.base.InvalidID, e: + except InvalidID, e: if default == libbe.util.InvalidObject: raise e return default relpath = self._u_rel_path(path) - contents = self._vcs_get_file_contents(relpath,revision) + try: + contents = self._vcs_get_file_contents(relpath,revision) + except InvalidID, e: + raise InvalidID(id) if contents in [libbe.storage.base.InvalidDirectory, libbe.util.InvalidObject]: - raise libbe.storage.base.InvalidID(id) + raise InvalidID(id) elif len(contents) == 0: return None return contents @@ -754,10 +757,10 @@ os.listdir(self.get_path("bugs")): def _set(self, id, value): try: path = self._cached_path_id.path(id) - except libbe.storage.base.InvalidID, e: + except InvalidID, e: raise e if not os.path.exists(path): - raise libbe.storage.base.InvalidID(id) + raise InvalidID(id) if os.path.isdir(path): raise libbe.storage.base.InvalidDirectory(id) f = open(path, "wb") @@ -892,6 +895,8 @@ os.listdir(self.get_path("bugs")): """ if path == None: path = os.path.join(self.repo, '.be', 'version') + if not os.path.exists(path): + raise libbe.storage.InvalidStorageVersion(None) if revision == None: # don't require connection return libbe.util.encoding.get_file_contents( path, decode=True).rstrip('\n') @@ -900,6 +905,17 @@ os.listdir(self.get_path("bugs")): contents = unicode(contents, self.encoding) return contents.strip() + def _setup_storage_version(self): + """ + Requires disk access. + """ + assert self._rooted == True + path = os.path.join(self.be_dir, 'version') + if not os.path.exists(path): + libbe.util.encoding.set_file_contents(path, + libbe.storage.STORAGE_VERSION+'\n') + self._vcs_add(self._u_rel_path(path)) + if libbe.TESTING == True: class VCSTestCase (unittest.TestCase): -- cgit From c90044dff5feaf5f43fee9e8559fecec2ec60091 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 28 Dec 2009 12:30:19 -0500 Subject: Fixed VCS.children() and Bzr.children() for non-None revisions. Now they both pass VersionedStorage_commit_TestCase.test_commit_revision_ids() The .children() implementation for previous revisions lacks the working directory's id<->path cache, so it's fairly slow... --- libbe/storage/vcs/base.py | 78 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 10 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index cfb39a1..3b66019 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -98,10 +98,10 @@ class VCSUnableToRoot (libbe.storage.base.ConnectionError): self.vcs = vcs class InvalidPath (InvalidID): - def __init__(self, path, root, msg=None): + def __init__(self, path, root, msg=None, **kwargs): if msg == None: msg = 'Path "%s" not in root "%s"' % (path, root) - InvalidID.__init__(self, msg) + InvalidID.__init__(self, msg=msg, **kwargs) self.path = path self.root = root @@ -277,9 +277,9 @@ class CachedPathID (object): self._changed = True def id(self, path): - path = os.path.abspath(path) + path = os.path.join(self._root, path) if not path.startswith(self._root + os.path.sep): - raise InvalidPath('Path %s not in root %s' % (path, self._root)) + raise InvalidPath(path, self._root) path = path[len(self._root)+1:] orig_path = path if not path.startswith(self._spacer_dirs[0] + os.path.sep): @@ -548,6 +548,33 @@ os.listdir(self.get_path("bugs")): f.close() return contents + def _vcs_path(self, id, revision): + """ + Return the path to object id as of revision. + + Revision will not be None. + """ + raise NotImplementedError + + def _vcs_isdir(self, path, revision): + """ + Return True if path (as returned by _vcs_path) was a directory + as of revision, False otherwise. + + Revision will not be None. + """ + raise NotImplementedError + + def _vcs_listdir(self, path, revision): + """ + Return a list of the contents of the directory path (as + returned by _vcs_path) as of revision. + + Revision will not be None, and ._vcs_isdir(path, revision) + will be True. + """ + raise NotImplementedError + def _vcs_commit(self, commitfile, allow_empty=False): """ Commit the current working directory, using the contents of @@ -711,28 +738,40 @@ os.listdir(self.get_path("bugs")): self._cached_path_id.remove_id(id) def _children(self, id=None, revision=None): + if revision == None: + id_to_path = self._cached_path_id.path + path_to_id = self._cached_path_id.id + isdir = os.path.isdir + listdir = os.listdir + else: + id_to_path = lambda id : self._vcs_path(id, revision) + path_to_id = self._cached_path_id.id + isdir = lambda path : self._vcs_isdir(path, revision) + listdir = lambda path : self._vcs_listdir(path, revision) if id==None: path = self.be_dir else: - path = self._cached_path_id.path(id) - if os.path.isdir(path) == False: + path = id_to_path(id) + if isdir(path) == False: return [] - children = os.listdir(path) + children = listdir(path) for i,c in enumerate(children): if c in self._cached_path_id._spacer_dirs: children[i] = None children.extend([os.path.join(c, c2) for c2 in - os.listdir(os.path.join(path, c))]) + listdir(os.path.join(path, c))]) elif c in ['id-cache', 'version']: children[i] = None for i,c in enumerate(children): if c == None: continue cpath = os.path.join(path, c) if self.interspersed_vcs_files == True \ + and revision != None \ and self._vcs_is_versioned(cpath) == False: children[i] = None else: - children[i] = self._cached_path_id.id(cpath) + children[i] = path_to_id(cpath) + children[i] return [c for c in children if c != None] def _get(self, id, default=libbe.util.InvalidObject, revision=None): @@ -746,7 +785,7 @@ os.listdir(self.get_path("bugs")): try: contents = self._vcs_get_file_contents(relpath,revision) except InvalidID, e: - raise InvalidID(id) + raise InvalidPath(path=path, root=self.repo, id=id) if contents in [libbe.storage.base.InvalidDirectory, libbe.util.InvalidObject]: raise InvalidID(id) @@ -837,6 +876,25 @@ os.listdir(self.get_path("bugs")): """ return search_parent_directories(path, filename) + def _u_find_id(self, id, revision): + """ + Search for the relative path to id as of revision. + Returns None if the id is not found. + """ + assert self._rooted == True + be_dir = self._cached_path_id._spacer_dirs[0] + stack = [(be_dir, be_dir)] + while len(stack) > 0: + path,long_id = stack.pop() + if long_id.endswith('/'+id): + return path + if self._vcs_isdir(path, revision) == False: + continue + for child in self._vcs_listdir(path, revision): + stack.append((os.path.join(path, child), + '/'.join([long_id, child]))) + return None + def _u_rel_path(self, path, root=None): """ Return the relative path to path from root. -- cgit From e0e7328742b92cb5e08aeec348fce966375d7d52 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 28 Dec 2009 13:22:27 -0500 Subject: Updated Git backend to support .children(revision). + some minor fixes to vcs/base.py and vcs/bzr.py Also removed .be/id-cache, which should never have been versioned in the first place. --- libbe/storage/vcs/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 3b66019..8a8b3ca 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -783,9 +783,11 @@ os.listdir(self.get_path("bugs")): return default relpath = self._u_rel_path(path) try: - contents = self._vcs_get_file_contents(relpath,revision) + contents = self._vcs_get_file_contents(relpath, revision) except InvalidID, e: - raise InvalidPath(path=path, root=self.repo, id=id) + if InvalidID == None: + e.id = InvalidID + raise if contents in [libbe.storage.base.InvalidDirectory, libbe.util.InvalidObject]: raise InvalidID(id) -- cgit From aba3a8b27063a1765c49194cb7f9aba8b277d92f Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 06:36:23 -0500 Subject: Updated Hg backend to support .children(revision). --- libbe/storage/vcs/base.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 8a8b3ca..040c3f9 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -740,12 +740,10 @@ os.listdir(self.get_path("bugs")): def _children(self, id=None, revision=None): if revision == None: id_to_path = self._cached_path_id.path - path_to_id = self._cached_path_id.id isdir = os.path.isdir listdir = os.listdir else: id_to_path = lambda id : self._vcs_path(id, revision) - path_to_id = self._cached_path_id.id isdir = lambda path : self._vcs_isdir(path, revision) listdir = lambda path : self._vcs_listdir(path, revision) if id==None: @@ -770,7 +768,7 @@ os.listdir(self.get_path("bugs")): and self._vcs_is_versioned(cpath) == False: children[i] = None else: - children[i] = path_to_id(cpath) + children[i] = self._u_path_to_id(cpath) children[i] return [c for c in children if c != None] @@ -895,7 +893,10 @@ os.listdir(self.get_path("bugs")): for child in self._vcs_listdir(path, revision): stack.append((os.path.join(path, child), '/'.join([long_id, child]))) - return None + raise InvalidID(id, revision=revision) + + def _u_path_to_id(self, path): + return self._cached_path_id.id(path) def _u_rel_path(self, path, root=None): """ -- cgit From 6dafff3ad4a88d8af7b1cd4837b90179ac34a1e3 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 09:46:59 -0500 Subject: Added root directory handling to VCS._u_rel_path(). Now it returns '.' when you ask for the relative path from root to itself. It used to raise AssertionError or InvalidPath. --- libbe/storage/vcs/base.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 040c3f9..7565caf 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -904,6 +904,10 @@ os.listdir(self.get_path("bugs")): >>> vcs = new() >>> vcs._u_rel_path("/a.b/c/.be", "/a.b/c") '.be' + >>> vcs._u_rel_path("/a.b/c/", "/a.b/c") + '.' + >>> vcs._u_rel_path("/a.b/c/", "/a.b/c/") + '.' """ if root == None: if self.repo == None: @@ -912,10 +916,10 @@ os.listdir(self.get_path("bugs")): path = os.path.abspath(path) absRoot = os.path.abspath(root) absRootSlashedDir = os.path.join(absRoot,"") + if path in [absRoot, absRootSlashedDir]: + return '.' if not path.startswith(absRootSlashedDir): raise InvalidPath(path, absRootSlashedDir) - assert path != absRootSlashedDir, \ - "file %s == root directory %s" % (path, absRootSlashedDir) relpath = path[len(absRootSlashedDir):] return relpath @@ -1093,4 +1097,5 @@ if libbe.TESTING == True: make_vcs_testcase_subclasses(VCS, sys.modules[__name__]) unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) - suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) + #suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) + suite = unittest.TestSuite([doctest.DocTestSuite()]) -- cgit From b2d5da700c83f693191573c300aee1ffb80a8e98 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 10:18:20 -0500 Subject: Added an additional VCS._u_rel_path() unittest. Also re-enabled the unitsuite in libbe.storage.vcs.base, which I'd disabled while testing the VCS unittests. --- libbe/storage/vcs/base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 7565caf..533ef4c 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -908,6 +908,8 @@ os.listdir(self.get_path("bugs")): '.' >>> vcs._u_rel_path("/a.b/c/", "/a.b/c/") '.' + >>> vcs._u_rel_path("./a", ".") + 'a' """ if root == None: if self.repo == None: @@ -1097,5 +1099,4 @@ if libbe.TESTING == True: make_vcs_testcase_subclasses(VCS, sys.modules[__name__]) unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) - #suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) - suite = unittest.TestSuite([doctest.DocTestSuite()]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) -- cgit From b1540a08131173ace920f2d3d0829e54b8f26283 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 21:12:12 -0500 Subject: Fixed make_*_testcase_subclasses() to avoid duplication. Also removed final check for 'parent' existence in Storage_add_remove_TestCase.test_remove_nonrooted() because some VCSs (e.g. Git) don't keep track of blank directories. --- libbe/storage/vcs/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 533ef4c..6ece16d 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -1085,7 +1085,8 @@ if libbe.TESTING == True: vcs_testcase_classes = [ c for c in ( ob for ob in globals().values() if isinstance(ob, type)) - if issubclass(c, VCSTestCase)] + if issubclass(c, VCSTestCase) \ + and c.Class == VCS] for base_class in vcs_testcase_classes: testcase_class_name = vcs_class.__name__ + base_class.__name__ -- cgit From 07ee90254ce64ec734dacebea715a0b7a24599af Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 21:17:39 -0500 Subject: Use ._vcs_is_versioned() in VCS._children() Otherwise Arch will return '.arch-ids' in its list, etc. --- libbe/storage/vcs/base.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 6ece16d..b47ed2f 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -760,6 +760,9 @@ os.listdir(self.get_path("bugs")): listdir(os.path.join(path, c))]) elif c in ['id-cache', 'version']: children[i] = None + elif self.interspersed_vcs_files \ + and self._vcs_is_versioned(c) == False: + children[i] = None for i,c in enumerate(children): if c == None: continue cpath = os.path.join(path, c) -- cgit From 0d31c5a14719ed8f18b1554b3975690aea81c719 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 31 Dec 2009 11:47:33 -0500 Subject: Track connection status to allow multiple Storage.disconnect() calls. This makes cleaning up UIs easier: just call disconnect() :p. --- libbe/storage/vcs/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index b47ed2f..99f43f3 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -683,7 +683,7 @@ os.listdir(self.get_path("bugs")): self._cached_path_id.connect() self.check_storage_version() - def disconnect(self): + def _disconnect(self): self._cached_path_id.disconnect() def _add_path(self, path, directory=False): -- cgit From 4d4283ecd654f1efb058cd7f7dba6be88b70ee92 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 1 Jan 2010 08:11:08 -0500 Subject: Updated copyright information --- libbe/storage/vcs/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 99f43f3..39f5082 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -1,4 +1,4 @@ -# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. # Alexander Belchenko # Ben Finney # Chris Ball -- cgit From d313d3651ae5875fda86491e693e1963d2de91a5 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 1 Jan 2010 14:41:01 -0500 Subject: Hand nonexistent paths in VCS._u_search_parent_directories(). search_parent_directries raises an AssertionError if the original path doesn't exist. --- libbe/storage/vcs/base.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 39f5082..e0d3170 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -877,7 +877,11 @@ os.listdir(self.get_path("bugs")): /.be or None if none of those files exist. """ - return search_parent_directories(path, filename) + try: + ret = search_parent_directories(path, filename) + except AssertionError, e: + return None + return ret def _u_find_id(self, id, revision): """ -- cgit From 0679e0b1fd941d6aefef146fb4289472edee9b62 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 1 Jan 2010 16:33:39 -0500 Subject: Use more kwargs in libbe.command.serve kwargs make things easier to maintain. Also make sure the .handle_*() methods return two items (content,ctype) even when both are None. --- libbe/storage/vcs/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index e0d3170..8390cbc 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -800,7 +800,7 @@ os.listdir(self.get_path("bugs")): try: path = self._cached_path_id.path(id) except InvalidID, e: - raise e + raise if not os.path.exists(path): raise InvalidID(id) if os.path.isdir(path): -- cgit From ed6e3707a45804a282601ab9ec1ac2b5c8ef47c0 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 18 Jan 2010 08:06:08 -0500 Subject: Add .changed() support to VCS --- libbe/storage/vcs/base.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 8390cbc..e837780 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -597,6 +597,14 @@ os.listdir(self.get_path("bugs")): """ return None + def _vcs_changed(self, revision): + """ + Return a tuple of lists of ids + (new, modified, removed) + from the specified revision to the current situation. + """ + return ([], [], []) + def version(self): # Cache version string for efficiency. if not hasattr(self, '_version'): @@ -839,6 +847,13 @@ os.listdir(self.get_path("bugs")): raise libbe.storage.base.InvalidRevision(index) return revid + def changed(self, revision): + new,mod,rem = self._vcs_changed(revision) + new = [self._u_path_to_id(p) for p in new] + mod = [self._u_path_to_id(p) for p in mod] + rem = [self._u_path_to_id(p) for p in rem] + return (new, mod, rem) + def _u_any_in_string(self, list, string): """ Return True if any of the strings in list are in string. -- cgit From ba583f2af95291bf210da819978810dbbb9bfb56 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 18 Jan 2010 09:26:53 -0500 Subject: Ignore paths with _u_path_to_id errors in VCS.changed --- libbe/storage/vcs/base.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index e837780..1719f06 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -363,7 +363,7 @@ class VCS (libbe.storage.base.VersionedStorage): BugDir to search for an installed Storage backend and initialize it in the root directory. This is a convenience option for supporting tests of versioning functionality - (e.g. .duplicate_bugdir). + (e.g. RevisionedBugDir). Disable encoding manipulation ============================= @@ -849,10 +849,17 @@ os.listdir(self.get_path("bugs")): def changed(self, revision): new,mod,rem = self._vcs_changed(revision) - new = [self._u_path_to_id(p) for p in new] - mod = [self._u_path_to_id(p) for p in mod] - rem = [self._u_path_to_id(p) for p in rem] - return (new, mod, rem) + def paths_to_ids(paths): + for p in paths: + try: + id = self._u_path_to_id(p) + yield id + except (SpacerCollision, InvalidPath): + pass + new_id = list(paths_to_ids(new)) + mod_id = list(paths_to_ids(mod)) + rem_id = list(paths_to_ids(rem)) + return (new_id, mod_id, rem_id) def _u_any_in_string(self, list, string): """ -- cgit From 8e0e670cb788d941d3ce109da41d1d4491c85032 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 18 Jan 2010 10:10:30 -0500 Subject: Added VCS._ancestors --- libbe/storage/vcs/base.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 1719f06..692064c 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -745,6 +745,27 @@ os.listdir(self.get_path("bugs")): if p.startswith(path): self._cached_path_id.remove_id(id) + def _ancestors(self, id=None, revision=None): + if revision == None: + id_to_path = self._cached_path_id.path + else: + id_to_path = lambda id : self._vcs_path(id, revision) + if id==None: + path = self.be_dir + else: + path = id_to_path(id) + ancestors = [] + while True: + if path == self.repo: + break + path = os.path.dirname(path) + try: + id = self._u_path_to_id(path) + ancestors.append(id) + except (SpacerCollision, InvalidPath): + pass + return ancestors + def _children(self, id=None, revision=None): if revision == None: id_to_path = self._cached_path_id.path -- cgit From c7945daa3e1413b7c789df182b39c12dfbe2b4db Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 18 Jan 2010 10:12:25 -0500 Subject: Adjust VCSTestCase method docstrings for unittest. --- libbe/storage/vcs/base.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 692064c..716283a 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -1066,8 +1066,7 @@ if libbe.TESTING == True: class VCS_installed_TestCase (VCSTestCase): def test_installed(self): - """ - See if the VCS is installed. + """See if the VCS is installed. """ self.failUnless(self.s.installed() == True, '%(name)s VCS not found' % vars(self.Class)) @@ -1075,8 +1074,7 @@ if libbe.TESTING == True: class VCS_detection_TestCase (VCSTestCase): def test_detection(self): - """ - See if the VCS detects its installed repository + """See if the VCS detects its installed repository """ if self.s.installed(): self.s.disconnect() @@ -1086,8 +1084,7 @@ if libbe.TESTING == True: self.s.connect() def test_no_detection(self): - """ - See if the VCS detects its installed repository + """See if the VCS detects its installed repository """ if self.s.installed() and self.Class.name != 'None': self.s.disconnect() -- cgit From 7ae29f930fe73adada5174a2ce74266411809ac7 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 18 Jan 2010 12:25:17 -0500 Subject: Added VCS._u_find_id_from_manifest for faster id->path calculation --- libbe/storage/vcs/base.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 716283a..fa64b4e 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -756,7 +756,7 @@ os.listdir(self.get_path("bugs")): path = id_to_path(id) ancestors = [] while True: - if path == self.repo: + if not path.startswith(self.repo + os.path.sep): break path = os.path.dirname(path) try: @@ -926,6 +926,33 @@ os.listdir(self.get_path("bugs")): return None return ret + def _u_find_id_from_manifest(self, id, manifest, revision=None): + """ + Search for the relative path to id using manifest, a list of all files. + + Returns None if the id is not found. + """ + be_dir = self._cached_path_id._spacer_dirs[0] + be_dir_sep = self._cached_path_id._spacer_dirs[0] + os.path.sep + files = [f for f in manifest if f.startswith(be_dir_sep)] + for file in files: + if not file.startswith(be_dir+os.path.sep): + continue + parts = file.split(os.path.sep) + dir = parts.pop(0) # don't add the first spacer dir + for part in parts[:-1]: + dir = os.path.join(dir, part) + if not dir in files: + files.append(dir) + for file in files: + try: + p_id = self._u_path_to_id(file) + if p_id == id: + return file + except (SpacerCollision, InvalidPath): + pass + raise InvalidID(id, revision=revision) + def _u_find_id(self, id, revision): """ Search for the relative path to id as of revision. -- cgit From 7c1df98108a94f14c7bdfa635f489aff7c1f52af Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 18 Jan 2010 12:46:56 -0500 Subject: Fix VCS doctest for +revision InvalidID error message --- libbe/storage/vcs/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index fa64b4e..aa2bbaf 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -168,7 +168,7 @@ class CachedPathID (object): >>> c.path('qrs') Traceback (most recent call last): ... - InvalidID: 'qrs' + InvalidID: qrs in revision None >>> c.disconnect() >>> c.destroy() >>> dir.cleanup() -- cgit From d616494d67e5d50f85fccf12c2e679389f7445e1 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 19 Jan 2010 09:02:45 -0500 Subject: Better error messages in VCS._get --- libbe/storage/vcs/base.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index aa2bbaf..15460b0 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -815,12 +815,14 @@ os.listdir(self.get_path("bugs")): try: contents = self._vcs_get_file_contents(relpath, revision) except InvalidID, e: - if InvalidID == None: - e.id = InvalidID + if e.id == None: + e.id = id + if e.revision == None: + e.revision = revision raise if contents in [libbe.storage.base.InvalidDirectory, libbe.util.InvalidObject]: - raise InvalidID(id) + raise InvalidID(id, revision) elif len(contents) == 0: return None return contents -- cgit From 449d80de4bb035542498623133b9cd347e7cfe13 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 19 Jan 2010 09:43:37 -0500 Subject: Use relative paths *._vcs_* methods. --- libbe/storage/vcs/base.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 15460b0..83be287 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -773,8 +773,10 @@ os.listdir(self.get_path("bugs")): listdir = os.listdir else: id_to_path = lambda id : self._vcs_path(id, revision) - isdir = lambda path : self._vcs_isdir(path, revision) - listdir = lambda path : self._vcs_listdir(path, revision) + isdir = lambda path : self._vcs_isdir( + self._u_rel_path(path), revision) + listdir = lambda path : self._vcs_listdir( + self._u_rel_path(path), revision) if id==None: path = self.be_dir else: @@ -1046,7 +1048,8 @@ os.listdir(self.get_path("bugs")): if revision == None: # don't require connection return libbe.util.encoding.get_file_contents( path, decode=True).rstrip('\n') - contents = self._vcs_get_file_contents(path, revision=revision) + relpath = self._u_rel_path(path) + contents = self._vcs_get_file_contents(relpath, revision=revision) if type(contents) != types.UnicodeType: contents = unicode(contents, self.encoding) return contents.strip() -- cgit From 4437e500b4ad6bf7c007d8207928b3b1b0c01d3c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 20 Jan 2010 09:06:58 -0500 Subject: Fix _u_rel_path problems in VCS._children --- libbe/storage/vcs/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 83be287..9fc43c1 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -749,7 +749,8 @@ os.listdir(self.get_path("bugs")): if revision == None: id_to_path = self._cached_path_id.path else: - id_to_path = lambda id : self._vcs_path(id, revision) + id_to_path = lambda id : os.path.join( + self.repo, self._vcs_path(id, revision)) if id==None: path = self.be_dir else: @@ -772,7 +773,8 @@ os.listdir(self.get_path("bugs")): isdir = os.path.isdir listdir = os.listdir else: - id_to_path = lambda id : self._vcs_path(id, revision) + id_to_path = lambda id : os.path.join( + self.repo, self._vcs_path(id, revision)) isdir = lambda path : self._vcs_isdir( self._u_rel_path(path), revision) listdir = lambda path : self._vcs_listdir( -- cgit From aca616cef21fb7938e1aeb3edd87c96461476150 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 22 Jan 2010 13:54:53 -0500 Subject: Add VCS._exists(), VCS.path(). Fix default handling in VCS._get(). VCS.path() consolidates a bunch of distributed code. The VCS backend cannot distinguish between _EMPTY and '' entry values, so it assumes len(contents) == 0 means _EMPTY. However, it had been returing None then, not default like its supposed to. --- libbe/storage/vcs/base.py | 62 ++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 25 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 9fc43c1..0ef35ad 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -504,6 +504,12 @@ os.listdir(self.get_path("bugs")): """ pass + def _vcs_exists(self, path, revision=None): + """ + Does the path exist in a given revision? (True/False) + """ + pass + def _vcs_remove(self, path): """ Remove the file at path from version control. Optionally @@ -550,7 +556,7 @@ os.listdir(self.get_path("bugs")): def _vcs_path(self, id, revision): """ - Return the path to object id as of revision. + Return the relative path to object id as of revision. Revision will not be None. """ @@ -694,6 +700,17 @@ os.listdir(self.get_path("bugs")): def _disconnect(self): self._cached_path_id.disconnect() + def path(self, id, revision=None, relpath=True): + if revision == None: + path = self._cached_path_id.path(id) + if relpath == True: + return self._u_rel_path(path) + return path + path = self._vcs_path(id, revision) + if relpath == True: + return path + return os.path.join(self.repo, path) + def _add_path(self, path, directory=False): relpath = self._u_rel_path(path) reldirs = relpath.split(os.path.sep) @@ -716,6 +733,16 @@ os.listdir(self.get_path("bugs")): path = self._cached_path_id.add_id(id, parent) self._add_path(path, **kwargs) + def _exists(self, id, revision=None): + if revision == None: + try: + path = self.path(id, revision, relpath=False) + except InvalidID, e: + return False + return os.path.exists(path) + path = self.path(id, revision, relpath=True) + return self._vcs_exists(relpath, revision) + def _remove(self, id): path = self._cached_path_id.path(id) if os.path.exists(path): @@ -746,15 +773,10 @@ os.listdir(self.get_path("bugs")): self._cached_path_id.remove_id(id) def _ancestors(self, id=None, revision=None): - if revision == None: - id_to_path = self._cached_path_id.path - else: - id_to_path = lambda id : os.path.join( - self.repo, self._vcs_path(id, revision)) if id==None: path = self.be_dir else: - path = id_to_path(id) + path = self.path(id, revision, relpath=False) ancestors = [] while True: if not path.startswith(self.repo + os.path.sep): @@ -769,12 +791,9 @@ os.listdir(self.get_path("bugs")): def _children(self, id=None, revision=None): if revision == None: - id_to_path = self._cached_path_id.path isdir = os.path.isdir listdir = os.listdir else: - id_to_path = lambda id : os.path.join( - self.repo, self._vcs_path(id, revision)) isdir = lambda path : self._vcs_isdir( self._u_rel_path(path), revision) listdir = lambda path : self._vcs_listdir( @@ -782,7 +801,7 @@ os.listdir(self.get_path("bugs")): if id==None: path = self.be_dir else: - path = id_to_path(id) + path = self.path(id, revision, relpath=False) if isdir(path) == False: return [] children = listdir(path) @@ -810,25 +829,18 @@ os.listdir(self.get_path("bugs")): def _get(self, id, default=libbe.util.InvalidObject, revision=None): try: - path = self._cached_path_id.path(id) + relpath = self.path(id, revision, relpath=True) + contents = self._vcs_get_file_contents(relpath, revision) except InvalidID, e: if default == libbe.util.InvalidObject: raise e return default - relpath = self._u_rel_path(path) - try: - contents = self._vcs_get_file_contents(relpath, revision) - except InvalidID, e: - if e.id == None: - e.id = id - if e.revision == None: - e.revision = revision - raise if contents in [libbe.storage.base.InvalidDirectory, - libbe.util.InvalidObject]: - raise InvalidID(id, revision) - elif len(contents) == 0: - return None + libbe.util.InvalidObject] \ + or len(contents) == 0: + if default == libbe.util.InvalidObject: + raise InvalidID(id, revision) + return default return contents def _set(self, id, value): -- cgit From 355219426b8577123ea3db2d6b7247ec4fc085ea Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 22 Jan 2010 14:13:08 -0500 Subject: We don't need VCS._vcs_exists yet, with exists only used in _add --- libbe/storage/vcs/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 0ef35ad..7d4383f 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -508,7 +508,7 @@ os.listdir(self.get_path("bugs")): """ Does the path exist in a given revision? (True/False) """ - pass + raise NotImplementedError def _vcs_remove(self, path): """ -- cgit From 7d01fa142b05149479e633525fc4d7ddfa2addf0 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 24 Jan 2010 20:39:00 -0500 Subject: Fixed #bea/8fc# : be crashes on outdated id-cache Also explicitly avoid loading or saving settings for root comments. --- libbe/storage/vcs/base.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 7d4383f..7068e46 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -239,7 +239,11 @@ class CachedPathID (object): else: extra = fields[1:] if uuid not in self._cache: - raise InvalidID(uuid) + self.disconnect() + self.init() + self.connect() + if uuid not in self._cache: + raise InvalidID(uuid) if relpath == True: return os.path.join(self._cache[uuid], *extra) return os.path.join(self._root, self._cache[uuid], *extra) -- cgit From a87dde3eab86554f0ff70fb53d142ca7bca28b55 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 25 Jan 2010 07:31:57 -0500 Subject: Don't print 'Multiple paths' message on cache regen --- libbe/storage/vcs/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 7068e46..2269424 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -182,7 +182,7 @@ class CachedPathID (object): self._cache_path = os.path.join( self._root, self._spacer_dirs[0], 'id-cache') - def init(self): + def init(self, verbose=True): """ Create cache file for an existing .be directory. File if multiple lines of the form: @@ -197,7 +197,7 @@ class CachedPathID (object): id = self.id(dirpath) relpath = dirpath[len(self._root)+1:] if id.count('/') == 0: - if id in self._cache: + if verbose == True and id in self._cache: print >> sys.stderr, 'Multiple paths for %s: \n %s\n %s' % (id, self._cache[id], relpath) self._cache[id] = relpath except InvalidPath: @@ -240,7 +240,7 @@ class CachedPathID (object): extra = fields[1:] if uuid not in self._cache: self.disconnect() - self.init() + self.init(verbose=False) self.connect() if uuid not in self._cache: raise InvalidID(uuid) -- cgit From 9518e1a98e642175a2de74a8d6e54126b9399210 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 25 Jan 2010 07:54:37 -0500 Subject: Rework fix for #bea/8fc# : be crashes on outdated id-cache Now we re-run CachedPathID.init in an 'append' mode, rather than starting over from scratch. This avoids problems like ====================================================================== ERROR: Should not be able to add children to non-directories. ---------------------------------------------------------------------- Traceback (most recent call last): File ".../be.wtk/libbe/storage/base.py", line 680, in test_add_invalid_directory self.s.add('child', 'parent', directory=False) File ".../be.wtk/libbe/storage/base.py", line 248, in add self._add(id, *args, **kwargs) File ".../be.wtk/libbe/storage/vcs/base.py", line 737, in _add path = self._cached_path_id.add_id(id, parent) File ".../be.wtk/libbe/storage/vcs/base.py", line 267, in add_id parent_path = self.path(parent, relpath=True) File ".../be.wtk/libbe/storage/vcs/base.py", line 246, in path raise InvalidID(uuid) InvalidID: parent in revision None and similar. --- libbe/storage/vcs/base.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 2269424..8335cfa 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -182,13 +182,16 @@ class CachedPathID (object): self._cache_path = os.path.join( self._root, self._spacer_dirs[0], 'id-cache') - def init(self, verbose=True): + def init(self, verbose=True, cache=None): """ Create cache file for an existing .be directory. File if multiple lines of the form: UUID\tPATH """ - self._cache = {} + if cache == None: + self._cache = {} + else: + self._cache = cache spaced_root = os.path.join(self._root, self._spacer_dirs[0]) for dirpath, dirnames, filenames in os.walk(spaced_root): if dirpath == spaced_root: @@ -202,8 +205,10 @@ class CachedPathID (object): self._cache[id] = relpath except InvalidPath: pass - self._changed = True - self.disconnect() + if self._cache != cache: + self._changed = True + if cache == None: + self.disconnect() def destroy(self): if os.path.exists(self._cache_path): @@ -239,9 +244,7 @@ class CachedPathID (object): else: extra = fields[1:] if uuid not in self._cache: - self.disconnect() - self.init(verbose=False) - self.connect() + self.init(verbose=False, cache=self._cache) if uuid not in self._cache: raise InvalidID(uuid) if relpath == True: -- cgit From 4203368d2a1f9cc794646754a5027e307074fbf6 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 27 Jan 2010 10:27:27 -0500 Subject: Make VCS error messages and Storage test failures more descriptive --- libbe/storage/vcs/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 8335cfa..337576e 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -515,7 +515,7 @@ os.listdir(self.get_path("bugs")): """ Does the path exist in a given revision? (True/False) """ - raise NotImplementedError + raise NotImplementedError('Lazy BE developers') def _vcs_remove(self, path): """ -- cgit From 977eff5af10b50ba6e6edb6abc4f40804c418b12 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 7 Feb 2010 17:53:53 -0500 Subject: Fixed docstrings so only Sphinx errors are "autosummary" and "missing attribute" --- libbe/storage/vcs/base.py | 266 +++++++++++++++++----------------------------- 1 file changed, 95 insertions(+), 171 deletions(-) (limited to 'libbe/storage/vcs/base.py') diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py index 337576e..d85c94d 100644 --- a/libbe/storage/vcs/base.py +++ b/libbe/storage/vcs/base.py @@ -19,10 +19,9 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -""" -Define the base VCS (Version Control System) class, which should be -subclassed by other Version Control System backends. The base class -implements a "do not version" VCS. +"""Define the base :class:`VCS` (Version Control System) class, which +should be subclassed by other Version Control System backends. The +base class implements a "do not version" VCS. """ import codecs @@ -50,11 +49,17 @@ if libbe.TESTING == True: import libbe.ui.util.user -# List VCS modules in order of preference. -# Don't list this module, it is implicitly last. VCS_ORDER = ['arch', 'bzr', 'darcs', 'git', 'hg'] +"""List VCS modules in order of preference. + +Don't list this module, it is implicitly last. +""" def set_preferred_vcs(name): + """Manipulate :data:`VCS_ORDER` to place `name` first. + + This is primarily indended for testing purposes. + """ global VCS_ORDER assert name in VCS_ORDER, \ 'unrecognized VCS %s not in\n %s' % (name, VCS_ORDER) @@ -62,7 +67,10 @@ def set_preferred_vcs(name): VCS_ORDER.insert(0, name) def _get_matching_vcs(matchfn): - """Return the first module for which matchfn(VCS_instance) is true""" + """Return the first module for which matchfn(VCS_instance) is True. + + Searches in :data:`VCS_ORDER`. + """ for submodname in VCS_ORDER: module = import_by_name('libbe.storage.vcs.%s' % submodname) vcs = module.new() @@ -71,17 +79,26 @@ def _get_matching_vcs(matchfn): return VCS() def vcs_by_name(vcs_name): - """Return the module for the VCS with the given name""" + """Return the module for the VCS with the given name. + + Searches in :data:`VCS_ORDER`. + """ if vcs_name == VCS.name: return new() return _get_matching_vcs(lambda vcs: vcs.name == vcs_name) def detect_vcs(dir): - """Return an VCS instance for the vcs being used in this directory""" + """Return an VCS instance for the vcs being used in this directory. + + Searches in :data:`VCS_ORDER`. + """ return _get_matching_vcs(lambda vcs: vcs._detect(dir)) def installed_vcs(): - """Return an instance of an installed VCS""" + """Return an instance of an installed VCS. + + Searches in :data:`VCS_ORDER`. + """ return _get_matching_vcs(lambda vcs: vcs.installed()) @@ -118,10 +135,17 @@ class NoSuchFile (InvalidID): class CachedPathID (object): - """ - Storage ID <-> path policy. - .../.be/BUGDIR/bugs/BUG/comments/COMMENT - ^-- root path + """Cache Storage ID <-> path policy. + + Paths generated following:: + + .../.be/BUGDIR/bugs/BUG/comments/COMMENT + ^-- root path + + See :mod:`libbe.util.id` for a discussion of ID formats. + + Examples + -------- >>> dir = Dir() >>> os.mkdir(os.path.join(dir.path, '.be')) @@ -183,10 +207,11 @@ class CachedPathID (object): self._root, self._spacer_dirs[0], 'id-cache') def init(self, verbose=True, cache=None): - """ - Create cache file for an existing .be directory. - File if multiple lines of the form: - UUID\tPATH + """Create cache file for an existing .be directory. + + The file contains multiple lines of the form:: + + UUID\tPATH """ if cache == None: self._cache = {} @@ -311,142 +336,13 @@ def new(): return VCS() class VCS (libbe.storage.base.VersionedStorage): - """ - This class implements a 'no-vcs' interface. + """Implement a 'no-VCS' interface. Support for other VCSs can be added by subclassing this class, and - overriding methods _vcs_*() with code appropriate for your VCS. + overriding methods `_vcs_*()` with code appropriate for your VCS. - The methods _u_*() are utility methods available to the _vcs_*() + The methods `_u_*()` are utility methods available to the `_vcs_*()` methods. - - Sink to existing root - ====================== - - Consider the following usage case: - You have a bug directory rooted in - /path/to/source - by which I mean the '.be' directory is at - /path/to/source/.be - However, you're of in some subdirectory like - /path/to/source/GUI/testing - and you want to comment on a bug. Setting sink_to_root=True when - you initialize your BugDir will cause it to search for the '.be' - file in the ancestors of the path you passed in as 'root'. - /path/to/source/GUI/testing/.be miss - /path/to/source/GUI/.be miss - /path/to/source/.be hit! - So it still roots itself appropriately without much work for you. - - File-system access - ================== - - BugDirs live completely in memory when .sync_with_disk is False. - This is the default configuration setup by BugDir(from_disk=False). - If .sync_with_disk == True (e.g. BugDir(from_disk=True)), then - any changes to the BugDir will be immediately written to disk. - - If you want to change .sync_with_disk, we suggest you use - .set_sync_with_disk(), which propogates the new setting through to - all bugs/comments/etc. that have been loaded into memory. If - you've been living in memory and want to move to - .sync_with_disk==True, but you're not sure if anything has been - changed in memory, a call to .save() immediately before the - .set_sync_with_disk(True) call is a safe move. - - Regardless of .sync_with_disk, a call to .save() will write out - all the contents that the BugDir instance has loaded into memory. - If sync_with_disk has been True over the course of all interesting - changes, this .save() call will be a waste of time. - - The BugDir will only load information from the file system when it - loads new settings/bugs/comments that it doesn't already have in - memory and .sync_with_disk == True. - - Allow storage initialization - ======================== - - This one is for testing purposes. Setting it to True allows the - BugDir to search for an installed Storage backend and initialize - it in the root directory. This is a convenience option for - supporting tests of versioning functionality - (e.g. RevisionedBugDir). - - Disable encoding manipulation - ============================= - - This one is for testing purposed. You might have non-ASCII - Unicode in your bugs, comments, files, etc. BugDir instances try - and support your preferred encoding scheme (e.g. "utf-8") when - dealing with stream and file input/output. For stream output, - this involves replacing sys.stdout and sys.stderr - (libbe.encode.set_IO_stream_encodings). However this messes up - doctest's output catching. In order to support doctest tests - using BugDirs, set manipulate_encodings=False, and stick to ASCII - in your tests. - - if root == None: - root = os.getcwd() - if sink_to_existing_root == True: - self.root = self._find_root(root) - else: - if not os.path.exists(root): - self.root = None - raise NoRootEntry(root) - self.root = root - # get a temporary storage until we've loaded settings - self.sync_with_disk = False - self.storage = self._guess_storage() - - if assert_new_BugDir == True: - if os.path.exists(self.get_path()): - raise AlreadyInitialized, self.get_path() - if storage == None: - storage = self._guess_storage(allow_storage_init) - self.storage = storage - self._setup_user_id(self.user_id) - - - # methods for getting the BugDir situated in the filesystem - - def _find_root(self, path): - ''' - Search for an existing bug database dir and it's ancestors and - return a BugDir rooted there. Only called by __init__, and - then only if sink_to_existing_root == True. - ''' - if not os.path.exists(path): - self.root = None - raise NoRootEntry(path) - versionfile=utility.search_parent_directories(path, - os.path.join(".be", "version")) - if versionfile != None: - beroot = os.path.dirname(versionfile) - root = os.path.dirname(beroot) - return root - else: - beroot = utility.search_parent_directories(path, ".be") - if beroot == None: - self.root = None - raise NoBugDir(path) - return beroot - - def _guess_storage(self, allow_storage_init=False): - ''' - Only called by __init__. - ''' - deepdir = self.get_path() - if not os.path.exists(deepdir): - deepdir = os.path.dirname(deepdir) - new_storage = storage.detect_storage(deepdir) - install = False - if new_storage.name == "None": - if allow_storage_init == True: - new_storage = storage.installed_storage() - new_storage.init(self.root) - return new_storage - -os.listdir(self.get_path("bugs")): """ name = 'None' client = 'false' # command-line tool for _u_invoke_client @@ -659,9 +555,28 @@ os.listdir(self.get_path("bugs")): return self._vcs_detect(path) def root(self): - """ - Set the root directory to the path's VCS root. This is the - default working directory for future invocations. + """Set the root directory to the path's VCS root. + + This is the default working directory for future invocations. + Consider the following usage case: + + You have a project rooted in:: + + /path/to/source/ + + by which I mean the VCS repository is in, for example:: + + /path/to/source/.bzr + + However, you're of in some subdirectory like:: + + /path/to/source/ui/testing + + and you want to comment on a bug. `root` will locate your VCS + root (``/path/to/source/``) and set the repo there. This + means that it doesn't matter where you are in your project + tree when you call "be COMMAND", it always acts as if you called + it from the VCS root. """ if self._detect(self.repo) == False: raise VCSUnableToRoot(self) @@ -678,6 +593,10 @@ os.listdir(self.get_path("bugs")): """ Begin versioning the tree based at self.repo. Also roots the vcs at path. + + See Also + -------- + root : called if the VCS has already been initialized. """ if not os.path.exists(self.repo) or not os.path.isdir(self.repo): raise VCSUnableToRoot(self) @@ -908,8 +827,7 @@ os.listdir(self.get_path("bugs")): return (new_id, mod_id, rem_id) def _u_any_in_string(self, list, string): - """ - Return True if any of the strings in list are in string. + """Return True if any of the strings in list are in string. Otherwise return False. """ for list_string in list: @@ -932,9 +850,8 @@ os.listdir(self.get_path("bugs")): return self._u_invoke(cl_args, **kwargs) def _u_search_parent_directories(self, path, filename): - """ - Find the file (or directory) named filename in path or in any - of path's parents. + """Find the file (or directory) named filename in path or in any of + path's parents. e.g. search_parent_directories("/a/b/c", ".be") @@ -952,8 +869,8 @@ os.listdir(self.get_path("bugs")): return ret def _u_find_id_from_manifest(self, id, manifest, revision=None): - """ - Search for the relative path to id using manifest, a list of all files. + """Search for the relative path to id using manifest, a list of all + files. Returns None if the id is not found. """ @@ -979,8 +896,8 @@ os.listdir(self.get_path("bugs")): raise InvalidID(id, revision=revision) def _u_find_id(self, id, revision): - """ - Search for the relative path to id as of revision. + """Search for the relative path to id as of revision. + Returns None if the id is not found. """ assert self._rooted == True @@ -1001,8 +918,10 @@ os.listdir(self.get_path("bugs")): return self._cached_path_id.id(path) def _u_rel_path(self, path, root=None): - """ - Return the relative path to path from root. + """Return the relative path to path from root. + + Examples: + >>> vcs = new() >>> vcs._u_rel_path("/a.b/c/.be", "/a.b/c") '.be' @@ -1028,8 +947,11 @@ os.listdir(self.get_path("bugs")): return relpath def _u_abspath(self, path, root=None): - """ - Return the absolute path from a path realtive to root. + """Return the absolute path from a path realtive to root. + + Examples + -------- + >>> vcs = new() >>> vcs._u_abspath(".be", "/a.b/c") '/a.b/c/.be' @@ -1040,9 +962,8 @@ os.listdir(self.get_path("bugs")): return os.path.abspath(os.path.join(root, path)) def _u_parse_commitfile(self, commitfile): - """ - Split the commitfile created in self.commit() back into - summary and header lines. + """Split the commitfile created in self.commit() back into summary and + header lines. """ f = codecs.open(commitfile, 'r', self.encoding) summary = f.readline() @@ -1059,8 +980,11 @@ os.listdir(self.get_path("bugs")): upgrade.upgrade(self.repo, version) def storage_version(self, revision=None, path=None): - """ - Requires disk access. + """Return the storage version of the on-disk files. + + See Also + -------- + :mod:`libbe.storage.util.upgrade` """ if path == None: path = os.path.join(self.repo, '.be', 'version') -- cgit