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/arch.py | 315 +++++++++++++ libbe/storage/vcs/base.py | 941 ++++++++++++++++++++++++++++++++++++++ libbe/storage/vcs/bzr.py | 117 +++++ libbe/storage/vcs/darcs.py | 192 ++++++++ libbe/storage/vcs/git.py | 151 ++++++ libbe/storage/vcs/hg.py | 108 +++++ libbe/storage/vcs/util/config.py | 94 ++++ libbe/storage/vcs/util/mapfile.py | 126 +++++ libbe/storage/vcs/util/upgrade.py | 246 ++++++++++ 9 files changed, 2290 insertions(+) create mode 100644 libbe/storage/vcs/arch.py create mode 100644 libbe/storage/vcs/base.py create mode 100644 libbe/storage/vcs/bzr.py create mode 100644 libbe/storage/vcs/darcs.py create mode 100644 libbe/storage/vcs/git.py create mode 100644 libbe/storage/vcs/hg.py create mode 100644 libbe/storage/vcs/util/config.py create mode 100644 libbe/storage/vcs/util/mapfile.py create mode 100644 libbe/storage/vcs/util/upgrade.py (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/arch.py b/libbe/storage/vcs/arch.py new file mode 100644 index 0000000..45a3284 --- /dev/null +++ b/libbe/storage/vcs/arch.py @@ -0,0 +1,315 @@ +# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# Ben Finney +# Gianluca Montecchi +# James Rowe +# 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. + +""" +GNU Arch (tla) backend. +""" + +import codecs +import os +import re +import shutil +import sys +import time + +import libbe +from beuuid import uuid_gen +import config +import vcs +if libbe.TESTING == True: + import unittest + import doctest + + +DEFAULT_CLIENT = "tla" + +client = config.get_val("arch_client", default=DEFAULT_CLIENT) + +def new(): + return Arch() + +class Arch(vcs.VCS): + name = "arch" + client = client + versioned = True + _archive_name = None + _archive_dir = None + _tmp_archive = False + _project_name = None + _tmp_project = False + _arch_paramdir = os.path.expanduser("~/.arch-params") + def _vcs_version(self): + status,output,error = self._u_invoke_client("--version") + return output + def _vcs_detect(self, path): + """Detect whether a directory is revision-controlled using Arch""" + if self._u_search_parent_directories(path, "{arch}") != None : + config.set_val("arch_client", client) + return True + return False + def _vcs_init(self, path): + self._create_archive(path) + self._create_project(path) + self._add_project_code(path) + def _create_archive(self, path): + """ + Create a temporary Arch archive in the directory PATH. This + archive will be removed by + cleanup->_vcs_cleanup->_remove_archive + """ + # http://regexps.srparish.net/tutorial-tla/new-archive.html#Creating_a_New_Archive + assert self._archive_name == None + id = self.get_user_id() + name, email = self._u_parse_id(id) + if email == None: + email = "%s@example.com" % name + trailer = "%s-%s" % ("bugs-everywhere-auto", uuid_gen()[0:8]) + self._archive_name = "%s--%s" % (email, trailer) + self._archive_dir = "/tmp/%s" % trailer + self._tmp_archive = True + self._u_invoke_client("make-archive", self._archive_name, + self._archive_dir, cwd=path) + def _invoke_client(self, *args, **kwargs): + """ + Invoke the client on our archive. + """ + assert self._archive_name != None + command = args[0] + if len(args) > 1: + tailargs = args[1:] + else: + tailargs = [] + arglist = [command, "-A", self._archive_name] + arglist.extend(tailargs) + args = tuple(arglist) + return self._u_invoke_client(*args, **kwargs) + def _remove_archive(self): + assert self._tmp_archive == True + assert self._archive_dir != None + assert self._archive_name != None + os.remove(os.path.join(self._arch_paramdir, + "=locations", self._archive_name)) + shutil.rmtree(self._archive_dir) + self._tmp_archive = False + self._archive_dir = False + self._archive_name = False + def _create_project(self, path): + """ + Create a temporary Arch project in the directory PATH. This + project will be removed by + cleanup->_vcs_cleanup->_remove_project + """ + # http://mwolson.org/projects/GettingStartedWithArch.html + # http://regexps.srparish.net/tutorial-tla/new-project.html#Starting_a_New_Project + category = "bugs-everywhere" + branch = "mainline" + version = "0.1" + self._project_name = "%s--%s--%s" % (category, branch, version) + self._invoke_client("archive-setup", self._project_name, + cwd=path) + self._tmp_project = True + def _remove_project(self): + assert self._tmp_project == True + assert self._project_name != None + assert self._archive_dir != None + shutil.rmtree(os.path.join(self._archive_dir, self._project_name)) + self._tmp_project = False + self._project_name = False + def _archive_project_name(self): + assert self._archive_name != None + assert self._project_name != None + return "%s/%s" % (self._archive_name, self._project_name) + def _adjust_naming_conventions(self, path): + """ + By default, Arch restricts source code filenames to + ^[_=a-zA-Z0-9].*$ + See + http://regexps.srparish.net/tutorial-tla/naming-conventions.html + Since our bug directory '.be' doesn't satisfy these conventions, + we need to adjust them. + + The conventions are specified in + project-root/{arch}/=tagging-method + """ + tagpath = os.path.join(path, "{arch}", "=tagging-method") + lines_out = [] + f = codecs.open(tagpath, "r", self.encoding) + for line in f: + if line.startswith("source "): + lines_out.append("source ^[._=a-zA-X0-9].*$\n") + else: + lines_out.append(line) + f.close() + f = codecs.open(tagpath, "w", self.encoding) + f.write("".join(lines_out)) + f.close() + + def _add_project_code(self, path): + # http://mwolson.org/projects/GettingStartedWithArch.html + # http://regexps.srparish.net/tutorial-tla/new-source.html + # http://regexps.srparish.net/tutorial-tla/importing-first.html + self._invoke_client("init-tree", self._project_name, + cwd=path) + self._adjust_naming_conventions(path) + self._invoke_client("import", "--summary", "Began versioning", + cwd=path) + def _vcs_cleanup(self): + if self._tmp_project == True: + self._remove_project() + if self._tmp_archive == True: + self._remove_archive() + + def _vcs_root(self, path): + if not os.path.isdir(path): + dirname = os.path.dirname(path) + else: + dirname = path + status,output,error = self._u_invoke_client("tree-root", dirname) + root = output.rstrip('\n') + + self._get_archive_project_name(root) + + return root + def _get_archive_name(self, root): + status,output,error = self._u_invoke_client("archives") + lines = output.split('\n') + # e.g. output: + # jdoe@example.com--bugs-everywhere-auto-2008.22.24.52 + # /tmp/BEtestXXXXXX/rootdir + # (+ repeats) + for archive,location in zip(lines[::2], lines[1::2]): + if os.path.realpath(location) == os.path.realpath(root): + self._archive_name = archive + assert self._archive_name != None + def _get_archive_project_name(self, root): + # get project names + status,output,error = self._u_invoke_client("tree-version", cwd=root) + # e.g output + # jdoe@example.com--bugs-everywhere-auto-2008.22.24.52/be--mainline--0.1 + archive_name,project_name = output.rstrip('\n').split('/') + self._archive_name = archive_name + self._project_name = project_name + def _vcs_get_user_id(self): + try: + status,output,error = self._u_invoke_client('my-id') + return output.rstrip('\n') + except Exception, e: + if 'no arch user id set' in e.args[0]: + return None + else: + raise + def _vcs_set_user_id(self, value): + self._u_invoke_client('my-id', value) + def _vcs_add(self, path): + self._u_invoke_client("add-id", path) + realpath = os.path.realpath(self._u_abspath(path)) + pathAdded = realpath in self._list_added(self.rootdir) + if self.paranoid and not pathAdded: + self._force_source(path) + def _list_added(self, root): + assert os.path.exists(root) + assert os.access(root, os.X_OK) + root = os.path.realpath(root) + status,output,error = self._u_invoke_client("inventory", "--source", + "--both", "--all", root) + inv_str = output.rstrip('\n') + return [os.path.join(root, p) for p in inv_str.split('\n')] + def _add_dir_rule(self, rule, dirname, root): + inv_path = os.path.join(dirname, '.arch-inventory') + f = codecs.open(inv_path, "a", self.encoding) + f.write(rule) + f.close() + if os.path.realpath(inv_path) not in self._list_added(root): + paranoid = self.paranoid + self.paranoid = False + self.add(inv_path) + self.paranoid = paranoid + def _force_source(self, path): + rule = "source %s\n" % self._u_rel_path(path) + self._add_dir_rule(rule, os.path.dirname(path), self.rootdir) + if os.path.realpath(path) not in self._list_added(self.rootdir): + raise CantAddFile(path) + def _vcs_remove(self, path): + if not '.arch-ids' in path: + self._u_invoke_client("delete-id", path) + def _vcs_update(self, path): + pass + def _vcs_get_file_contents(self, path, revision=None, binary=False): + if revision == None: + return vcs.VCS._vcs_get_file_contents(self, path, revision, binary=binary) + else: + status,output,error = \ + self._invoke_client("file-find", path, revision) + relpath = output.rstrip('\n') + abspath = os.path.join(self.rootdir, relpath) + f = codecs.open(abspath, "r", self.encoding) + contents = f.read() + f.close() + return contents + def _vcs_duplicate_repo(self, directory, revision=None): + if revision == None: + vcs.VCS._vcs_duplicate_repo(self, directory, revision) + else: + status,output,error = \ + self._u_invoke_client("get", revision, directory) + def _vcs_commit(self, commitfile, allow_empty=False): + if allow_empty == False: + # arch applies empty commits without complaining, so check first + status,output,error = self._u_invoke_client("changes",expect=(0,1)) + if status == 0: + raise vcs.EmptyCommit() + summary,body = self._u_parse_commitfile(commitfile) + args = ["commit", "--summary", summary] + if body != None: + args.extend(["--log-message",body]) + status,output,error = self._u_invoke_client(*args) + revision = None + revline = re.compile("[*] committed (.*)") + match = revline.search(output) + assert match != None, output+error + assert len(match.groups()) == 1 + revpath = match.groups()[0] + assert not " " in revpath, revpath + assert revpath.startswith(self._archive_project_name()+'--') + revision = revpath[len(self._archive_project_name()+'--'):] + return revpath + def _vcs_revision_id(self, index): + status,output,error = self._u_invoke_client("logs") + logs = output.splitlines() + first_log = logs.pop(0) + assert first_log == "base-0", first_log + try: + log = logs[index] + except IndexError: + return None + return "%s--%s" % (self._archive_project_name(), log) + +class CantAddFile(Exception): + def __init__(self, file): + self.file = file + Exception.__init__(self, "Can't automatically add file %s" % file) + + + +if libbe.TESTING == True: + vcs.make_vcs_testcase_subclasses(Arch, sys.modules[__name__]) + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) 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()]) diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py new file mode 100644 index 0000000..62a9b11 --- /dev/null +++ b/libbe/storage/vcs/bzr.py @@ -0,0 +1,117 @@ +# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# Ben Finney +# Gianluca Montecchi +# Marien Zwart +# 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. + +""" +Bazaar (bzr) backend. +""" + +import os +import re +import sys +import unittest + +import libbe +import vcs +if libbe.TESTING == True: + import doctest + + +def new(): + return Bzr() + +class Bzr(vcs.VCS): + name = "bzr" + client = "bzr" + versioned = True + def _vcs_version(self): + status,output,error = self._u_invoke_client("--version") + return output + def _vcs_detect(self, path): + if self._u_search_parent_directories(path, ".bzr") != None : + return True + return False + def _vcs_root(self, path): + """Find the root of the deepest repository containing path.""" + status,output,error = self._u_invoke_client("root", path) + return output.rstrip('\n') + def _vcs_init(self, path): + self._u_invoke_client("init", cwd=path) + def _vcs_get_user_id(self): + status,output,error = self._u_invoke_client("whoami") + return output.rstrip('\n') + def _vcs_set_user_id(self, value): + self._u_invoke_client("whoami", value) + def _vcs_add(self, path): + self._u_invoke_client("add", path) + def _vcs_remove(self, path): + # --force to also remove unversioned files. + self._u_invoke_client("remove", "--force", path) + def _vcs_update(self, path): + pass + def _vcs_get_file_contents(self, path, revision=None, binary=False): + if revision == None: + return vcs.VCS._vcs_get_file_contents(self, path, revision, binary=binary) + else: + status,output,error = \ + self._u_invoke_client("cat","-r",revision,path) + return output + def _vcs_duplicate_repo(self, directory, revision=None): + if revision == None: + vcs.VCS._vcs_duplicate_repo(self, directory, revision) + else: + self._u_invoke_client("branch", "--revision", revision, + ".", directory) + def _vcs_commit(self, commitfile, allow_empty=False): + args = ["commit", "--file", commitfile] + if allow_empty == True: + args.append("--unchanged") + status,output,error = self._u_invoke_client(*args) + else: + kwargs = {"expect":(0,3)} + status,output,error = self._u_invoke_client(*args, **kwargs) + if status != 0: + strings = ["ERROR: no changes to commit.", # bzr 1.3.1 + "ERROR: No changes to commit."] # bzr 1.15.1 + if self._u_any_in_string(strings, error) == True: + raise vcs.EmptyCommit() + else: + raise vcs.CommandError(args, status, stderr=error) + revision = None + revline = re.compile("Committed revision (.*)[.]") + match = revline.search(error) + assert match != None, output+error + assert len(match.groups()) == 1 + revision = match.groups()[0] + return revision + def _vcs_revision_id(self, index): + status,output,error = self._u_invoke_client("revno") + current_revision = int(output) + if index >= current_revision or index < -current_revision: + return None + if index >= 0: + return str(index+1) # bzr commit 0 is the empty tree. + return str(current_revision+index+1) + + +if libbe.TESTING == True: + vcs.make_vcs_testcase_subclasses(Bzr, sys.modules[__name__]) + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/storage/vcs/darcs.py b/libbe/storage/vcs/darcs.py new file mode 100644 index 0000000..d94eaef --- /dev/null +++ b/libbe/storage/vcs/darcs.py @@ -0,0 +1,192 @@ +# Copyright (C) 2009 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. + +""" +Darcs backend. +""" + +import codecs +import os +import re +import sys +try: # import core module, Python >= 2.5 + from xml.etree import ElementTree +except ImportError: # look for non-core module + from elementtree import ElementTree +from xml.sax.saxutils import unescape + +import libbe +import vcs +if libbe.TESTING == True: + import doctest + import unittest + + +def new(): + return Darcs() + +class Darcs(vcs.VCS): + name="darcs" + client="darcs" + versioned=True + def _vcs_version(self): + status,output,error = self._u_invoke_client("--version") + num_part = output.split(" ")[0] + self.parsed_version = [int(i) for i in num_part.split(".")] + return output + def _vcs_detect(self, path): + if self._u_search_parent_directories(path, "_darcs") != None : + return True + return False + def _vcs_root(self, path): + """Find the root of the deepest repository containing path.""" + # Assume that nothing funny is going on; in particular, that we aren't + # dealing with a bare repo. + if os.path.isdir(path) != True: + path = os.path.dirname(path) + darcs_dir = self._u_search_parent_directories(path, "_darcs") + if darcs_dir == None: + return None + return os.path.dirname(darcs_dir) + def _vcs_init(self, path): + self._u_invoke_client("init", cwd=path) + def _vcs_get_user_id(self): + # following http://darcs.net/manual/node4.html#SECTION00410030000000000000 + # as of June 29th, 2009 + if self.rootdir == None: + return None + darcs_dir = os.path.join(self.rootdir, "_darcs") + if darcs_dir != None: + for pref_file in ["author", "email"]: + pref_path = os.path.join(darcs_dir, "prefs", pref_file) + if os.path.exists(pref_path): + return self.get_file_contents(pref_path) + for env_variable in ["DARCS_EMAIL", "EMAIL"]: + if env_variable in os.environ: + return os.environ[env_variable] + return None + def _vcs_set_user_id(self, value): + if self.rootdir == None: + self.root(".") + if self.rootdir == None: + raise vcs.SettingIDnotSupported + author_path = os.path.join(self.rootdir, "_darcs", "prefs", "author") + f = codecs.open(author_path, "w", self.encoding) + f.write(value) + f.close() + def _vcs_add(self, path): + if os.path.isdir(path): + return + self._u_invoke_client("add", path) + def _vcs_remove(self, path): + if not os.path.isdir(self._u_abspath(path)): + os.remove(os.path.join(self.rootdir, path)) # darcs notices removal + def _vcs_update(self, path): + pass # darcs notices changes + def _vcs_get_file_contents(self, path, revision=None, binary=False): + if revision == None: + return vcs.VCS._vcs_get_file_contents(self, path, revision, + binary=binary) + else: + if self.parsed_version[0] >= 2: + status,output,error = self._u_invoke_client( \ + "show", "contents", "--patch", revision, path) + return output + else: + # Darcs versions < 2.0.0pre2 lack the "show contents" command + + status,output,error = self._u_invoke_client( \ + "diff", "--unified", "--from-patch", revision, path, + unicode_output=False) + major_patch = output + status,output,error = self._u_invoke_client( \ + "diff", "--unified", "--patch", revision, path, + unicode_output=False) + target_patch = output + + # "--output -" to be supported in GNU patch > 2.5.9 + # but that hasn't been released as of June 30th, 2009. + + # Rewrite path to status before the patch we want + args=["patch", "--reverse", path] + status,output,error = self._u_invoke(args, stdin=major_patch) + # Now apply the patch we want + args=["patch", path] + status,output,error = self._u_invoke(args, stdin=target_patch) + + if os.path.exists(os.path.join(self.rootdir, path)) == True: + contents = vcs.VCS._vcs_get_file_contents(self, path, + binary=binary) + else: + contents = "" + + # Now restore path to it's current incarnation + args=["patch", "--reverse", path] + status,output,error = self._u_invoke(args, stdin=target_patch) + args=["patch", path] + status,output,error = self._u_invoke(args, stdin=major_patch) + current_contents = vcs.VCS._vcs_get_file_contents(self, path, + binary=binary) + return contents + def _vcs_duplicate_repo(self, directory, revision=None): + if revision==None: + vcs.VCS._vcs_duplicate_repo(self, directory, revision) + else: + self._u_invoke_client("put", "--to-patch", revision, directory) + def _vcs_commit(self, commitfile, allow_empty=False): + id = self.get_user_id() + if '@' not in id: + id = "%s <%s@invalid.com>" % (id, id) + args = ['record', '--all', '--author', id, '--logfile', commitfile] + status,output,error = self._u_invoke_client(*args) + empty_strings = ["No changes!"] + if self._u_any_in_string(empty_strings, output) == True: + if allow_empty == False: + raise vcs.EmptyCommit() + # note that darcs does _not_ make an empty revision. + # this returns the last non-empty revision id... + revision = self._vcs_revision_id(-1) + else: + revline = re.compile("Finished recording patch '(.*)'") + match = revline.search(output) + assert match != None, output+error + assert len(match.groups()) == 1 + revision = match.groups()[0] + return revision + def _vcs_revision_id(self, index): + status,output,error = self._u_invoke_client("changes", "--xml") + revisions = [] + xml_str = output.encode("unicode_escape").replace(r"\n", "\n") + element = ElementTree.XML(xml_str) + assert element.tag == "changelog", element.tag + for patch in element.getchildren(): + assert patch.tag == "patch", patch.tag + for child in patch.getchildren(): + if child.tag == "name": + text = unescape(unicode(child.text).decode("unicode_escape").strip()) + revisions.append(text) + revisions.reverse() + try: + return revisions[index] + except IndexError: + return None + +if libbe.TESTING == True: + vcs.make_vcs_testcase_subclasses(Darcs, sys.modules[__name__]) + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py new file mode 100644 index 0000000..7f6e53a --- /dev/null +++ b/libbe/storage/vcs/git.py @@ -0,0 +1,151 @@ +# Copyright (C) 2008-2009 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. + +""" +Git backend. +""" + +import os +import re +import sys +import unittest + +import libbe +import vcs +if libbe.TESTING == True: + import doctest + + +def new(): + return Git() + +class Git(vcs.VCS): + name="git" + client="git" + versioned=True + def _vcs_version(self): + status,output,error = self._u_invoke_client("--version") + return output + def _vcs_detect(self, path): + if self._u_search_parent_directories(path, ".git") != None : + return True + return False + def _vcs_root(self, path): + """Find the root of the deepest repository containing path.""" + # Assume that nothing funny is going on; in particular, that we aren't + # dealing with a bare repo. + if os.path.isdir(path) != True: + path = os.path.dirname(path) + status,output,error = self._u_invoke_client("rev-parse", "--git-dir", + cwd=path) + gitdir = os.path.join(path, output.rstrip('\n')) + dirname = os.path.abspath(os.path.dirname(gitdir)) + return dirname + def _vcs_init(self, path): + self._u_invoke_client("init", cwd=path) + def _vcs_get_user_id(self): + status,output,error = \ + self._u_invoke_client("config", "user.name", expect=(0,1)) + if status == 0: + name = output.rstrip('\n') + else: + name = "" + status,output,error = \ + self._u_invoke_client("config", "user.email", expect=(0,1)) + if status == 0: + email = output.rstrip('\n') + else: + email = "" + if name != "" or email != "": # got something! + # guess missing info, if necessary + if name == "": + name = self._u_get_fallback_username() + if email == "": + email = self._u_get_fallback_email() + return self._u_create_id(name, email) + return None # Git has no infomation + def _vcs_set_user_id(self, value): + name,email = self._u_parse_id(value) + if email != None: + self._u_invoke_client("config", "user.email", email) + self._u_invoke_client("config", "user.name", name) + def _vcs_add(self, path): + if os.path.isdir(path): + return + self._u_invoke_client("add", path) + def _vcs_remove(self, path): + if not os.path.isdir(self._u_abspath(path)): + self._u_invoke_client("rm", "-f", path) + def _vcs_update(self, path): + self._vcs_add(path) + def _vcs_get_file_contents(self, path, revision=None, binary=False): + if revision == None: + return vcs.VCS._vcs_get_file_contents(self, path, revision, binary=binary) + else: + arg = "%s:%s" % (revision,path) + status,output,error = self._u_invoke_client("show", arg) + return output + def _vcs_duplicate_repo(self, directory, revision=None): + if revision==None: + vcs.VCS._vcs_duplicate_repo(self, directory, revision) + else: + self._u_invoke_client("clone", "--no-checkout", ".", directory) + self._u_invoke_client("checkout", revision, cwd=directory) + def _vcs_commit(self, commitfile, allow_empty=False): + args = ['commit', '--all', '--file', commitfile] + if allow_empty == True: + args.append("--allow-empty") + status,output,error = self._u_invoke_client(*args) + else: + kwargs = {"expect":(0,1)} + status,output,error = self._u_invoke_client(*args, **kwargs) + strings = ["nothing to commit", + "nothing added to commit"] + if self._u_any_in_string(strings, output) == True: + raise vcs.EmptyCommit() + revision = None + revline = re.compile("(.*) (.*)[:\]] (.*)") + match = revline.search(output) + assert match != None, output+error + assert len(match.groups()) == 3 + revision = match.groups()[1] + full_revision = self._vcs_revision_id(-1) + assert full_revision.startswith(revision), \ + "Mismatched revisions:\n%s\n%s" % (revision, full_revision) + return full_revision + def _vcs_revision_id(self, index): + args = ["rev-list", "--first-parent", "--reverse", "HEAD"] + kwargs = {"expect":(0,128)} + status,output,error = self._u_invoke_client(*args, **kwargs) + if status == 128: + if error.startswith("fatal: ambiguous argument 'HEAD': unknown "): + return None + raise vcs.CommandError(args, status, stderr=error) + commits = output.splitlines() + try: + return commits[index] + except IndexError: + return None + + +if libbe.TESTING == True: + vcs.make_vcs_testcase_subclasses(Git, sys.modules[__name__]) + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py new file mode 100644 index 0000000..ed27717 --- /dev/null +++ b/libbe/storage/vcs/hg.py @@ -0,0 +1,108 @@ +# Copyright (C) 2007-2009 Aaron Bentley and Panometrics, Inc. +# Ben Finney +# 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. + +""" +Mercurial (hg) backend. +""" + +import os +import re +import sys + +import libbe +import vcs + +if libbe.TESTING == True: + import unittest + import doctest + + +def new(): + return Hg() + +class Hg(vcs.VCS): + name="hg" + client="hg" + versioned=True + def _vcs_version(self): + status,output,error = self._u_invoke_client("--version") + return output + def _vcs_detect(self, path): + """Detect whether a directory is revision-controlled using Mercurial""" + if self._u_search_parent_directories(path, ".hg") != None: + return True + return False + def _vcs_root(self, path): + status,output,error = self._u_invoke_client("root", cwd=path) + return output.rstrip('\n') + def _vcs_init(self, path): + self._u_invoke_client("init", cwd=path) + def _vcs_get_user_id(self): + status,output,error = self._u_invoke_client("showconfig","ui.username") + return output.rstrip('\n') + def _vcs_set_user_id(self, value): + """ + Supported by the Config Extension, but that is not part of + standard Mercurial. + http://www.selenic.com/mercurial/wiki/index.cgi/ConfigExtension + """ + raise vcs.SettingIDnotSupported + def _vcs_add(self, path): + self._u_invoke_client("add", path) + def _vcs_remove(self, path): + self._u_invoke_client("rm", "--force", path) + def _vcs_update(self, path): + pass + def _vcs_get_file_contents(self, path, revision=None, binary=False): + if revision == None: + return vcs.VCS._vcs_get_file_contents(self, path, revision, binary=binary) + else: + status,output,error = \ + self._u_invoke_client("cat","-r",revision,path) + return output + def _vcs_duplicate_repo(self, directory, revision=None): + if revision == None: + return vcs.VCS._vcs_duplicate_repo(self, directory, revision) + else: + self._u_invoke_client("archive", "--rev", revision, directory) + def _vcs_commit(self, commitfile, allow_empty=False): + args = ['commit', '--logfile', commitfile] + status,output,error = self._u_invoke_client(*args) + if allow_empty == False: + strings = ["nothing changed"] + if self._u_any_in_string(strings, output) == True: + raise vcs.EmptyCommit() + return self._vcs_revision_id(-1) + def _vcs_revision_id(self, index, style="id"): + args = ["identify", "--rev", str(int(index)), "--%s" % style] + kwargs = {"expect": (0,255)} + status,output,error = self._u_invoke_client(*args, **kwargs) + if status == 0: + id = output.strip() + if id == '000000000000': + return None # before initial commit. + return id + return None + + +if libbe.TESTING == True: + vcs.make_vcs_testcase_subclasses(Hg, sys.modules[__name__]) + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/storage/vcs/util/config.py b/libbe/storage/vcs/util/config.py new file mode 100644 index 0000000..ccd236b --- /dev/null +++ b/libbe/storage/vcs/util/config.py @@ -0,0 +1,94 @@ +# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# 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. + +""" +Create, save, and load the per-user config file at path(). +""" + +import ConfigParser +import codecs +import locale +import os.path +import sys + +import libbe +if libbe.TESTING == True: + import doctest + + +default_encoding = sys.getfilesystemencoding() or locale.getpreferredencoding() + +def path(): + """Return the path to the per-user config file""" + return os.path.expanduser("~/.bugs_everywhere") + +def set_val(name, value, section="DEFAULT", encoding=None): + """Set a value in the per-user config file + + :param name: The name of the value to set + :param value: The new value to set (or None to delete the value) + :param section: The section to store the name/value in + """ + if encoding == None: + encoding = default_encoding + config = ConfigParser.ConfigParser() + if os.path.exists(path()) == False: # touch file or config + open(path(), "w").close() # read chokes on missing file + f = codecs.open(path(), "r", encoding) + config.readfp(f, path()) + f.close() + if value is not None: + config.set(section, name, value) + else: + config.remove_option(section, name) + f = codecs.open(path(), "w", encoding) + config.write(f) + f.close() + +def get_val(name, section="DEFAULT", default=None, encoding=None): + """ + Get a value from the per-user config file + + :param name: The name of the value to get + :section: The section that the name is in + :return: The value, or None + >>> get_val("junk") is None + True + >>> set_val("junk", "random") + >>> get_val("junk") + u'random' + >>> set_val("junk", None) + >>> get_val("junk") is None + True + """ + if os.path.exists(path()): + if encoding == None: + encoding = default_encoding + config = ConfigParser.ConfigParser() + f = codecs.open(path(), "r", encoding) + config.readfp(f, path()) + f.close() + try: + return config.get(section, name) + except ConfigParser.NoOptionError: + return default + else: + return default + +if libbe.TESTING == True: + suite = doctest.DocTestSuite() diff --git a/libbe/storage/vcs/util/mapfile.py b/libbe/storage/vcs/util/mapfile.py new file mode 100644 index 0000000..8e1e279 --- /dev/null +++ b/libbe/storage/vcs/util/mapfile.py @@ -0,0 +1,126 @@ +# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# 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. + +""" +Provide a means of saving and loading dictionaries of parameters. The +saved "mapfiles" should be clear, flat-text files, and allow easy merging of +independent/conflicting changes. +""" + +import errno +import os.path +import yaml + +import libbe +if libbe.TESTING == True: + import doctest + + +class IllegalKey(Exception): + def __init__(self, key): + Exception.__init__(self, 'Illegal key "%s"' % key) + self.key = key + +class IllegalValue(Exception): + def __init__(self, value): + Exception.__init__(self, 'Illegal value "%s"' % value) + self.value = value + +def generate(map): + """Generate a YAML mapfile content string. + >>> generate({"q":"p"}) + 'q: p\\n\\n' + >>> generate({"q":u"Fran\u00e7ais"}) + 'q: Fran\\xc3\\xa7ais\\n\\n' + >>> generate({"q":u"hello"}) + 'q: hello\\n\\n' + >>> generate({"q=":"p"}) + Traceback (most recent call last): + IllegalKey: Illegal key "q=" + >>> generate({"q:":"p"}) + Traceback (most recent call last): + IllegalKey: Illegal key "q:" + >>> generate({"q\\n":"p"}) + Traceback (most recent call last): + IllegalKey: Illegal key "q\\n" + >>> generate({"":"p"}) + Traceback (most recent call last): + IllegalKey: Illegal key "" + >>> generate({">q":"p"}) + Traceback (most recent call last): + IllegalKey: Illegal key ">q" + >>> generate({"q":"p\\n"}) + Traceback (most recent call last): + IllegalValue: Illegal value "p\\n" + """ + keys = map.keys() + keys.sort() + for key in keys: + try: + assert not key.startswith('>') + assert('\n' not in key) + assert('=' not in key) + assert(':' not in key) + assert(len(key) > 0) + except AssertionError: + raise IllegalKey(unicode(key).encode('unicode_escape')) + if '\n' in map[key]: + raise IllegalValue(unicode(map[key]).encode('unicode_escape')) + + lines = [] + for key in keys: + lines.append(yaml.safe_dump({key: map[key]}, + default_flow_style=False, + allow_unicode=True)) + lines.append('') + return '\n'.join(lines) + +def parse(contents): + """ + Parse a YAML mapfile string. + >>> parse('q: p\\n\\n')['q'] + 'p' + >>> parse('q: \\'p\\'\\n\\n')['q'] + 'p' + >>> contents = generate({"a":"b", "c":"d", "e":"f"}) + >>> dict = parse(contents) + >>> dict["a"] + 'b' + >>> dict["c"] + 'd' + >>> dict["e"] + 'f' + >>> contents = generate({"q":u"Fran\u00e7ais"}) + >>> dict = parse(contents) + >>> dict["q"] + u'Fran\\xe7ais' + """ + return yaml.load(contents) or {} + +def map_save(vcs, path, map, allow_no_vcs=False): + """Save the map as a mapfile to the specified path""" + contents = generate(map) + vcs.set_file_contents(path, contents, allow_no_vcs, binary=True) + +def map_load(vcs, path, allow_no_vcs=False): + contents = vcs.get_file_contents(path, allow_no_vcs=allow_no_vcs, + binary=True) + return parse(contents) + +if libbe.TESTING == True: + suite = doctest.DocTestSuite() diff --git a/libbe/storage/vcs/util/upgrade.py b/libbe/storage/vcs/util/upgrade.py new file mode 100644 index 0000000..dc9d54f --- /dev/null +++ b/libbe/storage/vcs/util/upgrade.py @@ -0,0 +1,246 @@ +# Copyright (C) 2009 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. + +""" +Handle conversion between the various on-disk images. +""" + +import os, os.path +import sys + +import libbe +import bug +import encoding +import mapfile +import vcs +if libbe.TESTING == True: + import doctest + +# a list of all past versions +BUGDIR_DISK_VERSIONS = ["Bugs Everywhere Tree 1 0", + "Bugs Everywhere Directory v1.1", + "Bugs Everywhere Directory v1.2", + "Bugs Everywhere Directory v1.3"] + +# the current version +BUGDIR_DISK_VERSION = BUGDIR_DISK_VERSIONS[-1] + +class Upgrader (object): + "Class for converting " + initial_version = None + final_version = None + def __init__(self, root): + self.root = root + # use the "None" VCS to ensure proper encoding/decoding and + # simplify path construction. + self.vcs = vcs.vcs_by_name("None") + self.vcs.root(self.root) + self.vcs.encoding = encoding.get_encoding() + + def get_path(self, *args): + """ + Return a path relative to .root. + """ + dir = os.path.join(self.root, ".be") + if len(args) == 0: + return dir + assert args[0] in ["version", "settings", "bugs"], str(args) + return os.path.join(dir, *args) + + def check_initial_version(self): + path = self.get_path("version") + version = self.vcs.get_file_contents(path).rstrip("\n") + assert version == self.initial_version, version + + def set_version(self): + path = self.get_path("version") + self.vcs.set_file_contents(path, self.final_version+"\n") + + def upgrade(self): + print >> sys.stderr, "upgrading bugdir from '%s' to '%s'" \ + % (self.initial_version, self.final_version) + self.check_initial_version() + self.set_version() + self._upgrade() + + def _upgrade(self): + raise NotImplementedError + + +class Upgrade_1_0_to_1_1 (Upgrader): + initial_version = "Bugs Everywhere Tree 1 0" + final_version = "Bugs Everywhere Directory v1.1" + def _upgrade_mapfile(self, path): + contents = self.vcs.get_file_contents(path) + old_format = False + for line in contents.splitlines(): + if len(line.split("=")) == 2: + old_format = True + break + if old_format == True: + # translate to YAML. + newlines = [] + for line in contents.splitlines(): + line = line.rstrip('\n') + if len(line) == 0: + continue + fields = line.split("=") + if len(fields) == 2: + key,value = fields + newlines.append('%s: "%s"' % (key, value.replace('"','\\"'))) + else: + newlines.append(line) + contents = '\n'.join(newlines) + # load the YAML and save + map = mapfile.parse(contents) + mapfile.map_save(self.vcs, path, map) + + def _upgrade(self): + """ + Comment value field "From" -> "Author". + Homegrown mapfile -> YAML. + """ + path = self.get_path("settings") + self._upgrade_mapfile(path) + for bug_uuid in os.listdir(self.get_path("bugs")): + path = self.get_path("bugs", bug_uuid, "values") + self._upgrade_mapfile(path) + c_path = ["bugs", bug_uuid, "comments"] + if not os.path.exists(self.get_path(*c_path)): + continue # no comments for this bug + for comment_uuid in os.listdir(self.get_path(*c_path)): + path_list = c_path + [comment_uuid, "values"] + path = self.get_path(*path_list) + self._upgrade_mapfile(path) + settings = mapfile.map_load(self.vcs, path) + if "From" in settings: + settings["Author"] = settings.pop("From") + mapfile.map_save(self.vcs, path, settings) + + +class Upgrade_1_1_to_1_2 (Upgrader): + initial_version = "Bugs Everywhere Directory v1.1" + final_version = "Bugs Everywhere Directory v1.2" + def _upgrade(self): + """ + BugDir settings field "rcs_name" -> "vcs_name". + """ + path = self.get_path("settings") + settings = mapfile.map_load(self.vcs, path) + if "rcs_name" in settings: + settings["vcs_name"] = settings.pop("rcs_name") + mapfile.map_save(self.vcs, path, settings) + +class Upgrade_1_2_to_1_3 (Upgrader): + initial_version = "Bugs Everywhere Directory v1.2" + final_version = "Bugs Everywhere Directory v1.3" + def __init__(self, *args, **kwargs): + Upgrader.__init__(self, *args, **kwargs) + self._targets = {} # key: target text,value: new target bug + path = self.get_path('settings') + settings = mapfile.map_load(self.vcs, path) + if 'vcs_name' in settings: + old_vcs = self.vcs + self.vcs = vcs.vcs_by_name(settings['vcs_name']) + self.vcs.root(self.root) + self.vcs.encoding = old_vcs.encoding + + def _target_bug(self, target_text): + if target_text not in self._targets: + _bug = bug.Bug(bugdir=self, summary=target_text) + # note: we're not a bugdir, but all Bug.save() needs is + # .root, .vcs, and .get_path(), which we have. + _bug.severity = 'target' + self._targets[target_text] = _bug + return self._targets[target_text] + + def _upgrade_bugdir_mapfile(self): + path = self.get_path('settings') + settings = mapfile.map_load(self.vcs, path) + if 'target' in settings: + settings['target'] = self._target_bug(settings['target']).uuid + mapfile.map_save(self.vcs, path, settings) + + def _upgrade_bug_mapfile(self, bug_uuid): + import becommands.depend + path = self.get_path('bugs', bug_uuid, 'values') + settings = mapfile.map_load(self.vcs, path) + if 'target' in settings: + target_bug = self._target_bug(settings['target']) + _bug = bug.Bug(bugdir=self, uuid=bug_uuid, from_disk=True) + # note: we're not a bugdir, but all Bug.load_settings() + # needs is .root, .vcs, and .get_path(), which we have. + becommands.depend.add_block(target_bug, _bug) + _bug.settings.pop('target') + _bug.save() + + def _upgrade(self): + """ + Bug value field "target" -> target bugs. + Bugdir value field "target" -> pointer to current target bug. + """ + for bug_uuid in os.listdir(self.get_path('bugs')): + self._upgrade_bug_mapfile(bug_uuid) + self._upgrade_bugdir_mapfile() + for _bug in self._targets.values(): + _bug.save() + +upgraders = [Upgrade_1_0_to_1_1, + Upgrade_1_1_to_1_2, + Upgrade_1_2_to_1_3] +upgrade_classes = {} +for upgrader in upgraders: + upgrade_classes[(upgrader.initial_version,upgrader.final_version)]=upgrader + +def upgrade(path, current_version, + target_version=BUGDIR_DISK_VERSION): + """ + Call the appropriate upgrade function to convert current_version + to target_version. If a direct conversion function does not exist, + use consecutive conversion functions. + """ + if current_version not in BUGDIR_DISK_VERSIONS: + raise NotImplementedError, \ + "Cannot handle version '%s' yet." % version + if target_version not in BUGDIR_DISK_VERSIONS: + raise NotImplementedError, \ + "Cannot handle version '%s' yet." % version + + if (current_version, target_version) in upgrade_classes: + # direct conversion + upgrade_class = upgrade_classes[(current_version, target_version)] + u = upgrade_class(path) + u.upgrade() + else: + # consecutive single-step conversion + i = BUGDIR_DISK_VERSIONS.index(current_version) + while True: + version_a = BUGDIR_DISK_VERSIONS[i] + version_b = BUGDIR_DISK_VERSIONS[i+1] + try: + upgrade_class = upgrade_classes[(version_a, version_b)] + except KeyError: + raise NotImplementedError, \ + "Cannot convert version '%s' to '%s' yet." \ + % (version_a, version_b) + u = upgrade_class(path) + u.upgrade() + if version_b == target_version: + break + i += 1 + +if libbe.TESTING == True: + suite = doctest.DocTestSuite() -- cgit From eedd308ff46fb9d0529f4480d2d4ae17e435795d Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 8 Dec 2009 03:58:36 -0500 Subject: Use mapfile to only create & parse mapfile strings, not files --- libbe/storage/vcs/util/mapfile.py | 126 -------------------------------------- 1 file changed, 126 deletions(-) delete mode 100644 libbe/storage/vcs/util/mapfile.py (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/util/mapfile.py b/libbe/storage/vcs/util/mapfile.py deleted file mode 100644 index 8e1e279..0000000 --- a/libbe/storage/vcs/util/mapfile.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. -# 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. - -""" -Provide a means of saving and loading dictionaries of parameters. The -saved "mapfiles" should be clear, flat-text files, and allow easy merging of -independent/conflicting changes. -""" - -import errno -import os.path -import yaml - -import libbe -if libbe.TESTING == True: - import doctest - - -class IllegalKey(Exception): - def __init__(self, key): - Exception.__init__(self, 'Illegal key "%s"' % key) - self.key = key - -class IllegalValue(Exception): - def __init__(self, value): - Exception.__init__(self, 'Illegal value "%s"' % value) - self.value = value - -def generate(map): - """Generate a YAML mapfile content string. - >>> generate({"q":"p"}) - 'q: p\\n\\n' - >>> generate({"q":u"Fran\u00e7ais"}) - 'q: Fran\\xc3\\xa7ais\\n\\n' - >>> generate({"q":u"hello"}) - 'q: hello\\n\\n' - >>> generate({"q=":"p"}) - Traceback (most recent call last): - IllegalKey: Illegal key "q=" - >>> generate({"q:":"p"}) - Traceback (most recent call last): - IllegalKey: Illegal key "q:" - >>> generate({"q\\n":"p"}) - Traceback (most recent call last): - IllegalKey: Illegal key "q\\n" - >>> generate({"":"p"}) - Traceback (most recent call last): - IllegalKey: Illegal key "" - >>> generate({">q":"p"}) - Traceback (most recent call last): - IllegalKey: Illegal key ">q" - >>> generate({"q":"p\\n"}) - Traceback (most recent call last): - IllegalValue: Illegal value "p\\n" - """ - keys = map.keys() - keys.sort() - for key in keys: - try: - assert not key.startswith('>') - assert('\n' not in key) - assert('=' not in key) - assert(':' not in key) - assert(len(key) > 0) - except AssertionError: - raise IllegalKey(unicode(key).encode('unicode_escape')) - if '\n' in map[key]: - raise IllegalValue(unicode(map[key]).encode('unicode_escape')) - - lines = [] - for key in keys: - lines.append(yaml.safe_dump({key: map[key]}, - default_flow_style=False, - allow_unicode=True)) - lines.append('') - return '\n'.join(lines) - -def parse(contents): - """ - Parse a YAML mapfile string. - >>> parse('q: p\\n\\n')['q'] - 'p' - >>> parse('q: \\'p\\'\\n\\n')['q'] - 'p' - >>> contents = generate({"a":"b", "c":"d", "e":"f"}) - >>> dict = parse(contents) - >>> dict["a"] - 'b' - >>> dict["c"] - 'd' - >>> dict["e"] - 'f' - >>> contents = generate({"q":u"Fran\u00e7ais"}) - >>> dict = parse(contents) - >>> dict["q"] - u'Fran\\xe7ais' - """ - return yaml.load(contents) or {} - -def map_save(vcs, path, map, allow_no_vcs=False): - """Save the map as a mapfile to the specified path""" - contents = generate(map) - vcs.set_file_contents(path, contents, allow_no_vcs, binary=True) - -def map_load(vcs, path, allow_no_vcs=False): - contents = vcs.get_file_contents(path, allow_no_vcs=allow_no_vcs, - binary=True) - return parse(contents) - -if libbe.TESTING == True: - suite = doctest.DocTestSuite() -- cgit From df3a136010c3a05166a4a77028fd00a948d7cdeb Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 8 Dec 2009 04:10:10 -0500 Subject: Transitioned comment.py to new storage format. --- libbe/storage/vcs/util/config.py | 94 ---------------------------------------- 1 file changed, 94 deletions(-) delete mode 100644 libbe/storage/vcs/util/config.py (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/util/config.py b/libbe/storage/vcs/util/config.py deleted file mode 100644 index ccd236b..0000000 --- a/libbe/storage/vcs/util/config.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. -# 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. - -""" -Create, save, and load the per-user config file at path(). -""" - -import ConfigParser -import codecs -import locale -import os.path -import sys - -import libbe -if libbe.TESTING == True: - import doctest - - -default_encoding = sys.getfilesystemencoding() or locale.getpreferredencoding() - -def path(): - """Return the path to the per-user config file""" - return os.path.expanduser("~/.bugs_everywhere") - -def set_val(name, value, section="DEFAULT", encoding=None): - """Set a value in the per-user config file - - :param name: The name of the value to set - :param value: The new value to set (or None to delete the value) - :param section: The section to store the name/value in - """ - if encoding == None: - encoding = default_encoding - config = ConfigParser.ConfigParser() - if os.path.exists(path()) == False: # touch file or config - open(path(), "w").close() # read chokes on missing file - f = codecs.open(path(), "r", encoding) - config.readfp(f, path()) - f.close() - if value is not None: - config.set(section, name, value) - else: - config.remove_option(section, name) - f = codecs.open(path(), "w", encoding) - config.write(f) - f.close() - -def get_val(name, section="DEFAULT", default=None, encoding=None): - """ - Get a value from the per-user config file - - :param name: The name of the value to get - :section: The section that the name is in - :return: The value, or None - >>> get_val("junk") is None - True - >>> set_val("junk", "random") - >>> get_val("junk") - u'random' - >>> set_val("junk", None) - >>> get_val("junk") is None - True - """ - if os.path.exists(path()): - if encoding == None: - encoding = default_encoding - config = ConfigParser.ConfigParser() - f = codecs.open(path(), "r", encoding) - config.readfp(f, path()) - f.close() - try: - return config.get(section, name) - except ConfigParser.NoOptionError: - return default - else: - return default - -if libbe.TESTING == True: - suite = 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') 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') 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/__init__.py | 10 + libbe/storage/vcs/base.py | 1105 +++++++++++++++++-------------------- libbe/storage/vcs/util/upgrade.py | 246 --------- 3 files changed, 521 insertions(+), 840 deletions(-) create mode 100644 libbe/storage/vcs/__init__.py delete mode 100644 libbe/storage/vcs/util/upgrade.py (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/__init__.py b/libbe/storage/vcs/__init__.py new file mode 100644 index 0000000..ddfb00a --- /dev/null +++ b/libbe/storage/vcs/__init__.py @@ -0,0 +1,10 @@ +# Copyright + +import base + +set_preferred_vcs = base.set_preferred_vcs +vcs_by_name = base.vcs_by_name +detect_vcs = base.detect_vcs +installed_vcs = base.installed_vcs + +__all__ = [set_preferred_vcs, vcs_by_name, detect_vcs, installed_vcs] 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()]) diff --git a/libbe/storage/vcs/util/upgrade.py b/libbe/storage/vcs/util/upgrade.py deleted file mode 100644 index dc9d54f..0000000 --- a/libbe/storage/vcs/util/upgrade.py +++ /dev/null @@ -1,246 +0,0 @@ -# Copyright (C) 2009 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. - -""" -Handle conversion between the various on-disk images. -""" - -import os, os.path -import sys - -import libbe -import bug -import encoding -import mapfile -import vcs -if libbe.TESTING == True: - import doctest - -# a list of all past versions -BUGDIR_DISK_VERSIONS = ["Bugs Everywhere Tree 1 0", - "Bugs Everywhere Directory v1.1", - "Bugs Everywhere Directory v1.2", - "Bugs Everywhere Directory v1.3"] - -# the current version -BUGDIR_DISK_VERSION = BUGDIR_DISK_VERSIONS[-1] - -class Upgrader (object): - "Class for converting " - initial_version = None - final_version = None - def __init__(self, root): - self.root = root - # use the "None" VCS to ensure proper encoding/decoding and - # simplify path construction. - self.vcs = vcs.vcs_by_name("None") - self.vcs.root(self.root) - self.vcs.encoding = encoding.get_encoding() - - def get_path(self, *args): - """ - Return a path relative to .root. - """ - dir = os.path.join(self.root, ".be") - if len(args) == 0: - return dir - assert args[0] in ["version", "settings", "bugs"], str(args) - return os.path.join(dir, *args) - - def check_initial_version(self): - path = self.get_path("version") - version = self.vcs.get_file_contents(path).rstrip("\n") - assert version == self.initial_version, version - - def set_version(self): - path = self.get_path("version") - self.vcs.set_file_contents(path, self.final_version+"\n") - - def upgrade(self): - print >> sys.stderr, "upgrading bugdir from '%s' to '%s'" \ - % (self.initial_version, self.final_version) - self.check_initial_version() - self.set_version() - self._upgrade() - - def _upgrade(self): - raise NotImplementedError - - -class Upgrade_1_0_to_1_1 (Upgrader): - initial_version = "Bugs Everywhere Tree 1 0" - final_version = "Bugs Everywhere Directory v1.1" - def _upgrade_mapfile(self, path): - contents = self.vcs.get_file_contents(path) - old_format = False - for line in contents.splitlines(): - if len(line.split("=")) == 2: - old_format = True - break - if old_format == True: - # translate to YAML. - newlines = [] - for line in contents.splitlines(): - line = line.rstrip('\n') - if len(line) == 0: - continue - fields = line.split("=") - if len(fields) == 2: - key,value = fields - newlines.append('%s: "%s"' % (key, value.replace('"','\\"'))) - else: - newlines.append(line) - contents = '\n'.join(newlines) - # load the YAML and save - map = mapfile.parse(contents) - mapfile.map_save(self.vcs, path, map) - - def _upgrade(self): - """ - Comment value field "From" -> "Author". - Homegrown mapfile -> YAML. - """ - path = self.get_path("settings") - self._upgrade_mapfile(path) - for bug_uuid in os.listdir(self.get_path("bugs")): - path = self.get_path("bugs", bug_uuid, "values") - self._upgrade_mapfile(path) - c_path = ["bugs", bug_uuid, "comments"] - if not os.path.exists(self.get_path(*c_path)): - continue # no comments for this bug - for comment_uuid in os.listdir(self.get_path(*c_path)): - path_list = c_path + [comment_uuid, "values"] - path = self.get_path(*path_list) - self._upgrade_mapfile(path) - settings = mapfile.map_load(self.vcs, path) - if "From" in settings: - settings["Author"] = settings.pop("From") - mapfile.map_save(self.vcs, path, settings) - - -class Upgrade_1_1_to_1_2 (Upgrader): - initial_version = "Bugs Everywhere Directory v1.1" - final_version = "Bugs Everywhere Directory v1.2" - def _upgrade(self): - """ - BugDir settings field "rcs_name" -> "vcs_name". - """ - path = self.get_path("settings") - settings = mapfile.map_load(self.vcs, path) - if "rcs_name" in settings: - settings["vcs_name"] = settings.pop("rcs_name") - mapfile.map_save(self.vcs, path, settings) - -class Upgrade_1_2_to_1_3 (Upgrader): - initial_version = "Bugs Everywhere Directory v1.2" - final_version = "Bugs Everywhere Directory v1.3" - def __init__(self, *args, **kwargs): - Upgrader.__init__(self, *args, **kwargs) - self._targets = {} # key: target text,value: new target bug - path = self.get_path('settings') - settings = mapfile.map_load(self.vcs, path) - if 'vcs_name' in settings: - old_vcs = self.vcs - self.vcs = vcs.vcs_by_name(settings['vcs_name']) - self.vcs.root(self.root) - self.vcs.encoding = old_vcs.encoding - - def _target_bug(self, target_text): - if target_text not in self._targets: - _bug = bug.Bug(bugdir=self, summary=target_text) - # note: we're not a bugdir, but all Bug.save() needs is - # .root, .vcs, and .get_path(), which we have. - _bug.severity = 'target' - self._targets[target_text] = _bug - return self._targets[target_text] - - def _upgrade_bugdir_mapfile(self): - path = self.get_path('settings') - settings = mapfile.map_load(self.vcs, path) - if 'target' in settings: - settings['target'] = self._target_bug(settings['target']).uuid - mapfile.map_save(self.vcs, path, settings) - - def _upgrade_bug_mapfile(self, bug_uuid): - import becommands.depend - path = self.get_path('bugs', bug_uuid, 'values') - settings = mapfile.map_load(self.vcs, path) - if 'target' in settings: - target_bug = self._target_bug(settings['target']) - _bug = bug.Bug(bugdir=self, uuid=bug_uuid, from_disk=True) - # note: we're not a bugdir, but all Bug.load_settings() - # needs is .root, .vcs, and .get_path(), which we have. - becommands.depend.add_block(target_bug, _bug) - _bug.settings.pop('target') - _bug.save() - - def _upgrade(self): - """ - Bug value field "target" -> target bugs. - Bugdir value field "target" -> pointer to current target bug. - """ - for bug_uuid in os.listdir(self.get_path('bugs')): - self._upgrade_bug_mapfile(bug_uuid) - self._upgrade_bugdir_mapfile() - for _bug in self._targets.values(): - _bug.save() - -upgraders = [Upgrade_1_0_to_1_1, - Upgrade_1_1_to_1_2, - Upgrade_1_2_to_1_3] -upgrade_classes = {} -for upgrader in upgraders: - upgrade_classes[(upgrader.initial_version,upgrader.final_version)]=upgrader - -def upgrade(path, current_version, - target_version=BUGDIR_DISK_VERSION): - """ - Call the appropriate upgrade function to convert current_version - to target_version. If a direct conversion function does not exist, - use consecutive conversion functions. - """ - if current_version not in BUGDIR_DISK_VERSIONS: - raise NotImplementedError, \ - "Cannot handle version '%s' yet." % version - if target_version not in BUGDIR_DISK_VERSIONS: - raise NotImplementedError, \ - "Cannot handle version '%s' yet." % version - - if (current_version, target_version) in upgrade_classes: - # direct conversion - upgrade_class = upgrade_classes[(current_version, target_version)] - u = upgrade_class(path) - u.upgrade() - else: - # consecutive single-step conversion - i = BUGDIR_DISK_VERSIONS.index(current_version) - while True: - version_a = BUGDIR_DISK_VERSIONS[i] - version_b = BUGDIR_DISK_VERSIONS[i+1] - try: - upgrade_class = upgrade_classes[(version_a, version_b)] - except KeyError: - raise NotImplementedError, \ - "Cannot convert version '%s' to '%s' yet." \ - % (version_a, version_b) - u = upgrade_class(path) - u.upgrade() - if version_b == target_version: - break - i += 1 - -if libbe.TESTING == True: - suite = 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 ++++++++++++++++++++++-------------- libbe/storage/vcs/hg.py | 73 +++++++++++++++++++++++++---------------------- 2 files changed, 74 insertions(+), 56 deletions(-) (limited to 'libbe/storage/vcs') 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. diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index ed27717..67c5bf3 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -26,7 +26,7 @@ import re import sys import libbe -import vcs +import base if libbe.TESTING == True: import unittest @@ -36,62 +36,67 @@ if libbe.TESTING == True: def new(): return Hg() -class Hg(vcs.VCS): - name="hg" - client="hg" +class Hg(base.VCS): + name='hg' + client='hg' versioned=True + def _vcs_version(self): - status,output,error = self._u_invoke_client("--version") + status,output,error = self._u_invoke_client('--version') return output + + def _vcs_get_user_id(self): + status,output,error = self._u_invoke_client( + 'showconfig', 'ui.username') + return output.rstrip('\n') + def _vcs_detect(self, path): """Detect whether a directory is revision-controlled using Mercurial""" - if self._u_search_parent_directories(path, ".hg") != None: + if self._u_search_parent_directories(path, '.hg') != None: return True return False + def _vcs_root(self, path): - status,output,error = self._u_invoke_client("root", cwd=path) + status,output,error = self._u_invoke_client( + 'root', expect=(0,255), cwd=path) + if status == 255: + # "abort: There is no Mercurial repository here + # (.hg not found)!" + return None return output.rstrip('\n') + def _vcs_init(self, path): - self._u_invoke_client("init", cwd=path) - def _vcs_get_user_id(self): - status,output,error = self._u_invoke_client("showconfig","ui.username") - return output.rstrip('\n') - def _vcs_set_user_id(self, value): - """ - Supported by the Config Extension, but that is not part of - standard Mercurial. - http://www.selenic.com/mercurial/wiki/index.cgi/ConfigExtension - """ - raise vcs.SettingIDnotSupported + self._u_invoke_client('init', cwd=path) + def _vcs_add(self, path): - self._u_invoke_client("add", path) + self._u_invoke_client('add', path) + def _vcs_remove(self, path): - self._u_invoke_client("rm", "--force", path) + self._u_invoke_client('rm', '--force', path) + def _vcs_update(self, path): pass - def _vcs_get_file_contents(self, path, revision=None, binary=False): + + def _vcs_get_file_contents(self, path, revision=None): if revision == None: - return vcs.VCS._vcs_get_file_contents(self, path, revision, binary=binary) + return base.VCS._vcs_get_file_contents(self, path, revision) else: status,output,error = \ - self._u_invoke_client("cat","-r",revision,path) + self._u_invoke_client('cat', '-r', revision, path) return output - def _vcs_duplicate_repo(self, directory, revision=None): - if revision == None: - return vcs.VCS._vcs_duplicate_repo(self, directory, revision) - else: - self._u_invoke_client("archive", "--rev", revision, directory) + def _vcs_commit(self, commitfile, allow_empty=False): args = ['commit', '--logfile', commitfile] status,output,error = self._u_invoke_client(*args) if allow_empty == False: - strings = ["nothing changed"] + strings = ['nothing changed'] if self._u_any_in_string(strings, output) == True: - raise vcs.EmptyCommit() + raise base.EmptyCommit() return self._vcs_revision_id(-1) - def _vcs_revision_id(self, index, style="id"): - args = ["identify", "--rev", str(int(index)), "--%s" % style] - kwargs = {"expect": (0,255)} + + def _vcs_revision_id(self, index, style='id'): + args = ['identify', '--rev', str(int(index)), '--%s' % style] + kwargs = {'expect': (0,255)} status,output,error = self._u_invoke_client(*args, **kwargs) if status == 0: id = output.strip() @@ -102,7 +107,7 @@ class Hg(vcs.VCS): if libbe.TESTING == True: - vcs.make_vcs_testcase_subclasses(Hg, sys.modules[__name__]) + base.make_vcs_testcase_subclasses(Hg, sys.modules[__name__]) unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) -- 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') 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 +- libbe/storage/vcs/git.py | 120 ++++++++++++++++++++++++---------------------- libbe/storage/vcs/hg.py | 7 +++ 3 files changed, 72 insertions(+), 58 deletions(-) (limited to 'libbe/storage/vcs') 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() diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py index 7f6e53a..02144b5 100644 --- a/libbe/storage/vcs/git.py +++ b/libbe/storage/vcs/git.py @@ -27,7 +27,8 @@ import sys import unittest import libbe -import vcs +import libbe.ui.util.user +import base if libbe.TESTING == True: import doctest @@ -35,108 +36,113 @@ if libbe.TESTING == True: def new(): return Git() -class Git(vcs.VCS): - name="git" - client="git" +class Git(base.VCS): + name='git' + client='git' versioned=True + def _vcs_version(self): - status,output,error = self._u_invoke_client("--version") + status,output,error = self._u_invoke_client('--version') return output + + def _vcs_get_user_id(self): + status,output,error = \ + self._u_invoke_client('config', 'user.name', expect=(0,1)) + if status == 0: + name = output.rstrip('\n') + else: + name = '' + status,output,error = \ + self._u_invoke_client('config', 'user.email', expect=(0,1)) + if status == 0: + email = output.rstrip('\n') + else: + email = '' + if name != '' or email != '': # got something! + # guess missing info, if necessary + if name == '': + name = libbe.ui.util.user.get_fallback_username() + if email == '': + email = libe.ui.util.user.get_fallback_email() + return libbe.ui.util.user.create_user_id(name, email) + return None # Git has no infomation + def _vcs_detect(self, path): - if self._u_search_parent_directories(path, ".git") != None : + if self._u_search_parent_directories(path, '.git') != None : return True return False + def _vcs_root(self, path): """Find the root of the deepest repository containing path.""" # Assume that nothing funny is going on; in particular, that we aren't # dealing with a bare repo. if os.path.isdir(path) != True: path = os.path.dirname(path) - status,output,error = self._u_invoke_client("rev-parse", "--git-dir", + status,output,error = self._u_invoke_client('rev-parse', '--git-dir', cwd=path) gitdir = os.path.join(path, output.rstrip('\n')) dirname = os.path.abspath(os.path.dirname(gitdir)) return dirname + def _vcs_init(self, path): - self._u_invoke_client("init", cwd=path) - def _vcs_get_user_id(self): - status,output,error = \ - self._u_invoke_client("config", "user.name", expect=(0,1)) - if status == 0: - name = output.rstrip('\n') - else: - name = "" - status,output,error = \ - self._u_invoke_client("config", "user.email", expect=(0,1)) - if status == 0: - email = output.rstrip('\n') - else: - email = "" - if name != "" or email != "": # got something! - # guess missing info, if necessary - if name == "": - name = self._u_get_fallback_username() - if email == "": - email = self._u_get_fallback_email() - return self._u_create_id(name, email) - return None # Git has no infomation - def _vcs_set_user_id(self, value): - name,email = self._u_parse_id(value) - if email != None: - self._u_invoke_client("config", "user.email", email) - self._u_invoke_client("config", "user.name", name) + self._u_invoke_client('init', cwd=path) + + def _vcs_destroy(self): + vcs_dir = os.path.join(self.repo, '.git') + if os.path.exists(vcs_dir): + shutil.rmtree(vcs_dir) + def _vcs_add(self, path): if os.path.isdir(path): return - self._u_invoke_client("add", path) + self._u_invoke_client('add', path) + def _vcs_remove(self, path): if not os.path.isdir(self._u_abspath(path)): - self._u_invoke_client("rm", "-f", path) + self._u_invoke_client('rm', '-f', path) + def _vcs_update(self, path): self._vcs_add(path) - def _vcs_get_file_contents(self, path, revision=None, binary=False): + + def _vcs_get_file_contents(self, path, revision=None): if revision == None: - return vcs.VCS._vcs_get_file_contents(self, path, revision, binary=binary) + return base.VCS._vcs_get_file_contents(self, path, revision) else: - arg = "%s:%s" % (revision,path) - status,output,error = self._u_invoke_client("show", arg) + arg = '%s:%s' % (revision,path) + status,output,error = self._u_invoke_client('show', arg) return output - def _vcs_duplicate_repo(self, directory, revision=None): - if revision==None: - vcs.VCS._vcs_duplicate_repo(self, directory, revision) - else: - self._u_invoke_client("clone", "--no-checkout", ".", directory) - self._u_invoke_client("checkout", revision, cwd=directory) + def _vcs_commit(self, commitfile, allow_empty=False): args = ['commit', '--all', '--file', commitfile] if allow_empty == True: - args.append("--allow-empty") + args.append('--allow-empty') status,output,error = self._u_invoke_client(*args) else: - kwargs = {"expect":(0,1)} + kwargs = {'expect':(0,1)} status,output,error = self._u_invoke_client(*args, **kwargs) - strings = ["nothing to commit", - "nothing added to commit"] + strings = ['nothing to commit', + 'nothing added to commit'] if self._u_any_in_string(strings, output) == True: - raise vcs.EmptyCommit() + raise base.EmptyCommit() revision = None - revline = re.compile("(.*) (.*)[:\]] (.*)") + revline = re.compile('(.*) (.*)[:\]] (.*)') match = revline.search(output) assert match != None, output+error assert len(match.groups()) == 3 revision = match.groups()[1] full_revision = self._vcs_revision_id(-1) assert full_revision.startswith(revision), \ - "Mismatched revisions:\n%s\n%s" % (revision, full_revision) + 'Mismatched revisions:\n%s\n%s' % (revision, full_revision) return full_revision + def _vcs_revision_id(self, index): - args = ["rev-list", "--first-parent", "--reverse", "HEAD"] - kwargs = {"expect":(0,128)} + args = ['rev-list', '--first-parent', '--reverse', 'HEAD'] + kwargs = {'expect':(0,128)} status,output,error = self._u_invoke_client(*args, **kwargs) if status == 128: if error.startswith("fatal: ambiguous argument 'HEAD': unknown "): return None - raise vcs.CommandError(args, status, stderr=error) + raise base.CommandError(args, status, stderr=error) commits = output.splitlines() try: return commits[index] @@ -145,7 +151,7 @@ class Git(vcs.VCS): if libbe.TESTING == True: - vcs.make_vcs_testcase_subclasses(Git, sys.modules[__name__]) + base.make_vcs_testcase_subclasses(Git, sys.modules[__name__]) unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index 67c5bf3..fb3ee3f 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -22,7 +22,9 @@ Mercurial (hg) backend. """ import os +import os.path import re +import shutil import sys import libbe @@ -68,6 +70,11 @@ class Hg(base.VCS): def _vcs_init(self, path): self._u_invoke_client('init', cwd=path) + def _vcs_destroy(self): + vcs_dir = os.path.join(self.repo, '.hg') + if os.path.exists(vcs_dir): + shutil.rmtree(vcs_dir) + def _vcs_add(self, path): self._u_invoke_client('add', path) -- cgit From ca52b5cca130fb3bd810276d9de1f198df3cf5b7 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 07:45:43 -0500 Subject: .bzr transition. --- libbe/storage/vcs/bzr.py | 83 ++++++++++++++++++++++++++++-------------------- libbe/storage/vcs/git.py | 7 +++- libbe/storage/vcs/hg.py | 5 ++- 3 files changed, 58 insertions(+), 37 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index 62a9b11..c847019 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -23,12 +23,14 @@ Bazaar (bzr) backend. """ import os +import os.path import re +import shutil import sys import unittest import libbe -import vcs +import base if libbe.TESTING == True: import doctest @@ -36,72 +38,83 @@ if libbe.TESTING == True: def new(): return Bzr() -class Bzr(vcs.VCS): - name = "bzr" - client = "bzr" - versioned = True +class Bzr(base.VCS): + name = 'bzr' + client = 'bzr' + + def __init__(self, *args, **kwargs): + base.VCS.__init__(self, *args, **kwargs) + self.versioned = True + def _vcs_version(self): - status,output,error = self._u_invoke_client("--version") + status,output,error = self._u_invoke_client('--version') return output + + def _vcs_get_user_id(self): + status,output,error = self._u_invoke_client('whoami') + return output.rstrip('\n') + def _vcs_detect(self, path): - if self._u_search_parent_directories(path, ".bzr") != None : + if self._u_search_parent_directories(path, '.bzr') != None : return True return False + def _vcs_root(self, path): """Find the root of the deepest repository containing path.""" - status,output,error = self._u_invoke_client("root", path) + status,output,error = self._u_invoke_client('root', path) return output.rstrip('\n') + def _vcs_init(self, path): - self._u_invoke_client("init", cwd=path) - def _vcs_get_user_id(self): - status,output,error = self._u_invoke_client("whoami") - return output.rstrip('\n') - def _vcs_set_user_id(self, value): - self._u_invoke_client("whoami", value) + self._u_invoke_client('init', cwd=path) + + def _vcs_destroy(self): + vcs_dir = os.path.join(self.repo, '.bzr') + if os.path.exists(vcs_dir): + shutil.rmtree(vcs_dir) + def _vcs_add(self, path): - self._u_invoke_client("add", path) + self._u_invoke_client('add', path) + def _vcs_remove(self, path): # --force to also remove unversioned files. - self._u_invoke_client("remove", "--force", path) + self._u_invoke_client('remove', '--force', path) + def _vcs_update(self, path): pass - def _vcs_get_file_contents(self, path, revision=None, binary=False): + + def _vcs_get_file_contents(self, path, revision=None): if revision == None: - return vcs.VCS._vcs_get_file_contents(self, path, revision, binary=binary) + return base.VCS._vcs_get_file_contents(self, path, revision) else: status,output,error = \ - self._u_invoke_client("cat","-r",revision,path) + self._u_invoke_client('cat', '-r', revision,path) return output - def _vcs_duplicate_repo(self, directory, revision=None): - if revision == None: - vcs.VCS._vcs_duplicate_repo(self, directory, revision) - else: - self._u_invoke_client("branch", "--revision", revision, - ".", directory) + def _vcs_commit(self, commitfile, allow_empty=False): - args = ["commit", "--file", commitfile] + args = ['commit', '--file', commitfile] if allow_empty == True: - args.append("--unchanged") + args.append('--unchanged') status,output,error = self._u_invoke_client(*args) else: - kwargs = {"expect":(0,3)} + kwargs = {'expect':(0,3)} status,output,error = self._u_invoke_client(*args, **kwargs) if status != 0: - strings = ["ERROR: no changes to commit.", # bzr 1.3.1 - "ERROR: No changes to commit."] # bzr 1.15.1 + strings = ['ERROR: no changes to commit.', # bzr 1.3.1 + 'ERROR: No changes to commit.'] # bzr 1.15.1 if self._u_any_in_string(strings, error) == True: - raise vcs.EmptyCommit() + raise base.EmptyCommit() else: - raise vcs.CommandError(args, status, stderr=error) + raise base.CommandError(args, status, stderr=error) revision = None - revline = re.compile("Committed revision (.*)[.]") + revline = re.compile('Committed revision (.*)[.]') match = revline.search(error) assert match != None, output+error assert len(match.groups()) == 1 revision = match.groups()[0] return revision + def _vcs_revision_id(self, index): - status,output,error = self._u_invoke_client("revno") + status,output,error = self._u_invoke_client('revno') current_revision = int(output) if index >= current_revision or index < -current_revision: return None @@ -111,7 +124,7 @@ class Bzr(vcs.VCS): if libbe.TESTING == True: - vcs.make_vcs_testcase_subclasses(Bzr, sys.modules[__name__]) + base.make_vcs_testcase_subclasses(Bzr, sys.modules[__name__]) unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py index 02144b5..0b006f3 100644 --- a/libbe/storage/vcs/git.py +++ b/libbe/storage/vcs/git.py @@ -22,7 +22,9 @@ Git backend. """ import os +import os.path import re +import shutil import sys import unittest @@ -39,7 +41,10 @@ def new(): class Git(base.VCS): name='git' client='git' - versioned=True + + def __init__(self, *args, **kwargs): + base.VCS.__init__(self, *args, **kwargs) + self.versioned = True def _vcs_version(self): status,output,error = self._u_invoke_client('--version') diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index fb3ee3f..a8504d0 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -41,7 +41,10 @@ def new(): class Hg(base.VCS): name='hg' client='hg' - versioned=True + + def __init__(self, *args, **kwargs): + base.VCS.__init__(self, *args, **kwargs) + self.versioned = True def _vcs_version(self): status,output,error = self._u_invoke_client('--version') -- 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 ++++++++--------------------------------------- libbe/storage/vcs/hg.py | 7 +----- 2 files changed, 11 insertions(+), 56 deletions(-) (limited to 'libbe/storage/vcs') 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") diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index a8504d0..f1a7eef 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -62,12 +62,7 @@ class Hg(base.VCS): return False def _vcs_root(self, path): - status,output,error = self._u_invoke_client( - 'root', expect=(0,255), cwd=path) - if status == 255: - # "abort: There is no Mercurial repository here - # (.hg not found)!" - return None + status,output,error = self._u_invoke_client('root', cwd=path) return output.rstrip('\n') def _vcs_init(self, path): -- 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 + libbe/storage/vcs/hg.py | 4 ++++ 2 files changed, 5 insertions(+) (limited to 'libbe/storage/vcs') 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 diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index f1a7eef..260f0c4 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -100,6 +100,10 @@ class Hg(base.VCS): return self._vcs_revision_id(-1) def _vcs_revision_id(self, index, style='id'): + try: + index = str(int(index)) + except ValueError: + return None args = ['identify', '--rev', str(int(index)), '--%s' % style] kwargs = {'expect': (0,255)} status,output,error = self._u_invoke_client(*args, **kwargs) -- 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 +++++ libbe/storage/vcs/hg.py | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'libbe/storage/vcs') 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) diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index 260f0c4..f1a7eef 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -100,10 +100,6 @@ class Hg(base.VCS): return self._vcs_revision_id(-1) def _vcs_revision_id(self, index, style='id'): - try: - index = str(int(index)) - except ValueError: - return None args = ['identify', '--rev', str(int(index)), '--%s' % style] kwargs = {'expect': (0,255)} status,output,error = self._u_invoke_client(*args, **kwargs) -- cgit From d21c50ece316536b5972725eced19b40d6e2589d Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 08:31:33 -0500 Subject: Fix Git._vcs_revision_id() offset bug. --- libbe/storage/vcs/bzr.py | 5 +++-- libbe/storage/vcs/git.py | 10 ++++++++-- libbe/storage/vcs/hg.py | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index c847019..7d84415 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -26,13 +26,14 @@ import os import os.path import re import shutil -import sys -import unittest import libbe import base + if libbe.TESTING == True: import doctest + import sys + import unittest def new(): diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py index 0b006f3..77ddd88 100644 --- a/libbe/storage/vcs/git.py +++ b/libbe/storage/vcs/git.py @@ -25,14 +25,15 @@ import os import os.path import re import shutil -import sys import unittest import libbe import libbe.ui.util.user import base + if libbe.TESTING == True: import doctest + import sys def new(): @@ -150,7 +151,12 @@ class Git(base.VCS): raise base.CommandError(args, status, stderr=error) commits = output.splitlines() try: - return commits[index] + if index > 0: + return commits[index-1] + elif index < 0: + return commits[index] + else: + return None except IndexError: return None diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index f1a7eef..633987a 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -25,14 +25,14 @@ import os import os.path import re import shutil -import sys import libbe import base if libbe.TESTING == True: - import unittest import doctest + import sys + import unittest def new(): -- cgit From e8a0ff2488d1535cea0528aa1c7b9ef8aa43dfed Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 08:43:01 -0500 Subject: Don't regexp out the short-revid in Git._vcs_commit() The output version strings change: Version 1.5.4.3: Created initial commit 217efa7: MESSAGE Created commit acb3066: MESSAGE Version 1.6.4.4: [master (root-commit) c5b48cf] MESSAGE [master 66a48c1] MESSAGE Instead, get the full revid, and look for its beginning in the output. --- libbe/storage/vcs/git.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py index 77ddd88..6d240c2 100644 --- a/libbe/storage/vcs/git.py +++ b/libbe/storage/vcs/git.py @@ -130,15 +130,9 @@ class Git(base.VCS): 'nothing added to commit'] if self._u_any_in_string(strings, output) == True: raise base.EmptyCommit() - revision = None - revline = re.compile('(.*) (.*)[:\]] (.*)') - match = revline.search(output) - assert match != None, output+error - assert len(match.groups()) == 3 - revision = match.groups()[1] full_revision = self._vcs_revision_id(-1) - assert full_revision.startswith(revision), \ - 'Mismatched revisions:\n%s\n%s' % (revision, full_revision) + assert full_revision[:7] in output, \ + 'Mismatched revisions:\n%s\n%s' % (full_revision, output) return full_revision def _vcs_revision_id(self, index): -- cgit From 5fb31c1fa646de076b999532690375319b5d4eb6 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 19:40:06 -0500 Subject: Work around mercurial (hg) issue 618. --- libbe/storage/vcs/hg.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index 633987a..776d986 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -25,6 +25,7 @@ import os import os.path import re import shutil +import time # work around http://mercurial.selenic.com/bts/issue618 import libbe import base @@ -45,6 +46,7 @@ class Hg(base.VCS): def __init__(self, *args, **kwargs): base.VCS.__init__(self, *args, **kwargs) self.versioned = True + self.__updated = [] # work around http://mercurial.selenic.com/bts/issue618 def _vcs_version(self): status,output,error = self._u_invoke_client('--version') @@ -80,7 +82,7 @@ class Hg(base.VCS): self._u_invoke_client('rm', '--force', path) def _vcs_update(self, path): - pass + self.__updated.append(path) # work around http://mercurial.selenic.com/bts/issue618 def _vcs_get_file_contents(self, path, revision=None): if revision == None: @@ -93,6 +95,16 @@ class Hg(base.VCS): def _vcs_commit(self, commitfile, allow_empty=False): args = ['commit', '--logfile', commitfile] status,output,error = self._u_invoke_client(*args) + # work around http://mercurial.selenic.com/bts/issue618 + strings = ['nothing changed'] + if self._u_any_in_string(strings, output) == True \ + and len(self.__updated) > 0: + time.sleep(1) + for path in self.__updated: + os.utime(os.path.join(self.repo, path), None) + status,output,error = self._u_invoke_client(*args) + self.__updated = [] + # end work around if allow_empty == False: strings = ['nothing changed'] if self._u_any_in_string(strings, output) == True: -- cgit From 7dc16313f3426640830a79be914be9dc01d08849 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 20:09:51 -0500 Subject: Adjust Hg._vcs_revision_id for 1-indexed revision ids. --- libbe/storage/vcs/hg.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index 776d986..7e0643b 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -112,6 +112,8 @@ class Hg(base.VCS): return self._vcs_revision_id(-1) def _vcs_revision_id(self, index, style='id'): + if index > 0: + index -= 1 args = ['identify', '--rev', str(int(index)), '--%s' % style] kwargs = {'expect': (0,255)} status,output,error = self._u_invoke_client(*args, **kwargs) -- cgit From 86b5fba698855cb4709d6f009e84b4002361f0db Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 20:24:21 -0500 Subject: Adjust Bzr._vcs_revision_id for 1-indexed revision ids. --- libbe/storage/vcs/bzr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index 7d84415..04cc6c1 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -120,7 +120,7 @@ class Bzr(base.VCS): if index >= current_revision or index < -current_revision: return None if index >= 0: - return str(index+1) # bzr commit 0 is the empty tree. + return str(index) # bzr commit 0 is the empty tree. return str(current_revision+index+1) -- 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') 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 e3a48b356ace6bd88f064fb65f16c53dfdc5f8eb Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 21:56:34 -0500 Subject: Move Darcs over to Storage format. We have to work around the same issue as mercurial (hg) issue 618. I can't find a Darcs bug report for this, but it's been fixed somewhere between 1.0.9 and 2.3.1. Example scripts demonstrating the bug: $ darcs=/usr/bin/darcs $ mkdir x; cd x; $darcs init; echo a > b; $darcs add b; \ $darcs record --all --author 'x ' --logfile b; \ echo z>b; $darcs record --all --author 'x ' --logfile b; \ echo g>b; $darcs record --all --author 'x ' --logfile b; \ cd ..; rm -rf x > /dev/null; $darcs --version Finished recording patch 'a' No changes! No changes! 1.0.9 (release) And showing it's been fixed: $ darcs=~/.cabal/bin/darcs $ mkdir x; cd x; $darcs init; echo a > b; $darcs add b; \ $darcs record --all --author 'x ' --logfile b; \ echo z>b; $darcs record --all --author 'x ' --logfile b; \ echo g>b; $darcs record --all --author 'x ' --logfile b; \ cd ..; rm -rf x > /dev/null; $darcs --version Finished recording patch 'a' Finished recording patch 'z' Finished recording patch 'g' 2.3.1 (release) --- libbe/storage/vcs/darcs.py | 164 +++++++++++++++++++++++++-------------------- libbe/storage/vcs/git.py | 6 +- 2 files changed, 96 insertions(+), 74 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/darcs.py b/libbe/storage/vcs/darcs.py index d94eaef..97e31ff 100644 --- a/libbe/storage/vcs/darcs.py +++ b/libbe/storage/vcs/darcs.py @@ -22,7 +22,9 @@ Darcs backend. import codecs import os import re +import shutil import sys +import time # work around http://mercurial.selenic.com/bts/issue618 try: # import core module, Python >= 2.5 from xml.etree import ElementTree except ImportError: # look for non-core module @@ -30,7 +32,8 @@ except ImportError: # look for non-core module from xml.sax.saxutils import unescape import libbe -import vcs +import base + if libbe.TESTING == True: import doctest import unittest @@ -39,124 +42,136 @@ if libbe.TESTING == True: def new(): return Darcs() -class Darcs(vcs.VCS): - name="darcs" - client="darcs" - versioned=True +class Darcs(base.VCS): + name='darcs' + client='darcs' + + def __init__(self, *args, **kwargs): + base.VCS.__init__(self, *args, **kwargs) + self.versioned = True + self.__updated = [] # work around http://mercurial.selenic.com/bts/issue618 + def _vcs_version(self): - status,output,error = self._u_invoke_client("--version") - num_part = output.split(" ")[0] - self.parsed_version = [int(i) for i in num_part.split(".")] + status,output,error = self._u_invoke_client('--version') + num_part = output.split(' ')[0] + self.parsed_version = [int(i) for i in num_part.split('.')] return output + + def _vcs_get_user_id(self): + # following http://darcs.net/manual/node4.html#SECTION00410030000000000000 + # as of June 29th, 2009 + if self.repo == None: + return None + darcs_dir = os.path.join(self.repo, '_darcs') + if darcs_dir != None: + for pref_file in ['author', 'email']: + pref_path = os.path.join(darcs_dir, 'prefs', pref_file) + if os.path.exists(pref_path): + return self.get_file_contents(pref_path) + for env_variable in ['DARCS_EMAIL', 'EMAIL']: + if env_variable in os.environ: + return os.environ[env_variable] + return None + def _vcs_detect(self, path): if self._u_search_parent_directories(path, "_darcs") != None : return True return False + def _vcs_root(self, path): """Find the root of the deepest repository containing path.""" # Assume that nothing funny is going on; in particular, that we aren't # dealing with a bare repo. if os.path.isdir(path) != True: path = os.path.dirname(path) - darcs_dir = self._u_search_parent_directories(path, "_darcs") + darcs_dir = self._u_search_parent_directories(path, '_darcs') if darcs_dir == None: return None return os.path.dirname(darcs_dir) + def _vcs_init(self, path): - self._u_invoke_client("init", cwd=path) - def _vcs_get_user_id(self): - # following http://darcs.net/manual/node4.html#SECTION00410030000000000000 - # as of June 29th, 2009 - if self.rootdir == None: - return None - darcs_dir = os.path.join(self.rootdir, "_darcs") - if darcs_dir != None: - for pref_file in ["author", "email"]: - pref_path = os.path.join(darcs_dir, "prefs", pref_file) - if os.path.exists(pref_path): - return self.get_file_contents(pref_path) - for env_variable in ["DARCS_EMAIL", "EMAIL"]: - if env_variable in os.environ: - return os.environ[env_variable] - return None - def _vcs_set_user_id(self, value): - if self.rootdir == None: - self.root(".") - if self.rootdir == None: - raise vcs.SettingIDnotSupported - author_path = os.path.join(self.rootdir, "_darcs", "prefs", "author") - f = codecs.open(author_path, "w", self.encoding) - f.write(value) - f.close() + self._u_invoke_client('init', cwd=path) + + def _vcs_destroy(self): + vcs_dir = os.path.join(self.repo, '_darcs') + if os.path.exists(vcs_dir): + shutil.rmtree(vcs_dir) + def _vcs_add(self, path): if os.path.isdir(path): return - self._u_invoke_client("add", path) + self._u_invoke_client('add', path) + def _vcs_remove(self, path): if not os.path.isdir(self._u_abspath(path)): - os.remove(os.path.join(self.rootdir, path)) # darcs notices removal + os.remove(os.path.join(self.repo, path)) # darcs notices removal + def _vcs_update(self, path): + self.__updated.append(path) # work around http://mercurial.selenic.com/bts/issue618 pass # darcs notices changes - def _vcs_get_file_contents(self, path, revision=None, binary=False): + + def _vcs_get_file_contents(self, path, revision=None): if revision == None: - return vcs.VCS._vcs_get_file_contents(self, path, revision, - binary=binary) + return base.VCS._vcs_get_file_contents(self, path, revision) else: if self.parsed_version[0] >= 2: status,output,error = self._u_invoke_client( \ - "show", "contents", "--patch", revision, path) + 'show', 'contents', '--patch', revision, path) return output else: - # Darcs versions < 2.0.0pre2 lack the "show contents" command + # Darcs versions < 2.0.0pre2 lack the 'show contents' command status,output,error = self._u_invoke_client( \ - "diff", "--unified", "--from-patch", revision, path, + 'diff', '--unified', '--from-patch', revision, path, unicode_output=False) major_patch = output status,output,error = self._u_invoke_client( \ - "diff", "--unified", "--patch", revision, path, + 'diff', '--unified', '--patch', revision, path, unicode_output=False) target_patch = output - # "--output -" to be supported in GNU patch > 2.5.9 + # '--output -' to be supported in GNU patch > 2.5.9 # but that hasn't been released as of June 30th, 2009. # Rewrite path to status before the patch we want - args=["patch", "--reverse", path] + args=['patch', '--reverse', path] status,output,error = self._u_invoke(args, stdin=major_patch) # Now apply the patch we want - args=["patch", path] + args=['patch', path] status,output,error = self._u_invoke(args, stdin=target_patch) - if os.path.exists(os.path.join(self.rootdir, path)) == True: - contents = vcs.VCS._vcs_get_file_contents(self, path, - binary=binary) + if os.path.exists(os.path.join(self.repo, path)) == True: + contents = base.VCS._vcs_get_file_contents(self, path) else: - contents = "" + contents = '' # Now restore path to it's current incarnation - args=["patch", "--reverse", path] + args=['patch', '--reverse', path] status,output,error = self._u_invoke(args, stdin=target_patch) - args=["patch", path] + args=['patch', path] status,output,error = self._u_invoke(args, stdin=major_patch) - current_contents = vcs.VCS._vcs_get_file_contents(self, path, - binary=binary) + current_contents = base.VCS._vcs_get_file_contents(self, path) return contents - def _vcs_duplicate_repo(self, directory, revision=None): - if revision==None: - vcs.VCS._vcs_duplicate_repo(self, directory, revision) - else: - self._u_invoke_client("put", "--to-patch", revision, directory) + def _vcs_commit(self, commitfile, allow_empty=False): id = self.get_user_id() - if '@' not in id: - id = "%s <%s@invalid.com>" % (id, id) + if id == None or '@' not in id: + id = '%s <%s@invalid.com>' % (id, id) args = ['record', '--all', '--author', id, '--logfile', commitfile] status,output,error = self._u_invoke_client(*args) - empty_strings = ["No changes!"] + empty_strings = ['No changes!'] + # work around http://mercurial.selenic.com/bts/issue618 + if self._u_any_in_string(empty_strings, output) == True \ + and len(self.__updated) > 0: + time.sleep(1) + for path in self.__updated: + os.utime(os.path.join(self.repo, path), None) + status,output,error = self._u_invoke_client(*args) + self.__updated = [] + # end work around if self._u_any_in_string(empty_strings, output) == True: if allow_empty == False: - raise vcs.EmptyCommit() + raise base.EmptyCommit() # note that darcs does _not_ make an empty revision. # this returns the last non-empty revision id... revision = self._vcs_revision_id(-1) @@ -167,26 +182,33 @@ class Darcs(vcs.VCS): assert len(match.groups()) == 1 revision = match.groups()[0] return revision + def _vcs_revision_id(self, index): - status,output,error = self._u_invoke_client("changes", "--xml") + status,output,error = self._u_invoke_client('changes', '--xml') revisions = [] - xml_str = output.encode("unicode_escape").replace(r"\n", "\n") + xml_str = output.encode('unicode_escape').replace(r'\n', '\n') element = ElementTree.XML(xml_str) - assert element.tag == "changelog", element.tag + assert element.tag == 'changelog', element.tag for patch in element.getchildren(): - assert patch.tag == "patch", patch.tag + assert patch.tag == 'patch', patch.tag for child in patch.getchildren(): - if child.tag == "name": - text = unescape(unicode(child.text).decode("unicode_escape").strip()) + if child.tag == 'name': + text = unescape(unicode(child.text).decode('unicode_escape').strip()) revisions.append(text) revisions.reverse() try: - return revisions[index] + if index > 0: + return revisions[index-1] + elif index < 0: + return revisions[index] + else: + return None except IndexError: return None + if libbe.TESTING == True: - vcs.make_vcs_testcase_subclasses(Darcs, sys.modules[__name__]) + base.make_vcs_testcase_subclasses(Darcs, sys.modules[__name__]) unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py index 6d240c2..29abda7 100644 --- a/libbe/storage/vcs/git.py +++ b/libbe/storage/vcs/git.py @@ -143,12 +143,12 @@ class Git(base.VCS): if error.startswith("fatal: ambiguous argument 'HEAD': unknown "): return None raise base.CommandError(args, status, stderr=error) - commits = output.splitlines() + revisions = output.splitlines() try: if index > 0: - return commits[index-1] + return revisions[index-1] elif index < 0: - return commits[index] + return revisions[index] else: return None except IndexError: -- 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/arch.py | 199 +++++++++++++++++++++++++++------------------- libbe/storage/vcs/base.py | 19 ++++- 2 files changed, 135 insertions(+), 83 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/arch.py b/libbe/storage/vcs/arch.py index 45a3284..8afdca9 100644 --- a/libbe/storage/vcs/arch.py +++ b/libbe/storage/vcs/arch.py @@ -30,62 +30,80 @@ import sys import time import libbe -from beuuid import uuid_gen -import config -import vcs +import libbe.ui.util.user +import libbe.storage.util.config +from libbe.util.id import uuid_gen +import base + if libbe.TESTING == True: import unittest import doctest -DEFAULT_CLIENT = "tla" +class CantAddFile(Exception): + def __init__(self, file): + self.file = file + Exception.__init__(self, "Can't automatically add file %s" % file) + +DEFAULT_CLIENT = 'tla' -client = config.get_val("arch_client", default=DEFAULT_CLIENT) +client = libbe.storage.util.config.get_val( + 'arch_client', default=DEFAULT_CLIENT) def new(): return Arch() -class Arch(vcs.VCS): - name = "arch" +class Arch(base.VCS): + name = 'arch' client = client - versioned = True _archive_name = None _archive_dir = None _tmp_archive = False _project_name = None _tmp_project = False - _arch_paramdir = os.path.expanduser("~/.arch-params") + _arch_paramdir = os.path.expanduser('~/.arch-params') + + def __init__(self, *args, **kwargs): + base.VCS.__init__(self, *args, **kwargs) + self.versioned = True + self.interspersed_vcs_files = True + self.paranoid = False + def _vcs_version(self): - status,output,error = self._u_invoke_client("--version") + status,output,error = self._u_invoke_client('--version') return output + def _vcs_detect(self, path): """Detect whether a directory is revision-controlled using Arch""" - if self._u_search_parent_directories(path, "{arch}") != None : - config.set_val("arch_client", client) + if self._u_search_parent_directories(path, '{arch}') != None : + libbe.storage.util.config.set_val('arch_client', client) return True return False + def _vcs_init(self, path): self._create_archive(path) self._create_project(path) self._add_project_code(path) + def _create_archive(self, path): """ Create a temporary Arch archive in the directory PATH. This archive will be removed by - cleanup->_vcs_cleanup->_remove_archive + destroy->_vcs_destroy->_remove_archive """ # http://regexps.srparish.net/tutorial-tla/new-archive.html#Creating_a_New_Archive assert self._archive_name == None id = self.get_user_id() - name, email = self._u_parse_id(id) + name, email = libbe.ui.util.user.parse_user_id(id) if email == None: - email = "%s@example.com" % name - trailer = "%s-%s" % ("bugs-everywhere-auto", uuid_gen()[0:8]) - self._archive_name = "%s--%s" % (email, trailer) - self._archive_dir = "/tmp/%s" % trailer + email = '%s@example.com' % name + trailer = '%s-%s' % ('bugs-everywhere-auto', uuid_gen()[0:8]) + self._archive_name = '%s--%s' % (email, trailer) + self._archive_dir = '/tmp/%s' % trailer self._tmp_archive = True - self._u_invoke_client("make-archive", self._archive_name, + self._u_invoke_client('make-archive', self._archive_name, self._archive_dir, cwd=path) + def _invoke_client(self, *args, **kwargs): """ Invoke the client on our archive. @@ -96,35 +114,38 @@ class Arch(vcs.VCS): tailargs = args[1:] else: tailargs = [] - arglist = [command, "-A", self._archive_name] + arglist = [command, '-A', self._archive_name] arglist.extend(tailargs) args = tuple(arglist) return self._u_invoke_client(*args, **kwargs) + def _remove_archive(self): assert self._tmp_archive == True assert self._archive_dir != None assert self._archive_name != None os.remove(os.path.join(self._arch_paramdir, - "=locations", self._archive_name)) + '=locations', self._archive_name)) shutil.rmtree(self._archive_dir) self._tmp_archive = False self._archive_dir = False self._archive_name = False + def _create_project(self, path): """ Create a temporary Arch project in the directory PATH. This project will be removed by - cleanup->_vcs_cleanup->_remove_project + destroy->_vcs_destroy->_remove_project """ # http://mwolson.org/projects/GettingStartedWithArch.html # http://regexps.srparish.net/tutorial-tla/new-project.html#Starting_a_New_Project - category = "bugs-everywhere" - branch = "mainline" - version = "0.1" - self._project_name = "%s--%s--%s" % (category, branch, version) - self._invoke_client("archive-setup", self._project_name, + category = 'bugs-everywhere' + branch = 'mainline' + version = '0.1' + self._project_name = '%s--%s--%s' % (category, branch, version) + self._invoke_client('archive-setup', self._project_name, cwd=path) self._tmp_project = True + def _remove_project(self): assert self._tmp_project == True assert self._project_name != None @@ -132,10 +153,12 @@ class Arch(vcs.VCS): shutil.rmtree(os.path.join(self._archive_dir, self._project_name)) self._tmp_project = False self._project_name = False + def _archive_project_name(self): assert self._archive_name != None assert self._project_name != None - return "%s/%s" % (self._archive_name, self._project_name) + return '%s/%s' % (self._archive_name, self._project_name) + def _adjust_naming_conventions(self, path): """ By default, Arch restricts source code filenames to @@ -148,47 +171,53 @@ class Arch(vcs.VCS): The conventions are specified in project-root/{arch}/=tagging-method """ - tagpath = os.path.join(path, "{arch}", "=tagging-method") + tagpath = os.path.join(path, '{arch}', '=tagging-method') lines_out = [] - f = codecs.open(tagpath, "r", self.encoding) + f = codecs.open(tagpath, 'r', self.encoding) for line in f: - if line.startswith("source "): - lines_out.append("source ^[._=a-zA-X0-9].*$\n") + if line.startswith('source '): + lines_out.append('source ^[._=a-zA-X0-9].*$\n') else: lines_out.append(line) f.close() - f = codecs.open(tagpath, "w", self.encoding) - f.write("".join(lines_out)) + f = codecs.open(tagpath, 'w', self.encoding) + f.write(''.join(lines_out)) f.close() def _add_project_code(self, path): # http://mwolson.org/projects/GettingStartedWithArch.html # http://regexps.srparish.net/tutorial-tla/new-source.html # http://regexps.srparish.net/tutorial-tla/importing-first.html - self._invoke_client("init-tree", self._project_name, + self._invoke_client('init-tree', self._project_name, cwd=path) self._adjust_naming_conventions(path) - self._invoke_client("import", "--summary", "Began versioning", + self._invoke_client('import', '--summary', 'Began versioning', cwd=path) - def _vcs_cleanup(self): + + def _vcs_destroy(self): if self._tmp_project == True: self._remove_project() if self._tmp_archive == True: self._remove_archive() + vcs_dir = os.path.join(self.repo, '{arch}') + if os.path.exists(vcs_dir): + shutil.rmtree(vcs_dir) + self._archive_name = None def _vcs_root(self, path): if not os.path.isdir(path): dirname = os.path.dirname(path) else: dirname = path - status,output,error = self._u_invoke_client("tree-root", dirname) + status,output,error = self._u_invoke_client('tree-root', dirname) root = output.rstrip('\n') self._get_archive_project_name(root) return root + def _get_archive_name(self, root): - status,output,error = self._u_invoke_client("archives") + status,output,error = self._u_invoke_client('archives') lines = output.split('\n') # e.g. output: # jdoe@example.com--bugs-everywhere-auto-2008.22.24.52 @@ -198,14 +227,16 @@ class Arch(vcs.VCS): if os.path.realpath(location) == os.path.realpath(root): self._archive_name = archive assert self._archive_name != None + def _get_archive_project_name(self, root): # get project names - status,output,error = self._u_invoke_client("tree-version", cwd=root) + status,output,error = self._u_invoke_client('tree-version', cwd=root) # e.g output # jdoe@example.com--bugs-everywhere-auto-2008.22.24.52/be--mainline--0.1 archive_name,project_name = output.rstrip('\n').split('/') self._archive_name = archive_name self._project_name = project_name + def _vcs_get_user_id(self): try: status,output,error = self._u_invoke_client('my-id') @@ -215,25 +246,26 @@ class Arch(vcs.VCS): return None else: raise - def _vcs_set_user_id(self, value): - self._u_invoke_client('my-id', value) + def _vcs_add(self, path): - self._u_invoke_client("add-id", path) + self._u_invoke_client('add-id', path) realpath = os.path.realpath(self._u_abspath(path)) - pathAdded = realpath in self._list_added(self.rootdir) + pathAdded = realpath in self._list_added(self.repo) if self.paranoid and not pathAdded: self._force_source(path) + def _list_added(self, root): assert os.path.exists(root) assert os.access(root, os.X_OK) root = os.path.realpath(root) - status,output,error = self._u_invoke_client("inventory", "--source", - "--both", "--all", root) + status,output,error = self._u_invoke_client('inventory', '--source', + '--both', '--all', root) inv_str = output.rstrip('\n') return [os.path.join(root, p) for p in inv_str.split('\n')] + def _add_dir_rule(self, rule, dirname, root): inv_path = os.path.join(dirname, '.arch-inventory') - f = codecs.open(inv_path, "a", self.encoding) + f = codecs.open(inv_path, 'a', self.encoding) f.write(rule) f.close() if os.path.realpath(inv_path) not in self._list_added(root): @@ -241,47 +273,50 @@ class Arch(vcs.VCS): self.paranoid = False self.add(inv_path) self.paranoid = paranoid + def _force_source(self, path): - rule = "source %s\n" % self._u_rel_path(path) - self._add_dir_rule(rule, os.path.dirname(path), self.rootdir) - if os.path.realpath(path) not in self._list_added(self.rootdir): + rule = 'source %s\n' % self._u_rel_path(path) + self._add_dir_rule(rule, os.path.dirname(path), self.repo) + if os.path.realpath(path) not in self._list_added(self.repo): raise CantAddFile(path) + def _vcs_remove(self, path): - if not '.arch-ids' in path: - self._u_invoke_client("delete-id", path) + if self._vcs_is_versioned(path): + self._u_invoke_client('delete-id', path) + arch_ids = os.path.join(self.repo, path, '.arch-ids') + if os.path.exists(arch_ids): + shutil.rmtree(arch_ids) + def _vcs_update(self, path): pass - def _vcs_get_file_contents(self, path, revision=None, binary=False): + + def _vcs_is_versioned(self, path): + if '.arch-ids' in path: + return False + return True + + def _vcs_get_file_contents(self, path, revision=None): if revision == None: - return vcs.VCS._vcs_get_file_contents(self, path, revision, binary=binary) + return base.VCS._vcs_get_file_contents(self, path, revision) else: status,output,error = \ - self._invoke_client("file-find", path, revision) + self._invoke_client('file-find', path, revision) relpath = output.rstrip('\n') - abspath = os.path.join(self.rootdir, relpath) - f = codecs.open(abspath, "r", self.encoding) - contents = f.read() - f.close() - return contents - def _vcs_duplicate_repo(self, directory, revision=None): - if revision == None: - vcs.VCS._vcs_duplicate_repo(self, directory, revision) - else: - status,output,error = \ - self._u_invoke_client("get", revision, directory) + return base.VCS._vcs_get_file_contents(self, relpath) + def _vcs_commit(self, commitfile, allow_empty=False): if allow_empty == False: # arch applies empty commits without complaining, so check first - status,output,error = self._u_invoke_client("changes",expect=(0,1)) + status,output,error = self._u_invoke_client('changes',expect=(0,1)) if status == 0: - raise vcs.EmptyCommit() + raise base.EmptyCommit() summary,body = self._u_parse_commitfile(commitfile) - args = ["commit", "--summary", summary] + args = ['commit', '--summary', summary] if body != None: - args.extend(["--log-message",body]) + args.extend(['--log-message',body]) status,output,error = self._u_invoke_client(*args) revision = None - revline = re.compile("[*] committed (.*)") + revline = re.compile('[*] committed (.*)') match = revline.search(output) assert match != None, output+error assert len(match.groups()) == 1 @@ -290,26 +325,26 @@ class Arch(vcs.VCS): assert revpath.startswith(self._archive_project_name()+'--') revision = revpath[len(self._archive_project_name()+'--'):] return revpath + def _vcs_revision_id(self, index): - status,output,error = self._u_invoke_client("logs") + status,output,error = self._u_invoke_client('logs') logs = output.splitlines() first_log = logs.pop(0) - assert first_log == "base-0", first_log + assert first_log == 'base-0', first_log try: - log = logs[index] + if index > 0: + log = logs[index-1] + elif index < 0: + log = logs[index] + else: + return None except IndexError: return None - return "%s--%s" % (self._archive_project_name(), log) - -class CantAddFile(Exception): - def __init__(self, file): - self.file = file - Exception.__init__(self, "Can't automatically add file %s" % file) - + return '%s--%s' % (self._archive_project_name(), log) if libbe.TESTING == True: - vcs.make_vcs_testcase_subclasses(Arch, sys.modules[__name__]) + base.make_vcs_testcase_subclasses(Arch, sys.modules[__name__]) unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) 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') 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') 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') 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/arch.py | 4 ++-- libbe/storage/vcs/base.py | 5 ++++- libbe/storage/vcs/bzr.py | 6 +++--- libbe/storage/vcs/darcs.py | 6 +++--- libbe/storage/vcs/git.py | 4 ++-- libbe/storage/vcs/hg.py | 2 +- 6 files changed, 15 insertions(+), 12 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/arch.py b/libbe/storage/vcs/arch.py index 8afdca9..f1b5b7b 100644 --- a/libbe/storage/vcs/arch.py +++ b/libbe/storage/vcs/arch.py @@ -167,7 +167,7 @@ class Arch(base.VCS): http://regexps.srparish.net/tutorial-tla/naming-conventions.html Since our bug directory '.be' doesn't satisfy these conventions, we need to adjust them. - + The conventions are specified in project-root/{arch}/=tagging-method """ @@ -211,7 +211,7 @@ class Arch(base.VCS): dirname = path status,output,error = self._u_invoke_client('tree-root', dirname) root = output.rstrip('\n') - + self._get_archive_project_name(root) return root 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 diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index 04cc6c1..6f3e840 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -49,7 +49,7 @@ class Bzr(base.VCS): def _vcs_version(self): status,output,error = self._u_invoke_client('--version') - return output + return output def _vcs_get_user_id(self): status,output,error = self._u_invoke_client('whoami') @@ -88,7 +88,7 @@ class Bzr(base.VCS): return base.VCS._vcs_get_file_contents(self, path, revision) else: status,output,error = \ - self._u_invoke_client('cat', '-r', revision,path) + self._u_invoke_client('cat', '-r', revision, path) return output def _vcs_commit(self, commitfile, allow_empty=False): @@ -123,7 +123,7 @@ class Bzr(base.VCS): return str(index) # bzr commit 0 is the empty tree. return str(current_revision+index+1) - + if libbe.TESTING == True: base.make_vcs_testcase_subclasses(Bzr, sys.modules[__name__]) diff --git a/libbe/storage/vcs/darcs.py b/libbe/storage/vcs/darcs.py index 97e31ff..9a371d9 100644 --- a/libbe/storage/vcs/darcs.py +++ b/libbe/storage/vcs/darcs.py @@ -76,7 +76,7 @@ class Darcs(base.VCS): def _vcs_detect(self, path): if self._u_search_parent_directories(path, "_darcs") != None : return True - return False + return False def _vcs_root(self, path): """Find the root of the deepest repository containing path.""" @@ -129,7 +129,7 @@ class Darcs(base.VCS): 'diff', '--unified', '--patch', revision, path, unicode_output=False) target_patch = output - + # '--output -' to be supported in GNU patch > 2.5.9 # but that hasn't been released as of June 30th, 2009. @@ -206,7 +206,7 @@ class Darcs(base.VCS): except IndexError: return None - + if libbe.TESTING == True: base.make_vcs_testcase_subclasses(Darcs, sys.modules[__name__]) diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py index 29abda7..8d1b529 100644 --- a/libbe/storage/vcs/git.py +++ b/libbe/storage/vcs/git.py @@ -76,7 +76,7 @@ class Git(base.VCS): def _vcs_detect(self, path): if self._u_search_parent_directories(path, '.git') != None : return True - return False + return False def _vcs_root(self, path): """Find the root of the deepest repository containing path.""" @@ -154,7 +154,7 @@ class Git(base.VCS): except IndexError: return None - + if libbe.TESTING == True: base.make_vcs_testcase_subclasses(Git, sys.modules[__name__]) diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index 7e0643b..d2d3281 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -124,7 +124,7 @@ class Hg(base.VCS): return id return None - + if libbe.TESTING == True: base.make_vcs_testcase_subclasses(Hg, sys.modules[__name__]) -- 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') 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') 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') 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') 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 f96762deddc0cb6b1380abdcbbe7347ae23f18a1 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 28 Dec 2009 10:28:58 -0500 Subject: Bzr storage now based off bzrlib module, not 'bzr' executible. This should make repeated calls to Bzr storage instances _much_ faster, since we avoid repeatedly loading and tearing down a python subprocess. --- libbe/storage/vcs/bzr.py | 114 +++++++++++++++++++++++++++++++---------------- 1 file changed, 76 insertions(+), 38 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index 6f3e840..4e3f330 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -22,10 +22,20 @@ Bazaar (bzr) backend. """ +try: + import bzrlib + import bzrlib.branch + import bzrlib.builtins + import bzrlib.config + import bzrlib.errors + import bzrlib.option +except ImportError: + bzrlib = None import os import os.path import re import shutil +import StringIO import libbe import base @@ -41,19 +51,24 @@ def new(): class Bzr(base.VCS): name = 'bzr' - client = 'bzr' + client = None # bzrlib def __init__(self, *args, **kwargs): base.VCS.__init__(self, *args, **kwargs) self.versioned = True def _vcs_version(self): - status,output,error = self._u_invoke_client('--version') - return output + if bzrlib == None: + return None + return bzrlib.__version__ def _vcs_get_user_id(self): - status,output,error = self._u_invoke_client('whoami') - return output.rstrip('\n') + # excerpted from bzrlib.builtins.cmd_whoami.run() + try: + c = bzrlib.branch.Branch.open_containing(self.repo)[0].get_config() + except errors.NotBranchError: + c = bzrlib.config.GlobalConfig() + return c.username() def _vcs_detect(self, path): if self._u_search_parent_directories(path, '.bzr') != None : @@ -62,11 +77,15 @@ class Bzr(base.VCS): def _vcs_root(self, path): """Find the root of the deepest repository containing path.""" - status,output,error = self._u_invoke_client('root', path) - return output.rstrip('\n') + cmd = bzrlib.builtins.cmd_root() + cmd.outf = StringIO.StringIO() + cmd.run(filename=path) + return cmd.outf.getvalue().rstrip('\n') def _vcs_init(self, path): - self._u_invoke_client('init', cwd=path) + cmd = bzrlib.builtins.cmd_init() + cmd.outf = StringIO.StringIO() + cmd.run(location=path) def _vcs_destroy(self): vcs_dir = os.path.join(self.repo, '.bzr') @@ -74,50 +93,69 @@ class Bzr(base.VCS): shutil.rmtree(vcs_dir) def _vcs_add(self, path): - self._u_invoke_client('add', path) + path = os.path.join(self.repo, path) + cmd = bzrlib.builtins.cmd_add() + cmd.outf = StringIO.StringIO() + cmd.run(file_list=[path], file_ids_from=self.repo) def _vcs_remove(self, path): # --force to also remove unversioned files. - self._u_invoke_client('remove', '--force', path) + path = os.path.join(self.repo, path) + cmd = bzrlib.builtins.cmd_remove() + cmd.outf = StringIO.StringIO() + cmd.run(file_list=[path], file_deletion_strategy='force') def _vcs_update(self, path): pass + def _parse_revision_string(self, revision=None): + if revision == None: + return revision + rev_opt = bzrlib.option.Option.OPTIONS['revision'] + try: + rev_spec = rev_opt.type(revision) + except bzrlib.errors.NoSuchRevisionSpec: + raise base.InvalidRevision(revision) + return rev_spec + def _vcs_get_file_contents(self, path, revision=None): if revision == None: return base.VCS._vcs_get_file_contents(self, path, revision) - else: - status,output,error = \ - self._u_invoke_client('cat', '-r', revision, path) - return output + path = os.path.join(self.repo, path) + revision = self._parse_revision_string(revision) + cmd = bzrlib.builtins.cmd_cat() + cmd.outf = StringIO.StringIO() + try: + cmd.run(filename=path, revision=revision) + except bzrlib.errors.BzrCommandError, e: + if 'not present in revision' in str(e): + raise base.InvalidID(path) + raise + return cmd.outf.getvalue() def _vcs_commit(self, commitfile, allow_empty=False): - args = ['commit', '--file', commitfile] - if allow_empty == True: - args.append('--unchanged') - status,output,error = self._u_invoke_client(*args) - else: - kwargs = {'expect':(0,3)} - status,output,error = self._u_invoke_client(*args, **kwargs) - if status != 0: - strings = ['ERROR: no changes to commit.', # bzr 1.3.1 - 'ERROR: No changes to commit.'] # bzr 1.15.1 - if self._u_any_in_string(strings, error) == True: - raise base.EmptyCommit() - else: - raise base.CommandError(args, status, stderr=error) - revision = None - revline = re.compile('Committed revision (.*)[.]') - match = revline.search(error) - assert match != None, output+error - assert len(match.groups()) == 1 - revision = match.groups()[0] - return revision + cmd = bzrlib.builtins.cmd_commit() + cmd.outf = StringIO.StringIO() + cwd = os.getcwd() + os.chdir(self.repo) + try: + cmd.run(file=commitfile, unchanged=allow_empty) + except bzrlib.errors.BzrCommandError, e: + strings = ['no changes to commit.', # bzr 1.3.1 + 'No changes to commit.'] # bzr 1.15.1 + if self._u_any_in_string(strings, str(e)) == True: + raise base.EmptyCommit() + raise + finally: + os.chdir(cwd) + return self._vcs_revision_id(-1) def _vcs_revision_id(self, index): - status,output,error = self._u_invoke_client('revno') - current_revision = int(output) - if index >= current_revision or index < -current_revision: + cmd = bzrlib.builtins.cmd_revno() + cmd.outf = StringIO.StringIO() + cmd.run(location=self.repo) + current_revision = int(cmd.outf.getvalue()) + if index > current_revision or index < -current_revision: return None if index >= 0: return str(index) # bzr commit 0 is the empty tree. -- 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 +++++++++++++++++++++++++++++++++++++++++------ libbe/storage/vcs/bzr.py | 29 +++++++++++++++++- 2 files changed, 96 insertions(+), 11 deletions(-) (limited to 'libbe/storage/vcs') 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. diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index 4e3f330..d6e7799 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -129,10 +129,37 @@ class Bzr(base.VCS): cmd.run(filename=path, revision=revision) except bzrlib.errors.BzrCommandError, e: if 'not present in revision' in str(e): - raise base.InvalidID(path) + raise base.InvalidID(path, revision) raise return cmd.outf.getvalue() + def _vcs_path(self, id, revision): + return self._u_find_id(id, revision) + + def _vcs_isdir(self, path, revision): + try: + self._vcs_listdir(path, revision) + except AttributeError, e: + if 'children' in str(e): + return False + raise + return True + + def _vcs_listdir(self, path, revision): + path = os.path.join(self.repo, path) + revision = self._parse_revision_string(revision) + cmd = bzrlib.builtins.cmd_ls() + cmd.outf = StringIO.StringIO() + try: + cmd.run(revision=revision, path=path) + except bzrlib.errors.BzrCommandError, e: + if 'not present in revision' in str(e): + raise base.InvalidID(path, revision) + raise + children = cmd.outf.getvalue().rstrip('\n').splitlines() + children = [self._u_rel_path(c, path) for c in children] + return children + def _vcs_commit(self, commitfile, allow_empty=False): cmd = bzrlib.builtins.cmd_commit() cmd.outf = StringIO.StringIO() -- 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 ++++-- libbe/storage/vcs/bzr.py | 4 ++-- libbe/storage/vcs/git.py | 20 ++++++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) (limited to 'libbe/storage/vcs') 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) diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index d6e7799..397267a 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -129,7 +129,7 @@ class Bzr(base.VCS): cmd.run(filename=path, revision=revision) except bzrlib.errors.BzrCommandError, e: if 'not present in revision' in str(e): - raise base.InvalidID(path, revision) + raise base.InvalidPath(path, root=self.repo, revision=revision) raise return cmd.outf.getvalue() @@ -154,7 +154,7 @@ class Bzr(base.VCS): cmd.run(revision=revision, path=path) except bzrlib.errors.BzrCommandError, e: if 'not present in revision' in str(e): - raise base.InvalidID(path, revision) + raise base.InvalidPath(path, root=self.repo, revision=revision) raise children = cmd.outf.getvalue().rstrip('\n').splitlines() children = [self._u_rel_path(c, path) for c in children] diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py index 8d1b529..35dcd68 100644 --- a/libbe/storage/vcs/git.py +++ b/libbe/storage/vcs/git.py @@ -118,6 +118,26 @@ class Git(base.VCS): status,output,error = self._u_invoke_client('show', arg) return output + + def _vcs_path(self, id, revision): + return self._u_find_id(id, revision) + + def _vcs_isdir(self, path, revision): + arg = '%s:%s' % (revision,path) + args = ['ls-tree', arg] + status,output,error = self._u_invoke_client(*args, expect=(0,128)) + if status != 0: + if 'not a tree object' in error: + return False + raise base.CommandError(args, status, stderr=error) + return True + + def _vcs_listdir(self, path, revision): + arg = '%s:%s' % (revision,path) + status,output,error = self._u_invoke_client( + 'ls-tree', '--name-only', arg) + return output.rstrip('\n').splitlines() + def _vcs_commit(self, commitfile, allow_empty=False): args = ['commit', '--all', '--file', commitfile] if allow_empty == True: -- cgit From 0aa80631bd2dc0a5f28f1dd7db2cbda7d14e67fe Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 05:52:27 -0500 Subject: Hg storage now based off mercurial module, not 'hg' executible. This should make repeated calls to Hg storage instances _much_ faster, since we avoid repeatedly loading and tearing down a python subprocess. For example, the testsuite runs ~6x faster on my box. Here's a run with the old Hg implementation: $ python test.py libbe.storage.vcs.hg ... ================================= ERROR: test_get_previous_children --------------------------------- Traceback (most recent call last): ... NotImplementedError --------------------------------- Ran 49 tests in 133.285s FAILED (errors=1) A run with the new implementation gives the same results, except for: Ran 49 tests in 22.328s --- libbe/storage/vcs/bzr.py | 2 +- libbe/storage/vcs/hg.py | 60 +++++++++++++++++++++++++++++++----------------- 2 files changed, 40 insertions(+), 22 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index 397267a..7335861 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -51,7 +51,7 @@ def new(): class Bzr(base.VCS): name = 'bzr' - client = None # bzrlib + client = None # bzrlib module def __init__(self, *args, **kwargs): base.VCS.__init__(self, *args, **kwargs) diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index d2d3281..373dfd2 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -21,10 +21,21 @@ Mercurial (hg) backend. """ +try: + # enable importing on demand to reduce startup time + from mercurial import demandimport; demandimport.enable() + import mercurial + import mercurial.version + import mercurial.dispatch + import mercurial.ui +except ImportError: + mercurial = None import os import os.path import re import shutil +import StringIO +import sys import time # work around http://mercurial.selenic.com/bts/issue618 import libbe @@ -41,7 +52,7 @@ def new(): class Hg(base.VCS): name='hg' - client='hg' + client=None # mercurial module def __init__(self, *args, **kwargs): base.VCS.__init__(self, *args, **kwargs) @@ -49,13 +60,26 @@ class Hg(base.VCS): self.__updated = [] # work around http://mercurial.selenic.com/bts/issue618 def _vcs_version(self): - status,output,error = self._u_invoke_client('--version') - return output + if mercurial == None: + return None + return mercurial.version.get_version() + + def _u_invoke_client(self, *args, **kwargs): + if 'cwd' not in kwargs: + kwargs['cwd'] = self.repo + assert len(kwargs) == 1, kwargs + ui = mercurial.ui.ui(interactive=False) + fullargs = ['--cwd', kwargs['cwd']] + fullargs.extend(args) + stdout = sys.stdout + tmp_stdout = StringIO.StringIO() + sys.stdout = tmp_stdout + mercurial.dispatch._dispatch(ui, fullargs) + sys.stdout = stdout + return tmp_stdout.getvalue().rstrip('\n') def _vcs_get_user_id(self): - status,output,error = self._u_invoke_client( - 'showconfig', 'ui.username') - return output.rstrip('\n') + return self._u_invoke_client('showconfig', 'ui.username') def _vcs_detect(self, path): """Detect whether a directory is revision-controlled using Mercurial""" @@ -64,8 +88,7 @@ class Hg(base.VCS): return False def _vcs_root(self, path): - status,output,error = self._u_invoke_client('root', cwd=path) - return output.rstrip('\n') + return self._u_invoke_client('root', cwd=path) def _vcs_init(self, path): self._u_invoke_client('init', cwd=path) @@ -88,13 +111,11 @@ class Hg(base.VCS): if revision == None: return base.VCS._vcs_get_file_contents(self, path, revision) else: - status,output,error = \ - self._u_invoke_client('cat', '-r', revision, path) - return output + return self._u_invoke_client('cat', '-r', revision, path) def _vcs_commit(self, commitfile, allow_empty=False): args = ['commit', '--logfile', commitfile] - status,output,error = self._u_invoke_client(*args) + output = self._u_invoke_client(*args) # work around http://mercurial.selenic.com/bts/issue618 strings = ['nothing changed'] if self._u_any_in_string(strings, output) == True \ @@ -102,7 +123,7 @@ class Hg(base.VCS): time.sleep(1) for path in self.__updated: os.utime(os.path.join(self.repo, path), None) - status,output,error = self._u_invoke_client(*args) + output = self._u_invoke_client(*args) self.__updated = [] # end work around if allow_empty == False: @@ -115,14 +136,11 @@ class Hg(base.VCS): if index > 0: index -= 1 args = ['identify', '--rev', str(int(index)), '--%s' % style] - kwargs = {'expect': (0,255)} - status,output,error = self._u_invoke_client(*args, **kwargs) - if status == 0: - id = output.strip() - if id == '000000000000': - return None # before initial commit. - return id - return None + output = self._u_invoke_client(*args) + id = output.strip() + if id == '000000000000': + return None # before initial commit. + return id if libbe.TESTING == True: -- 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 +++++---- libbe/storage/vcs/hg.py | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) (limited to 'libbe/storage/vcs') 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): """ diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index 373dfd2..6baf19c 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -43,7 +43,6 @@ import base if libbe.TESTING == True: import doctest - import sys import unittest @@ -113,6 +112,38 @@ class Hg(base.VCS): else: return self._u_invoke_client('cat', '-r', revision, path) + def _vcs_path(self, id, revision): + output = self._u_invoke_client('manifest', '--rev', revision) + 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 output.splitlines() 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: + if self._u_path_to_id(file) == id: + return file + raise base.InvalidId(id, revision=revision) + + def _vcs_isdir(self, path, revision): + output = self._u_invoke_client('manifest', '--rev', revision) + files = output.splitlines() + if path in files: + return False + return True + + def _vcs_listdir(self, path, revision): + output = self._u_invoke_client('manifest', '--rev', revision) + files = output.splitlines() + path = path.rstrip(os.path.sep) + os.path.sep + return [self._u_rel_path(f, path) for f in files if f.startswith(path)] + def _vcs_commit(self, commitfile, allow_empty=False): args = ['commit', '--logfile', commitfile] output = self._u_invoke_client(*args) -- cgit From 8bf8e2271f8273bdba3f8327d08b505a0fae11d5 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 06:40:38 -0500 Subject: Adjust Git._vcs_isdir() to Python-2.5-compatible syntax --- libbe/storage/vcs/git.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py index 35dcd68..2280665 100644 --- a/libbe/storage/vcs/git.py +++ b/libbe/storage/vcs/git.py @@ -125,7 +125,8 @@ class Git(base.VCS): def _vcs_isdir(self, path, revision): arg = '%s:%s' % (revision,path) args = ['ls-tree', arg] - status,output,error = self._u_invoke_client(*args, expect=(0,128)) + kwargs = {'expect':(0,128)} + status,output,error = self._u_invoke_client(*args, **kwargs) if status != 0: if 'not a tree object' in error: return False -- 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') 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') 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 268713c0e2ed76edd84a2196b5c14fe1bc4ff08a Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 10:39:09 -0500 Subject: Updated Darcs backend towards supporting .children(revision). ._vcs_isdir() and ._vcs_listdir() will need to parse the output of darcs show files [options] --patch REVISION PATH but both the --patch option and the PATH argument are new, and I can't get a recent enough version of Darcs to compile on my system. Theoretically they will work, but they remain untested for now. I don't think it's worth rolling my own darcs show files --patch REVISION to support earlier versions of Darcs, since the only solution I can think of now would be to check out the given revision and use os.walk() or some such, and that would be really ugly... Also added .version_cmp() for easy version comparison. Reindented ._vcs_get_file_contents() to remove trailing elses since the if clauses all contain returns. --- libbe/storage/vcs/darcs.py | 154 +++++++++++++++++++++++++++++++++------------ 1 file changed, 114 insertions(+), 40 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/darcs.py b/libbe/storage/vcs/darcs.py index 9a371d9..6d47ea5 100644 --- a/libbe/storage/vcs/darcs.py +++ b/libbe/storage/vcs/darcs.py @@ -53,9 +53,47 @@ class Darcs(base.VCS): def _vcs_version(self): status,output,error = self._u_invoke_client('--version') - num_part = output.split(' ')[0] - self.parsed_version = [int(i) for i in num_part.split('.')] - return output + return output.rstrip('\n') + + def version_cmp(self, *args): + """ + Compare the installed darcs version V_i with another version + V_o (given in *args). Returns + 1 if V_i > V_o, + 0 if V_i == V_o, and + -1 if V_i < V_o + >>> d = Darcs(repo='.') + >>> d._vcs_version = lambda : "2.3.1 (release)" + >>> d.version_cmp(2,3,1) + 0 + >>> d.version_cmp(2,3,2) + -1 + >>> d.version_cmp(2,3,0) + 1 + >>> d.version_cmp(3) + -1 + >>> d._vcs_version = lambda : "2.0.0pre2" + >>> d._parsed_version = None + >>> d.version_cmp(3) + Traceback (most recent call last): + ... + NotImplementedError: Cannot parse "2.0.0pre2" portion of Darcs version "2.0.0pre2" + invalid literal for int() with base 10: '0pre2' + """ + if not hasattr(self, '_parsed_version') \ + or self._parsed_version == None: + num_part = self._vcs_version().split(' ')[0] + try: + self._parsed_version = [int(i) for i in num_part.split('.')] + except ValueError, e: + raise NotImplementedError( + 'Cannot parse "%s" portion of Darcs version "%s"\n %s' + % (num_part, self._vcs_version(), str(e))) + cmps = [cmp(a,b) for a,b in zip(self._parsed_version, args)] + for c in cmps: + if c != 0: + return c + return 0 def _vcs_get_user_id(self): # following http://darcs.net/manual/node4.html#SECTION00410030000000000000 @@ -113,45 +151,81 @@ class Darcs(base.VCS): def _vcs_get_file_contents(self, path, revision=None): if revision == None: return base.VCS._vcs_get_file_contents(self, path, revision) + if self.version_cmp(2, 0, 0) == 1: + status,output,error = self._u_invoke_client( \ + 'show', 'contents', '--patch', revision, path) + return output + # Darcs versions < 2.0.0pre2 lack the 'show contents' command + + status,output,error = self._u_invoke_client( \ + 'diff', '--unified', '--from-patch', revision, path, + unicode_output=False) + major_patch = output + status,output,error = self._u_invoke_client( \ + 'diff', '--unified', '--patch', revision, path, + unicode_output=False) + target_patch = output + + # '--output -' to be supported in GNU patch > 2.5.9 + # but that hasn't been released as of June 30th, 2009. + + # Rewrite path to status before the patch we want + args=['patch', '--reverse', path] + status,output,error = self._u_invoke(args, stdin=major_patch) + # Now apply the patch we want + args=['patch', path] + status,output,error = self._u_invoke(args, stdin=target_patch) + + if os.path.exists(os.path.join(self.repo, path)) == True: + contents = base.VCS._vcs_get_file_contents(self, path) else: - if self.parsed_version[0] >= 2: - status,output,error = self._u_invoke_client( \ - 'show', 'contents', '--patch', revision, path) - return output + contents = '' + + # Now restore path to it's current incarnation + args=['patch', '--reverse', path] + status,output,error = self._u_invoke(args, stdin=target_patch) + args=['patch', path] + status,output,error = self._u_invoke(args, stdin=major_patch) + current_contents = base.VCS._vcs_get_file_contents(self, path) + return contents + + def _vcs_path(self, id, revision): + return self._u_find_id(id, revision) + + def _vcs_isdir(self, path, revision): + if self.version_cmp(2, 3, 1) == 1: + # Sun Nov 15 20:32:06 EST 2009 thomashartman1@gmail.com + # * add versioned show files functionality (darcs show files -p 'some patch') + status,output,error = self._u_invoke_client( \ + 'show', 'files', '--no-files', '--patch', revision) + children = output.rstrip('\n').splitlines() + rpath = '.' + children = [self._u_rel_path(c, rpath) for c in children] + if path in children: + return True + return False + # Darcs versions <= 2.3.1 lack the --patch option for 'show files' + raise NotImplementedError + + def _vcs_listdir(self, path, revision): + if self.version_cmp(2, 3, 1) == 1: + # Sun Nov 15 20:32:06 EST 2009 thomashartman1@gmail.com + # * add versioned show files functionality (darcs show files -p 'some patch') + # Wed Dec 9 05:42:21 EST 2009 Luca Molteni + # * resolve issue835 show file with file directory arguments + path = path.rstrip(os.path.sep) + status,output,error = self._u_invoke_client( \ + 'show', 'files', '--patch', revision, path) + files = output.rstrip('\n').splitlines() + if path == '.': + descendents = [self._u_rel_path(f, path) for f in files + if f != '.'] else: - # Darcs versions < 2.0.0pre2 lack the 'show contents' command - - status,output,error = self._u_invoke_client( \ - 'diff', '--unified', '--from-patch', revision, path, - unicode_output=False) - major_patch = output - status,output,error = self._u_invoke_client( \ - 'diff', '--unified', '--patch', revision, path, - unicode_output=False) - target_patch = output - - # '--output -' to be supported in GNU patch > 2.5.9 - # but that hasn't been released as of June 30th, 2009. - - # Rewrite path to status before the patch we want - args=['patch', '--reverse', path] - status,output,error = self._u_invoke(args, stdin=major_patch) - # Now apply the patch we want - args=['patch', path] - status,output,error = self._u_invoke(args, stdin=target_patch) - - if os.path.exists(os.path.join(self.repo, path)) == True: - contents = base.VCS._vcs_get_file_contents(self, path) - else: - contents = '' - - # Now restore path to it's current incarnation - args=['patch', '--reverse', path] - status,output,error = self._u_invoke(args, stdin=target_patch) - args=['patch', path] - status,output,error = self._u_invoke(args, stdin=major_patch) - current_contents = base.VCS._vcs_get_file_contents(self, path) - return contents + descendents = [self._u_rel_path(f, path) for f in files + if f.startswith(path)] + return [f for f in descendents if f.count(os.path.sep) == 0] + # Darcs versions <= 2.3.1 lack the --patch option for 'show files' + raise NotImplementedError def _vcs_commit(self, commitfile, allow_empty=False): id = self.get_user_id() -- cgit From d595aba006a39a2d75067fb7fc82956538e7e16d Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 20:13:43 -0500 Subject: We don't do much with Mercurial's ui, so _dispatch -> dispatch --- libbe/storage/vcs/hg.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index 6baf19c..19e3585 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -67,13 +67,12 @@ class Hg(base.VCS): if 'cwd' not in kwargs: kwargs['cwd'] = self.repo assert len(kwargs) == 1, kwargs - ui = mercurial.ui.ui(interactive=False) fullargs = ['--cwd', kwargs['cwd']] fullargs.extend(args) stdout = sys.stdout tmp_stdout = StringIO.StringIO() sys.stdout = tmp_stdout - mercurial.dispatch._dispatch(ui, fullargs) + mercurial.dispatch.dispatch(fullargs) sys.stdout = stdout return tmp_stdout.getvalue().rstrip('\n') -- 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') 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') 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 ec2472daa930a46949fcdb3fb9ca715f9bb3f200 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 21:25:33 -0500 Subject: Disable mercurial.demandimport, since it breaks Bzr Running python test.py libbe.storage.vcs.hg libbe.storage.vcs.bzr with the old setup produced lots of Traceback (most recent call last): File ".../libbe/storage/vcs/base.py", line 1010, in setUp self.s.init() File ".../libbe/storage/base.py", line 170, in init return self._init() File ".../libbe/storage/vcs/base.py", line 664, in _init self._vcs_init(self.repo) File ".../libbe/storage/vcs/bzr.py", line 88, in _vcs_init cmd.run(location=path) File ".../python2.5/site-packages/bzrlib/builtins.py", line 1685, in run format = bzrdir.format_registry.make_bzrdir('default') File ".../python2.5/site-packages/bzrlib/bzrdir.py", line 3452, in make_bzrdir return self.get(key)() File ".../python2.5/site-packages/bzrlib/bzrdir.py", line 3398, in helper bd.set_branch_format(_load(branch_format)) File ".../python2.5/site-packages/bzrlib/bzrdir.py", line 3385, in _load [factory_name]) File "/var/lib/python-support/python2.5/mercurial/demandimport.py", line 108, in _demandimport setattr(mod, x, _demandmod(x, mod.__dict__, locals)) File ".../python2.5/site-packages/bzrlib/lazy_import.py", line 106, in __getattribute__ obj = _replace() File ".../python2.5/site-packages/bzrlib/lazy_import.py", line 88, in _replace extra=e) IllegalUseOfScopeReplacer: ScopeReplacer object 'branch' was used incorrectly: Object already cleaned up, did you assign it to another variable?: _factory --- libbe/storage/vcs/hg.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index 19e3585..b280ff2 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -22,8 +22,6 @@ Mercurial (hg) backend. """ try: - # enable importing on demand to reduce startup time - from mercurial import demandimport; demandimport.enable() import mercurial import mercurial.version import mercurial.dispatch -- cgit From 16877141d526a5387a0f673b56c1cd6f3b900674 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 21:37:14 -0500 Subject: Correct for possible directory changes in mercurial.dispatch.dispatch() I ran across this when the hg unittests broke the vcs.base unittests: $ python test.py libbe.storage.vcs.base libbe.storage.vcs.hg ... OK $ python test.py libbe.storage.vcs.hg libbe.storage.vcs.base ... File ".../libbe/storage/vcs/base.py", line 914, in libbe.storage.vcs.base.VCSTestCase.Class._u_rel_path Failed example: vcs._u_rel_path("./a", ".") Exception raised: Traceback (most recent call last): File "/usr/lib/python2.5/doctest.py", line 1228, in __run compileflags, 1) in test.globs File "", line 1, in vcs._u_rel_path("./a", ".") File ".../libbe/storage/vcs/base.py", line 921, in _u_rel_path path = os.path.abspath(path) File "/usr/lib/python2.5/posixpath.py", line 403, in abspath path = join(os.getcwd(), path) OSError: [Errno 2] No such file or directory ... FAILED (failures=1) --- libbe/storage/vcs/hg.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index b280ff2..11494a9 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -70,7 +70,9 @@ class Hg(base.VCS): stdout = sys.stdout tmp_stdout = StringIO.StringIO() sys.stdout = tmp_stdout + cwd = os.getcwd() mercurial.dispatch.dispatch(fullargs) + os.chdir(cwd) sys.stdout = stdout return tmp_stdout.getvalue().rstrip('\n') -- 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') 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/__init__.py | 16 +++++++++++++++- libbe/storage/vcs/arch.py | 2 +- libbe/storage/vcs/base.py | 2 +- libbe/storage/vcs/bzr.py | 2 +- libbe/storage/vcs/darcs.py | 4 ++-- libbe/storage/vcs/git.py | 2 +- libbe/storage/vcs/hg.py | 2 +- 7 files changed, 22 insertions(+), 8 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/__init__.py b/libbe/storage/vcs/__init__.py index ddfb00a..777c723 100644 --- a/libbe/storage/vcs/__init__.py +++ b/libbe/storage/vcs/__init__.py @@ -1,4 +1,18 @@ -# Copyright +# Copyright (C) 2009-2010 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. import base diff --git a/libbe/storage/vcs/arch.py b/libbe/storage/vcs/arch.py index f1b5b7b..f9ae32b 100644 --- a/libbe/storage/vcs/arch.py +++ b/libbe/storage/vcs/arch.py @@ -1,4 +1,4 @@ -# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. # Ben Finney # Gianluca Montecchi # James Rowe 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 diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index 7335861..4d72fd0 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -1,4 +1,4 @@ -# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. # Ben Finney # Gianluca Montecchi # Marien Zwart diff --git a/libbe/storage/vcs/darcs.py b/libbe/storage/vcs/darcs.py index 6d47ea5..92dc1a7 100644 --- a/libbe/storage/vcs/darcs.py +++ b/libbe/storage/vcs/darcs.py @@ -1,5 +1,5 @@ -# Copyright (C) 2009 Gianluca Montecchi -# W. Trevor King +# Copyright (C) 2009-2010 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 diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py index 2280665..5382c7e 100644 --- a/libbe/storage/vcs/git.py +++ b/libbe/storage/vcs/git.py @@ -1,4 +1,4 @@ -# Copyright (C) 2008-2009 Ben Finney +# Copyright (C) 2008-2010 Ben Finney # Chris Ball # Gianluca Montecchi # W. Trevor King diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index 11494a9..824f687 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -1,4 +1,4 @@ -# Copyright (C) 2007-2009 Aaron Bentley and Panometrics, Inc. +# Copyright (C) 2007-2010 Aaron Bentley and Panometrics, Inc. # Ben Finney # Gianluca Montecchi # W. Trevor King -- 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') 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') 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') 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 310d934c08e99c10438df4e61e9f3e716444a57a Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 18 Jan 2010 08:06:30 -0500 Subject: Add .changed() support to Git --- libbe/storage/vcs/git.py | 81 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py index 5382c7e..6d3aa87 100644 --- a/libbe/storage/vcs/git.py +++ b/libbe/storage/vcs/git.py @@ -175,6 +175,87 @@ class Git(base.VCS): except IndexError: return None + def _diff(self, revision): + status,output,error = self._u_invoke_client('diff', revision) + return output + + def _parse_diff(self, diff_text): + """ + Example diff text: + + diff --git a/dir/changed b/dir/changed + index 6c3ea8c..2f2f7c7 100644 + --- a/dir/changed + +++ b/dir/changed + @@ -1,3 +1,3 @@ + hi + -there + +everyone and + joe + diff --git a/dir/deleted b/dir/deleted + deleted file mode 100644 + index 225ec04..0000000 + --- a/dir/deleted + +++ /dev/null + @@ -1,3 +0,0 @@ + -in + -the + -beginning + diff --git a/dir/moved b/dir/moved + deleted file mode 100644 + index 5ef102f..0000000 + --- a/dir/moved + +++ /dev/null + @@ -1,4 +0,0 @@ + -the + -ants + -go + -marching + diff --git a/dir/moved2 b/dir/moved2 + new file mode 100644 + index 0000000..5ef102f + --- /dev/null + +++ b/dir/moved2 + @@ -0,0 +1,4 @@ + +the + +ants + +go + +marching + diff --git a/dir/new b/dir/new + new file mode 100644 + index 0000000..94954ab + --- /dev/null + +++ b/dir/new + @@ -0,0 +1,2 @@ + +hello + +world + """ + new = [] + modified = [] + removed = [] + lines = diff_text.splitlines() + for i,line in enumerate(lines): + if not line.startswith('diff '): + continue + file_a,file_b = line.split()[-2:] + assert file_a.startswith('a/'), \ + 'missformed file_a %s' % file_a + assert file_b.startswith('b/'), \ + 'missformed file_a %s' % file_b + file = file_a[2:] + assert file_b[2:] == file, \ + 'diff file missmatch %s != %s' % (file_a, file_b) + if lines[i+1].startswith('new '): + new.append(file) + elif lines[i+1].startswith('index '): + modified.append(file) + elif lines[i+1].startswith('deleted '): + removed.append(file) + return (new,modified,removed) + + def _vcs_changed(self, revision): + return self._parse_diff(self._diff(revision)) + if libbe.TESTING == True: base.make_vcs_testcase_subclasses(Git, sys.modules[__name__]) -- cgit From 9fd1decbc4631a8d4d3fcbfde11358ec215be162 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 18 Jan 2010 08:45:16 -0500 Subject: Add .changed() support to Bzr --- libbe/storage/vcs/bzr.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index 4d72fd0..285ecf1 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -36,13 +36,13 @@ import os.path import re import shutil import StringIO +import sys import libbe import base if libbe.TESTING == True: import doctest - import sys import unittest @@ -188,6 +188,88 @@ class Bzr(base.VCS): return str(index) # bzr commit 0 is the empty tree. return str(current_revision+index+1) + def _diff(self, revision): + revision = self._parse_revision_string(revision) + cmd = bzrlib.builtins.cmd_diff() + cmd.outf = StringIO.StringIO() + # for some reason, cmd_diff uses sys.stdout not self.outf for output. + stdout = sys.stdout + sys.stdout = cmd.outf + try: + status = cmd.run(revision=revision, file_list=[self.repo]) + finally: + sys.stdout = stdout + assert status in [0,1], "Invalid status %d" % status + return cmd.outf.getvalue() + + def _parse_diff(self, diff_text): + """ + Example diff text: + + === modified file 'dir/changed' + --- dir/changed 2010-01-16 01:54:53 +0000 + +++ dir/changed 2010-01-16 01:54:54 +0000 + @@ -1,3 +1,3 @@ + hi + -there + +everyone and + joe + + === removed file 'dir/deleted' + --- dir/deleted 2010-01-16 01:54:53 +0000 + +++ dir/deleted 1970-01-01 00:00:00 +0000 + @@ -1,3 +0,0 @@ + -in + -the + -beginning + + === removed file 'dir/moved' + --- dir/moved 2010-01-16 01:54:53 +0000 + +++ dir/moved 1970-01-01 00:00:00 +0000 + @@ -1,4 +0,0 @@ + -the + -ants + -go + -marching + + === added file 'dir/moved2' + --- dir/moved2 1970-01-01 00:00:00 +0000 + +++ dir/moved2 2010-01-16 01:54:34 +0000 + @@ -0,0 +1,4 @@ + +the + +ants + +go + +marching + + === added file 'dir/new' + --- dir/new 1970-01-01 00:00:00 +0000 + +++ dir/new 2010-01-16 01:54:54 +0000 + @@ -0,0 +1,2 @@ + +hello + +world + + """ + new = [] + modified = [] + removed = [] + lines = diff_text.splitlines() + for i,line in enumerate(lines): + if not line.startswith('=== '): + continue + fields = line.split() + action = fields[1] + file = fields[-1].strip("'") + if action == 'added': + new.append(file) + elif action == 'modified': + modified.append(file) + elif action == 'removed': + removed.append(file) + return (new,modified,removed) + + def _vcs_changed(self, revision): + return self._parse_diff(self._diff(revision)) + if libbe.TESTING == True: base.make_vcs_testcase_subclasses(Bzr, sys.modules[__name__]) -- 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') 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') 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') 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 ++++++++++++++++++++++++++++- libbe/storage/vcs/bzr.py | 11 ++++++----- libbe/storage/vcs/hg.py | 20 +++----------------- 3 files changed, 37 insertions(+), 23 deletions(-) (limited to 'libbe/storage/vcs') 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. diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index 285ecf1..e1cd2e5 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -134,7 +134,9 @@ class Bzr(base.VCS): return cmd.outf.getvalue() def _vcs_path(self, id, revision): - return self._u_find_id(id, revision) + manifest = self._vcs_listdir( + self.repo, revision=revision, recursive=True) + return self._u_find_id_from_manifest(id, manifest, revision=revision) def _vcs_isdir(self, path, revision): try: @@ -145,13 +147,13 @@ class Bzr(base.VCS): raise return True - def _vcs_listdir(self, path, revision): + def _vcs_listdir(self, path, revision, recursive=False): path = os.path.join(self.repo, path) revision = self._parse_revision_string(revision) cmd = bzrlib.builtins.cmd_ls() cmd.outf = StringIO.StringIO() try: - cmd.run(revision=revision, path=path) + cmd.run(revision=revision, path=path, recursive=recursive) except bzrlib.errors.BzrCommandError, e: if 'not present in revision' in str(e): raise base.InvalidPath(path, root=self.repo, revision=revision) @@ -252,8 +254,7 @@ class Bzr(base.VCS): new = [] modified = [] removed = [] - lines = diff_text.splitlines() - for i,line in enumerate(lines): + for line in diff_text.splitlines(): if not line.startswith('=== '): continue fields = line.split() diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index 824f687..5295a57 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -112,23 +112,9 @@ class Hg(base.VCS): return self._u_invoke_client('cat', '-r', revision, path) def _vcs_path(self, id, revision): - output = self._u_invoke_client('manifest', '--rev', revision) - 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 output.splitlines() 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: - if self._u_path_to_id(file) == id: - return file - raise base.InvalidId(id, revision=revision) + manifest = self._u_invoke_client( + 'manifest', '--rev', revision).splitlines() + return self._u_find_id_from_manifest(id, manifest, revision=revision) def _vcs_isdir(self, path, revision): output = self._u_invoke_client('manifest', '--rev', 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') 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 51f24ff4b3bae358ecd9903537885f4eaf0d1e4b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 18 Jan 2010 13:12:46 -0500 Subject: Add .changed() support to Hg --- libbe/storage/vcs/hg.py | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index 5295a57..076943a 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -158,6 +158,77 @@ class Hg(base.VCS): return None # before initial commit. return id + def _diff(self, revision): + return self._u_invoke_client( + 'diff', '-r', revision, '--git') + + def _parse_diff(self, diff_text): + """ + Example diff text: + + diff --git a/.be/dir/bugs/modified b/.be/dir/bugs/modified + --- a/.be/dir/bugs/modified + +++ b/.be/dir/bugs/modified + @@ -1,1 +1,1 @@ some value to be modified + -some value to be modified + \ No newline at end of file + +a new value + \ No newline at end of file + diff --git a/.be/dir/bugs/moved b/.be/dir/bugs/moved + deleted file mode 100644 + --- a/.be/dir/bugs/moved + +++ /dev/null + @@ -1,1 +0,0 @@ + -this entry will be moved + \ No newline at end of file + diff --git a/.be/dir/bugs/moved2 b/.be/dir/bugs/moved2 + new file mode 100644 + --- /dev/null + +++ b/.be/dir/bugs/moved2 + @@ -0,0 +1,1 @@ + +this entry will be moved + \ No newline at end of file + diff --git a/.be/dir/bugs/new b/.be/dir/bugs/new + new file mode 100644 + --- /dev/null + +++ b/.be/dir/bugs/new + @@ -0,0 +1,1 @@ + +this entry is new + \ No newline at end of file + diff --git a/.be/dir/bugs/removed b/.be/dir/bugs/removed + deleted file mode 100644 + --- a/.be/dir/bugs/removed + +++ /dev/null + @@ -1,1 +0,0 @@ + -this entry will be deleted + \ No newline at end of file + """ + new = [] + modified = [] + removed = [] + lines = diff_text.splitlines() + for i,line in enumerate(lines): + if not line.startswith('diff '): + continue + file_a,file_b = line.split()[-2:] + assert file_a.startswith('a/'), \ + 'missformed file_a %s' % file_a + assert file_b.startswith('b/'), \ + 'missformed file_a %s' % file_b + file = file_a[2:] + assert file_b[2:] == file, \ + 'diff file missmatch %s != %s' % (file_a, file_b) + if lines[i+1].startswith('new '): + new.append(file) + elif lines[i+1].startswith('deleted '): + removed.append(file) + else: + modified.append(file) + return (new,modified,removed) + + def _vcs_changed(self, revision): + return self._parse_diff(self._diff(revision)) + if libbe.TESTING == True: base.make_vcs_testcase_subclasses(Hg, sys.modules[__name__]) -- cgit From a1eaad0ac5250ed062d86a3636ca06ec9aa95b67 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 18 Jan 2010 16:35:33 -0500 Subject: Added changed() support for Darcs --- libbe/storage/vcs/darcs.py | 127 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 110 insertions(+), 17 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/darcs.py b/libbe/storage/vcs/darcs.py index 92dc1a7..c6892b4 100644 --- a/libbe/storage/vcs/darcs.py +++ b/libbe/storage/vcs/darcs.py @@ -157,24 +157,14 @@ class Darcs(base.VCS): return output # Darcs versions < 2.0.0pre2 lack the 'show contents' command - status,output,error = self._u_invoke_client( \ - 'diff', '--unified', '--from-patch', revision, path, - unicode_output=False) - major_patch = output - status,output,error = self._u_invoke_client( \ - 'diff', '--unified', '--patch', revision, path, - unicode_output=False) - target_patch = output + patch = self._diff(revision, path=path, unicode_output=False) # '--output -' to be supported in GNU patch > 2.5.9 # but that hasn't been released as of June 30th, 2009. # Rewrite path to status before the patch we want args=['patch', '--reverse', path] - status,output,error = self._u_invoke(args, stdin=major_patch) - # Now apply the patch we want - args=['patch', path] - status,output,error = self._u_invoke(args, stdin=target_patch) + status,output,error = self._u_invoke(args, stdin=patch) if os.path.exists(os.path.join(self.repo, path)) == True: contents = base.VCS._vcs_get_file_contents(self, path) @@ -182,11 +172,8 @@ class Darcs(base.VCS): contents = '' # Now restore path to it's current incarnation - args=['patch', '--reverse', path] - status,output,error = self._u_invoke(args, stdin=target_patch) args=['patch', path] - status,output,error = self._u_invoke(args, stdin=major_patch) - current_contents = base.VCS._vcs_get_file_contents(self, path) + status,output,error = self._u_invoke(args, stdin=patch) return contents def _vcs_path(self, id, revision): @@ -257,7 +244,10 @@ class Darcs(base.VCS): revision = match.groups()[0] return revision - def _vcs_revision_id(self, index): + def _revisions(self): + """ + Return a list of revisions in the repository. + """ status,output,error = self._u_invoke_client('changes', '--xml') revisions = [] xml_str = output.encode('unicode_escape').replace(r'\n', '\n') @@ -270,6 +260,10 @@ class Darcs(base.VCS): text = unescape(unicode(child.text).decode('unicode_escape').strip()) revisions.append(text) revisions.reverse() + return revisions + + def _vcs_revision_id(self, index): + revisions = self._revisions() try: if index > 0: return revisions[index-1] @@ -280,6 +274,105 @@ class Darcs(base.VCS): except IndexError: return None + def _diff(self, revision, path=None, unicode_output=True): + revisions = self._revisions() + i = revisions.index(revision) + args = ['diff', '--unified'] + if i+1 < len(revisions): + next_rev = revisions[i+1] + args.extend(['--from-patch', next_rev]) + if path != None: + args.append(path) + kwargs = {'unicode_output':unicode_output} + status,output,error = self._u_invoke_client( + *args, **kwargs) + return output + + def _parse_diff(self, diff_text): + """ + Example diff text: + + Mon Jan 18 15:19:30 EST 2010 None + * Final state + diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/modified new-BEtestgQtDuD/.be/dir/bugs/modified + --- old-BEtestgQtDuD/.be/dir/bugs/modified 2010-01-18 15:19:30.000000000 -0500 + +++ new-BEtestgQtDuD/.be/dir/bugs/modified 2010-01-18 15:19:30.000000000 -0500 + @@ -1 +1 @@ + -some value to be modified + \ No newline at end of file + +a new value + \ No newline at end of file + diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/moved new-BEtestgQtDuD/.be/dir/bugs/moved + --- old-BEtestgQtDuD/.be/dir/bugs/moved 2010-01-18 15:19:30.000000000 -0500 + +++ new-BEtestgQtDuD/.be/dir/bugs/moved 1969-12-31 19:00:00.000000000 -0500 + @@ -1 +0,0 @@ + -this entry will be moved + \ No newline at end of file + diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/moved2 new-BEtestgQtDuD/.be/dir/bugs/moved2 + --- old-BEtestgQtDuD/.be/dir/bugs/moved2 1969-12-31 19:00:00.000000000 -0500 + +++ new-BEtestgQtDuD/.be/dir/bugs/moved2 2010-01-18 15:19:30.000000000 -0500 + @@ -0,0 +1 @@ + +this entry will be moved + \ No newline at end of file + diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/new new-BEtestgQtDuD/.be/dir/bugs/new + --- old-BEtestgQtDuD/.be/dir/bugs/new 1969-12-31 19:00:00.000000000 -0500 + +++ new-BEtestgQtDuD/.be/dir/bugs/new 2010-01-18 15:19:30.000000000 -0500 + @@ -0,0 +1 @@ + +this entry is new + \ No newline at end of file + diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/removed new-BEtestgQtDuD/.be/dir/bugs/removed + --- old-BEtestgQtDuD/.be/dir/bugs/removed 2010-01-18 15:19:30.000000000 -0500 + +++ new-BEtestgQtDuD/.be/dir/bugs/removed 1969-12-31 19:00:00.000000000 -0500 + @@ -1 +0,0 @@ + -this entry will be deleted + \ No newline at end of file + + """ + new = [] + modified = [] + removed = [] + lines = diff_text.splitlines() + repodir = os.path.basename(self.repo) + os.path.sep + i = 0 + while i < len(lines): + line = lines[i]; i += 1 + if not line.startswith('diff '): + continue + file_a,file_b = line.split()[-2:] + assert file_a.startswith('old-'), \ + 'missformed file_a %s' % file_a + assert file_b.startswith('new-'), \ + 'missformed file_a %s' % file_b + file = file_a[4:] + assert file_b[4:] == file, \ + 'diff file missmatch %s != %s' % (file_a, file_b) + assert file.startswith(repodir), \ + 'missformed file_a %s' % file_a + file = file[len(repodir):] + lines_added = 0 + lines_removed = 0 + line = lines[i]; i += 1 + assert line.startswith('--- old-'), \ + 'missformed "---" line %s' % line + time_a = line.split('\t')[1] + line = lines[i]; i += 1 + assert line.startswith('+++ new-'), \ + 'missformed "+++" line %s' % line + time_b = line.split('\t')[1] + zero_time = time.strftime('%Y-%m-%d %H:%M:%S.000000000 ', + time.localtime(0)) + # note that zero_time is missing the trailing timezone offset + if time_a.startswith(zero_time): + new.append(file) + elif time_b.startswith(zero_time): + removed.append(file) + else: + modified.append(file) + return (new,modified,removed) + + def _vcs_changed(self, revision): + return self._parse_diff(self._diff(revision)) + if libbe.TESTING == True: base.make_vcs_testcase_subclasses(Darcs, sys.modules[__name__]) -- cgit From bcb526c0c8c48cfdcd34765198642b06d915114e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 19 Jan 2010 08:45:04 -0500 Subject: Work around Mercurial issue618 in Arch backend. Also add some NotImplementedErrors for clearer diagnostics. --- libbe/storage/vcs/arch.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/arch.py b/libbe/storage/vcs/arch.py index f9ae32b..b7f4d36 100644 --- a/libbe/storage/vcs/arch.py +++ b/libbe/storage/vcs/arch.py @@ -27,7 +27,7 @@ import os import re import shutil import sys -import time +import time # work around http://mercurial.selenic.com/bts/issue618 import libbe import libbe.ui.util.user @@ -68,6 +68,7 @@ class Arch(base.VCS): self.versioned = True self.interspersed_vcs_files = True self.paranoid = False + self.__updated = [] # work around http://mercurial.selenic.com/bts/issue618 def _vcs_version(self): status,output,error = self._u_invoke_client('--version') @@ -288,7 +289,7 @@ class Arch(base.VCS): shutil.rmtree(arch_ids) def _vcs_update(self, path): - pass + self.__updated.append(path) # work around http://mercurial.selenic.com/bts/issue618 def _vcs_is_versioned(self, path): if '.arch-ids' in path: @@ -300,16 +301,28 @@ class Arch(base.VCS): return base.VCS._vcs_get_file_contents(self, path, revision) else: status,output,error = \ - self._invoke_client('file-find', path, revision) + self._invoke_client( + 'file-find', '--unescaped', path, revision) relpath = output.rstrip('\n') return base.VCS._vcs_get_file_contents(self, relpath) + def _vcs_path(self, id, revision): + raise NotImplementedError + def _vcs_commit(self, commitfile, allow_empty=False): if allow_empty == False: # arch applies empty commits without complaining, so check first status,output,error = self._u_invoke_client('changes',expect=(0,1)) if status == 0: - raise base.EmptyCommit() + # work around http://mercurial.selenic.com/bts/issue618 + time.sleep(1) + for path in self.__updated: + os.utime(os.path.join(self.repo, path), None) + self.__updated = [] + status,output,error = self._u_invoke_client('changes',expect=(0,1)) + if status == 0: + # end work around + raise base.EmptyCommit() summary,body = self._u_parse_commitfile(commitfile) args = ['commit', '--summary', summary] if body != None: @@ -342,6 +355,9 @@ class Arch(base.VCS): return None return '%s--%s' % (self._archive_project_name(), log) + def _vcs_changed(self, revision): + raise NotImplementedError + if libbe.TESTING == True: base.make_vcs_testcase_subclasses(Arch, sys.modules[__name__]) -- cgit From e86d95647ba1dbd451fc06d0a25407e1e39cb023 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 19 Jan 2010 08:59:18 -0500 Subject: Work around the extra output of `tla file-find` to get path. Example output: * build pristine tree for ...--patch-1 * from import revision: ...--base-0 * patching for revision: ...--patch-1 ./{arch}/++pristine-trees/...--patch-1/./.be/unlikely id --- libbe/storage/vcs/arch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/arch.py b/libbe/storage/vcs/arch.py index b7f4d36..f9b01fd 100644 --- a/libbe/storage/vcs/arch.py +++ b/libbe/storage/vcs/arch.py @@ -303,7 +303,8 @@ class Arch(base.VCS): status,output,error = \ self._invoke_client( 'file-find', '--unescaped', path, revision) - relpath = output.rstrip('\n') + relpath = output.rstrip('\n').splitlines()[-1] + print >> sys.stderr, 'getting', relpath return base.VCS._vcs_get_file_contents(self, relpath) def _vcs_path(self, id, revision): -- 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') 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') 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/arch.py | 1 - libbe/storage/vcs/base.py | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/arch.py b/libbe/storage/vcs/arch.py index f9b01fd..74ba371 100644 --- a/libbe/storage/vcs/arch.py +++ b/libbe/storage/vcs/arch.py @@ -304,7 +304,6 @@ class Arch(base.VCS): self._invoke_client( 'file-find', '--unescaped', path, revision) relpath = output.rstrip('\n').splitlines()[-1] - print >> sys.stderr, 'getting', relpath return base.VCS._vcs_get_file_contents(self, relpath) def _vcs_path(self, id, revision): 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 d91aae83516d0326a0c4d19153f18d675674cc4c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 20 Jan 2010 09:27:16 -0500 Subject: Adjust to modern mercurial version definition. hg-stable$ hg log --patch mercurial/util.py ... changeset: 7640:9626819b2e3d user: Matt Mackall date: Sat Jan 10 18:02:38 2009 -0600 summary: refactor version code diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -142,6 +142,14 @@ """Find the length in characters of a local string""" return len(s.decode(_encoding, "replace")) +def version(): + """Return version information if available.""" + try: + import __version__ + return __version__.version + except ImportError: + return 'unknown' + # used by parsedate ... hg-stable$ hg tags ... 1.2 7823:11efa41037e2 1.1.2 7497:11a4eb81fb4f ... --- libbe/storage/vcs/hg.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index 076943a..4993233 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -23,11 +23,21 @@ Mercurial (hg) backend. try: import mercurial - import mercurial.version import mercurial.dispatch import mercurial.ui except ImportError: mercurial = None + +try: + # mercurial >= 1.2 + from mercurial.util import version +except ImportError: + try: + # mercurial <= 1.1.2 + from mercurial.version import version + except ImportError: + version = None + import os import os.path import re @@ -57,9 +67,9 @@ class Hg(base.VCS): self.__updated = [] # work around http://mercurial.selenic.com/bts/issue618 def _vcs_version(self): - if mercurial == None: + if version == None: return None - return mercurial.version.get_version() + return version() def _u_invoke_client(self, *args, **kwargs): if 'cwd' not in kwargs: -- cgit From 98faed2675ef86c4c9e59de57e815526fcad57f2 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 21 Jan 2010 12:45:49 -0500 Subject: Fix version import for mercurial <= 1.1.2 --- libbe/storage/vcs/hg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index 4993233..088a141 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -34,7 +34,7 @@ try: except ImportError: try: # mercurial <= 1.1.2 - from mercurial.version import version + from mercurial.version import get_version as version except ImportError: version = None -- 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') 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') 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 e9c0a069dc1819fc3225501f362c3e9c130cb72b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 22 Jan 2010 14:14:03 -0500 Subject: Add Bzr._vcs_exists() anyway :p --- libbe/storage/vcs/bzr.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index e1cd2e5..1db50f8 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -98,6 +98,13 @@ class Bzr(base.VCS): cmd.outf = StringIO.StringIO() cmd.run(file_list=[path], file_ids_from=self.repo) + def _vcs_exists(self, path, revision=None): + manifest = self._vcs_listdir( + self.repo, revision=revision, recursive=True) + if path in manifest: + return True + return False + def _vcs_remove(self, path): # --force to also remove unversioned files. path = os.path.join(self.repo, path) @@ -131,7 +138,7 @@ class Bzr(base.VCS): if 'not present in revision' in str(e): raise base.InvalidPath(path, root=self.repo, revision=revision) raise - return cmd.outf.getvalue() + return cmd.outf.getvalue() def _vcs_path(self, id, revision): manifest = self._vcs_listdir( -- 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') 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') 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') 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/arch.py | 9 ++++++--- libbe/storage/vcs/base.py | 2 +- libbe/storage/vcs/darcs.py | 6 +++--- libbe/storage/vcs/git.py | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/arch.py b/libbe/storage/vcs/arch.py index 74ba371..bfccf59 100644 --- a/libbe/storage/vcs/arch.py +++ b/libbe/storage/vcs/arch.py @@ -72,7 +72,8 @@ class Arch(base.VCS): def _vcs_version(self): status,output,error = self._u_invoke_client('--version') - return output + version = '\n'.join(output.splitlines()[:2]) + return version def _vcs_detect(self, path): """Detect whether a directory is revision-controlled using Arch""" @@ -307,7 +308,8 @@ class Arch(base.VCS): return base.VCS._vcs_get_file_contents(self, relpath) def _vcs_path(self, id, revision): - raise NotImplementedError + raise NotImplementedError( + 'Too little Arch understanding at the moment...') def _vcs_commit(self, commitfile, allow_empty=False): if allow_empty == False: @@ -356,7 +358,8 @@ class Arch(base.VCS): return '%s--%s' % (self._archive_project_name(), log) def _vcs_changed(self, revision): - raise NotImplementedError + raise NotImplementedError( + 'Too little Arch understanding at the moment...') if libbe.TESTING == True: 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): """ diff --git a/libbe/storage/vcs/darcs.py b/libbe/storage/vcs/darcs.py index c6892b4..7c6f069 100644 --- a/libbe/storage/vcs/darcs.py +++ b/libbe/storage/vcs/darcs.py @@ -53,7 +53,7 @@ class Darcs(base.VCS): def _vcs_version(self): status,output,error = self._u_invoke_client('--version') - return output.rstrip('\n') + return output.strip() def version_cmp(self, *args): """ @@ -191,8 +191,8 @@ class Darcs(base.VCS): if path in children: return True return False - # Darcs versions <= 2.3.1 lack the --patch option for 'show files' - raise NotImplementedError + raise NotImplementedError( + 'Darcs versions <= 2.3.1 lack the --patch option for "show files"') def _vcs_listdir(self, path, revision): if self.version_cmp(2, 3, 1) == 1: diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py index 6d3aa87..e2c1b83 100644 --- a/libbe/storage/vcs/git.py +++ b/libbe/storage/vcs/git.py @@ -49,7 +49,7 @@ class Git(base.VCS): def _vcs_version(self): status,output,error = self._u_invoke_client('--version') - return output + return output.strip() def _vcs_get_user_id(self): status,output,error = \ -- cgit From 9ad4b1cadfd0adf3fd359c274856a196019e913c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 27 Jan 2010 12:47:37 -0500 Subject: Implement Arch._vcs_changed(). Fixes VersionedStorage_changed_TestCase.test_changed failure. --- libbe/storage/vcs/arch.py | 48 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/arch.py b/libbe/storage/vcs/arch.py index bfccf59..2b305ea 100644 --- a/libbe/storage/vcs/arch.py +++ b/libbe/storage/vcs/arch.py @@ -357,9 +357,53 @@ class Arch(base.VCS): return None return '%s--%s' % (self._archive_project_name(), log) + def _diff(self, revision): + status,output,error = self._u_invoke_client( + 'diff', '--summary', revision, expect=(0,1)) + return output + + def _parse_diff(self, diff_text): + """ + Example diff text: + + * local directory is at ... + * build pristine tree for ... + * from import revision: ... + * patching for revision: ... + * comparing to ... + D .be/dir/bugs/.arch-ids/moved.id + D .be/dir/bugs/.arch-ids/removed.id + D .be/dir/bugs/moved + D .be/dir/bugs/removed + A .be/dir/bugs/.arch-ids/moved2.id + A .be/dir/bugs/.arch-ids/new.id + A .be/dir/bugs/moved2 + A .be/dir/bugs/new + A {arch}/bugs-everywhere/bugs-everywhere--mainline/... + M .be/dir/bugs/modified + """ + new = [] + modified = [] + removed = [] + lines = diff_text.splitlines() + for i,line in enumerate(lines): + if line.startswith('* ') or '/.arch-ids/' in line: + continue + change,file = line.split(' ',1) + print '"%s" "%s"' % (change, file) + if file.startswith('{arch}/'): + continue + if change == 'A': + new.append(file) + elif change == 'M': + modified.append(file) + elif change == 'D': + removed.append(file) + print new, modified, removed + return (new,modified,removed) + def _vcs_changed(self, revision): - raise NotImplementedError( - 'Too little Arch understanding at the moment...') + return self._parse_diff(self._diff(revision)) if libbe.TESTING == True: -- cgit From 9b42ab6d3e2372c2c0f26a0788f8b84d1d346171 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 27 Jan 2010 16:50:34 -0500 Subject: Implement Arch._vcs_path() Fixes VersionedStorage_commit_TestCase.test_get_previous_children. Should have fixed VersionedStorage_commit_TestCase.test_get_previous_version too, but 'tla file-find' is buggy: https://bugs.launchpad.net/ubuntu/+source/tla/+bug/513472 Also: * sort children in test_get_previous_children, since we shouldn't require a particular child order * unescape filenames in Arch._diff() * remove debugging prints from Arch._parse_diff() * remove silly blank line in git.py I'd stumbled across ;). --- libbe/storage/vcs/arch.py | 36 +++++++++++++++++++++++++++++------- libbe/storage/vcs/git.py | 1 - 2 files changed, 29 insertions(+), 8 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/arch.py b/libbe/storage/vcs/arch.py index 2b305ea..38b1d02 100644 --- a/libbe/storage/vcs/arch.py +++ b/libbe/storage/vcs/arch.py @@ -24,6 +24,7 @@ GNU Arch (tla) backend. import codecs import os +import os.path import re import shutil import sys @@ -33,6 +34,7 @@ import libbe import libbe.ui.util.user import libbe.storage.util.config from libbe.util.id import uuid_gen +from libbe.util.subproc import CommandError import base if libbe.TESTING == True: @@ -301,15 +303,37 @@ class Arch(base.VCS): if revision == None: return base.VCS._vcs_get_file_contents(self, path, revision) else: + relpath = self._file_find(path, revision, relpath=True) + return base.VCS._vcs_get_file_contents(self, relpath) + + def _file_find(self, path, revision, relpath=False): + try: status,output,error = \ self._invoke_client( 'file-find', '--unescaped', path, revision) - relpath = output.rstrip('\n').splitlines()[-1] - return base.VCS._vcs_get_file_contents(self, relpath) + path = output.rstrip('\n').splitlines()[-1] + except CommandError, e: + if e.status == 2 \ + and 'illegally formed changeset index' in e.stderr: + raise NotImplementedError( +"""Outstanding tla bug, see + https://bugs.launchpad.net/ubuntu/+source/tla/+bug/513472 +""") + raise + if relpath == True: + return path + return os.path.abspath(os.path.join(self.repo, path)) def _vcs_path(self, id, revision): - raise NotImplementedError( - 'Too little Arch understanding at the moment...') + return self._u_find_id(id, revision) + + def _vcs_isdir(self, path, revision): + abspath = self._file_find(path, revision) + return os.path.isdir(abspath) + + def _vcs_listdir(self, path, revision): + abspath = self._file_find(path, revision) + return [p for p in os.listdir(abspath) if self._vcs_is_versioned(p)] def _vcs_commit(self, commitfile, allow_empty=False): if allow_empty == False: @@ -359,7 +383,7 @@ class Arch(base.VCS): def _diff(self, revision): status,output,error = self._u_invoke_client( - 'diff', '--summary', revision, expect=(0,1)) + 'diff', '--summary', '--unescaped', revision, expect=(0,1)) return output def _parse_diff(self, diff_text): @@ -390,7 +414,6 @@ class Arch(base.VCS): if line.startswith('* ') or '/.arch-ids/' in line: continue change,file = line.split(' ',1) - print '"%s" "%s"' % (change, file) if file.startswith('{arch}/'): continue if change == 'A': @@ -399,7 +422,6 @@ class Arch(base.VCS): modified.append(file) elif change == 'D': removed.append(file) - print new, modified, removed return (new,modified,removed) def _vcs_changed(self, revision): diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py index e2c1b83..c6638bc 100644 --- a/libbe/storage/vcs/git.py +++ b/libbe/storage/vcs/git.py @@ -118,7 +118,6 @@ class Git(base.VCS): status,output,error = self._u_invoke_client('show', arg) return output - def _vcs_path(self, id, revision): return self._u_find_id(id, revision) -- cgit From 76d983ec670ec7f09dace232e8553a80b2a08878 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 28 Jan 2010 11:47:59 -0500 Subject: Fix bzrlib.builtins.cmd_ls() recursion argument for pre 2.0 bzrlibs. $ python test.py libbe.storage.vcs.bzr ... ====================================================================== ERROR: Children list should be revision dependent. ---------------------------------------------------------------------- Traceback (most recent call last): File ".../libbe/storage/base.py", line 997, in test_ get_previous_children ret = sorted(self.s.children('parent', revision=revs[i])) File ".../libbe/storage/base.py", line 314, in child ren return self._children(*args, **kwargs) File ".../libbe/storage/vcs/base.py", line 811, in _ children path = self.path(id, revision, relpath=False) File ".../libbe/storage/vcs/base.py", line 716, in p ath path = self._vcs_path(id, revision) File ".../libbe/storage/vcs/bzr.py", line 145, in _v cs_path self.repo, revision=revision, recursive=True) File ".../libbe/storage/vcs/bzr.py", line 163, in _v cs_listdir cmd.run(revision=revision, path=path, recursive=recursive) File "/usr/lib/python2.5/site-packages/bzrlib/commands.py", line 800, in ignor e_pipe result = func(*args, **kwargs) TypeError: run() got an unexpected keyword argument 'recursive' ... The change is due to (in bzr.dev): revno: 4206.2.1 revision-id: ian.clatworthy@canonical.com-20090326133831-orvicmmc6w29mpfp parent: pqm@pqm.ubuntu.com-20090326063330-evutyvml3067dpsz committer: Ian Clatworthy branch nick: bzr.ls-recursive-off timestamp: Thu 2009-03-26 23:38:31 +1000 message: ls should be non-recursive by default Which occured between bzr-1.9rc1 and 2.0rc1.: bzr.dev$ bzr tags 2.0rc1 4634.9.1 ... bzr-1.9rc1 3815.3.1 bzr-2.0.1 4634.73.2 ... --- libbe/storage/vcs/bzr.py | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index 1db50f8..b617d68 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -62,6 +62,46 @@ class Bzr(base.VCS): return None return bzrlib.__version__ + def version_cmp(self, *args): + """ + Compare the installed Bazaar version V_i with another version + V_o (given in *args). Returns + 1 if V_i > V_o, + 0 if V_i == V_o, and + -1 if V_i < V_o + >>> b = Bzr(repo='.') + >>> b._vcs_version = lambda : "2.3.1 (release)" + >>> b.version_cmp(2,3,1) + 0 + >>> b.version_cmp(2,3,2) + -1 + >>> b.version_cmp(2,3,0) + 1 + >>> b.version_cmp(3) + -1 + >>> b._vcs_version = lambda : "2.0.0pre2" + >>> b._parsed_version = None + >>> b.version_cmp(3) + Traceback (most recent call last): + ... + NotImplementedError: Cannot parse "2.0.0pre2" portion of Bazaar version "2.0.0pre2" + invalid literal for int() with base 10: '0pre2' + """ + if not hasattr(self, '_parsed_version') \ + or self._parsed_version == None: + num_part = self._vcs_version().split(' ')[0] + try: + self._parsed_version = [int(i) for i in num_part.split('.')] + except ValueError, e: + raise NotImplementedError( + 'Cannot parse "%s" portion of Bazaar version "%s"\n %s' + % (num_part, self._vcs_version(), str(e))) + cmps = [cmp(a,b) for a,b in zip(self._parsed_version, args)] + for c in cmps: + if c != 0: + return c + return 0 + def _vcs_get_user_id(self): # excerpted from bzrlib.builtins.cmd_whoami.run() try: @@ -160,7 +200,11 @@ class Bzr(base.VCS): cmd = bzrlib.builtins.cmd_ls() cmd.outf = StringIO.StringIO() try: - cmd.run(revision=revision, path=path, recursive=recursive) + if self.version_cmp(2,0,0) == 1: + cmd.run(revision=revision, path=path, recursive=recursive) + else: # Pre-2.0 Bazaar + cmd.run(revision=revision, path=path, + non_recursive=not recursive) except bzrlib.errors.BzrCommandError, e: if 'not present in revision' in str(e): raise base.InvalidPath(path, root=self.repo, revision=revision) -- cgit From 9ef5ba29e9fc2804784b7f33dde80000a16f43cb Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 28 Jan 2010 12:46:18 -0500 Subject: Fix bzrlib.builtins.cmd_cat() output for pre 1.6.0 bzrlibs. Fixed in bzr.dev revno: 3341.2.1 revision-id: bialix@ukr.net-20080407074826-5lwuyv4dn1qlijg4 parent: pqm@pqm.ubuntu.com-20080407044456-s1a9orh0kssphdh9 committer: Alexander Belchenko branch nick: cmd-cat timestamp: Mon 2008-04-07 10:48:26 +0300 message: `bzr cat` no more internally used Tree.print_file(). Merged into bzr.dev's trunk revno: 3512 [merge] revision-id: pqm@pqm.ubuntu.com-20080626004245-dnw85so4xqg8r9hy parent: pqm@pqm.ubuntu.com-20080625230724-lyux37pu8nx8tq34 parent: aaron@aaronbentley.com-20080626001706-wo3w74fwgliy12s4 committer: Canonical.com Patch Queue Manager branch nick: +trunk timestamp: Thu 2008-06-26 01:42:45 +0100 message: (bialix) Deprectate (Branch|Repository).print_file, fix cmd_cat Before bzr branch 1.6 bzr.dev$ bzr tags ... bzr-1.5rc1 3418.6.3 bzr-1.6 3606.5.9 ... Fixes: python test.py -q libbe.storage.vcs.bzr ...............................FSome value:1E.. ====================================================================== ERROR: Get should be able to return the previous version. ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/wking/src/fun/be/be.wtk/libbe/storage/base.py", line 976, in test_ get_previous_version ret = self.s.get(self.id, revision=revs[i]) File "/home/wking/src/fun/be/be.wtk/libbe/storage/base.py", line 335, in get value = self._get(*args, **kwargs) File "/home/wking/src/fun/be/be.wtk/libbe/storage/vcs/base.py", line 849, in _ get raise InvalidID(id, revision) InvalidID: unlikely id in revision 1 ... --- libbe/storage/vcs/bzr.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index b617d68..03a64f8 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -172,12 +172,20 @@ class Bzr(base.VCS): revision = self._parse_revision_string(revision) cmd = bzrlib.builtins.cmd_cat() cmd.outf = StringIO.StringIO() + if self.version_cmp(1,6,0) == -1: + # old bzrlib cmd_cat uses sys.stdout not self.outf for output. + stdout = sys.stdout + sys.stdout = cmd.outf try: cmd.run(filename=path, revision=revision) except bzrlib.errors.BzrCommandError, e: if 'not present in revision' in str(e): raise base.InvalidPath(path, root=self.repo, revision=revision) raise + finally: + if self.version_cmp(2,0,0) == -1: + cmd.outf = sys.stdout + sys.stdout = stdout return cmd.outf.getvalue() def _vcs_path(self, id, revision): @@ -200,7 +208,7 @@ class Bzr(base.VCS): cmd = bzrlib.builtins.cmd_ls() cmd.outf = StringIO.StringIO() try: - if self.version_cmp(2,0,0) == 1: + if self.version_cmp(2,0,0) >= 0: cmd.run(revision=revision, path=path, recursive=recursive) else: # Pre-2.0 Bazaar cmd.run(revision=revision, path=path, -- cgit From 7e120421446f88f9bde0674f57fb1667c5f70ebd Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 28 Jan 2010 13:06:06 -0500 Subject: Work around "bzr ls --non-recursive PATH : no list" bug in old bzrlib. See: https://bugs.launchpad.net/bzr/+bug/158690 Bug affected versions: 0.90.0 (reported) 1.3.1 (my test suite hit it) Doesn't affect versions: 2.0+ (non_recursive -> recursive) But I haven't isolated the source more specifically. Working around it for everything < 2.0 should be safe, but the cutoff could be fine-tuned if someone wants to dig through the bzr.dev history... --- libbe/storage/vcs/bzr.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index 03a64f8..ce140bc 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -172,7 +172,7 @@ class Bzr(base.VCS): revision = self._parse_revision_string(revision) cmd = bzrlib.builtins.cmd_cat() cmd.outf = StringIO.StringIO() - if self.version_cmp(1,6,0) == -1: + if self.version_cmp(1,6,0) < 0: # old bzrlib cmd_cat uses sys.stdout not self.outf for output. stdout = sys.stdout sys.stdout = cmd.outf @@ -183,7 +183,7 @@ class Bzr(base.VCS): raise base.InvalidPath(path, root=self.repo, revision=revision) raise finally: - if self.version_cmp(2,0,0) == -1: + if self.version_cmp(2,0,0) < 0: cmd.outf = sys.stdout sys.stdout = stdout return cmd.outf.getvalue() @@ -210,15 +210,20 @@ class Bzr(base.VCS): try: if self.version_cmp(2,0,0) >= 0: cmd.run(revision=revision, path=path, recursive=recursive) - else: # Pre-2.0 Bazaar + else: + # Pre-2.0 Bazaar (non_recursive) + # + working around broken non_recursive+path implementation + # (https://bugs.launchpad.net/bzr/+bug/158690) cmd.run(revision=revision, path=path, - non_recursive=not recursive) + non_recursive=False) except bzrlib.errors.BzrCommandError, e: if 'not present in revision' in str(e): raise base.InvalidPath(path, root=self.repo, revision=revision) raise children = cmd.outf.getvalue().rstrip('\n').splitlines() children = [self._u_rel_path(c, path) for c in children] + if self.version_cmp(2,0,0) < 0 and recursive == False: + children = [c for c in children if os.path.sep not in c] return children def _vcs_commit(self, commitfile, allow_empty=False): -- cgit From cf930f941de308f5d46ce89aa7f6358ba3c453d6 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 1 Feb 2010 12:02:11 -0500 Subject: Fixed Chris' "zero name length for Mercurial w/o ~/.hgrc" bug. From: Chris Ball Subject: Test suite status Date: Mon, 01 Feb 2010 11:27:53 -0500 Message-id: ... I hit the "assert len(name) > 0" in libbe/ui/util/libbe.py, coming from hg.py when running with no ~/.hgrc. Fixed by the following patch: === modified file 'libbe/storage/vcs/hg.py' --- libbe/storage/vcs/hg.py 2010-01-21 17:45:49 +0000 +++ libbe/storage/vcs/hg.py 2010-02-01 16:17:03 +0000 @@ -87,7 +87,14 @@ return tmp_stdout.getvalue().rstrip('\n') def _vcs_get_user_id(self): - return self._u_invoke_client('showconfig', 'ui.username') + output = self._u_invoke_client('showconfig', 'ui.username') + if output != "": + return output.rstrip('\n') + else: + # guess missing info + name = libbe.ui.util.user.get_fallback_username() + email = libbe.ui.util.user.get_fallback_email() + return libbe.ui.util.user.create_user_id(name, email) def _vcs_detect(self, path): """Detect whether a directory is revision-controlled using Mercurial""" --- libbe/storage/vcs/hg.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index 088a141..97fc470 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -87,7 +87,11 @@ class Hg(base.VCS): return tmp_stdout.getvalue().rstrip('\n') def _vcs_get_user_id(self): - return self._u_invoke_client('showconfig', 'ui.username') + output = self._u_invoke_client( + 'showconfig', 'ui.username').rstrip('\n') + if output != '': + return output + return None def _vcs_detect(self, path): """Detect whether a directory is revision-controlled using Mercurial""" -- cgit From 3e16a0ab627a095605f14a5164c2d8e14a3bcaa9 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 1 Feb 2010 12:31:53 -0500 Subject: Made Bzr/Darcs.version_cmp() more robust in response to Chris' email. From: Chris Ball Subject: Test suite status ... I ran the wking@drexel.edu-20100130162439-pmh5tg6kuq92x3l5 testsuite on Fedora 13/Rawhide. Had to downgrade Mercurial (bzr-hg doesn't support 1.4.2 yet) and bzr (my Fedora package contained a "b4" in the version string, which breaks libbe/storage/vcs/bzr.py:version_cmp()). ... --- libbe/storage/vcs/bzr.py | 24 +++++++++++++++--------- libbe/storage/vcs/darcs.py | 24 +++++++++++++++--------- 2 files changed, 30 insertions(+), 18 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index ce140bc..01d9948 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -37,6 +37,7 @@ import re import shutil import StringIO import sys +import types import libbe import base @@ -82,22 +83,27 @@ class Bzr(base.VCS): >>> b._vcs_version = lambda : "2.0.0pre2" >>> b._parsed_version = None >>> b.version_cmp(3) + -1 + >>> b.version_cmp(2,0,1) Traceback (most recent call last): ... - NotImplementedError: Cannot parse "2.0.0pre2" portion of Bazaar version "2.0.0pre2" - invalid literal for int() with base 10: '0pre2' + NotImplementedError: Cannot parse non-integer portion "0pre2" of Bzr version "2.0.0pre2" """ if not hasattr(self, '_parsed_version') \ or self._parsed_version == None: num_part = self._vcs_version().split(' ')[0] - try: - self._parsed_version = [int(i) for i in num_part.split('.')] - except ValueError, e: + self._parsed_version = [] + for num in num_part.split('.'): + try: + self._parsed_version.append(int(num)) + except ValueError, e: + self._parsed_version.append(num) + for current,other in zip(self._parsed_version, args): + if type(current) != types.IntType: raise NotImplementedError( - 'Cannot parse "%s" portion of Bazaar version "%s"\n %s' - % (num_part, self._vcs_version(), str(e))) - cmps = [cmp(a,b) for a,b in zip(self._parsed_version, args)] - for c in cmps: + 'Cannot parse non-integer portion "%s" of Bzr version "%s"' + % (current, self._vcs_version())) + c = cmp(current,other) if c != 0: return c return 0 diff --git a/libbe/storage/vcs/darcs.py b/libbe/storage/vcs/darcs.py index 7c6f069..fd8b7d5 100644 --- a/libbe/storage/vcs/darcs.py +++ b/libbe/storage/vcs/darcs.py @@ -25,6 +25,7 @@ import re import shutil import sys import time # work around http://mercurial.selenic.com/bts/issue618 +import types try: # import core module, Python >= 2.5 from xml.etree import ElementTree except ImportError: # look for non-core module @@ -75,22 +76,27 @@ class Darcs(base.VCS): >>> d._vcs_version = lambda : "2.0.0pre2" >>> d._parsed_version = None >>> d.version_cmp(3) + -1 + >>> d.version_cmp(2,0,1) Traceback (most recent call last): ... - NotImplementedError: Cannot parse "2.0.0pre2" portion of Darcs version "2.0.0pre2" - invalid literal for int() with base 10: '0pre2' + NotImplementedError: Cannot parse non-integer portion "0pre2" of Darcs version "2.0.0pre2" """ if not hasattr(self, '_parsed_version') \ or self._parsed_version == None: num_part = self._vcs_version().split(' ')[0] - try: - self._parsed_version = [int(i) for i in num_part.split('.')] - except ValueError, e: + self._parsed_version = [] + for num in num_part.split('.'): + try: + self._parsed_version.append(int(num)) + except ValueError, e: + self._parsed_version.append(num) + for current,other in zip(self._parsed_version, args): + if type(current) != types.IntType: raise NotImplementedError( - 'Cannot parse "%s" portion of Darcs version "%s"\n %s' - % (num_part, self._vcs_version(), str(e))) - cmps = [cmp(a,b) for a,b in zip(self._parsed_version, args)] - for c in cmps: + 'Cannot parse non-integer portion "%s" of Darcs version "%s"' + % (current, self._vcs_version())) + c = cmp(current,other) if c != 0: return c return 0 -- 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/__init__.py | 17 +++ libbe/storage/vcs/arch.py | 40 ++++--- libbe/storage/vcs/base.py | 266 +++++++++++++++--------------------------- libbe/storage/vcs/bzr.py | 118 ++++++++++--------- libbe/storage/vcs/darcs.py | 104 +++++++++-------- libbe/storage/vcs/git.py | 108 +++++++++-------- libbe/storage/vcs/hg.py | 86 +++++++------- 7 files changed, 361 insertions(+), 378 deletions(-) (limited to 'libbe/storage/vcs') diff --git a/libbe/storage/vcs/__init__.py b/libbe/storage/vcs/__init__.py index 777c723..552d43e 100644 --- a/libbe/storage/vcs/__init__.py +++ b/libbe/storage/vcs/__init__.py @@ -14,6 +14,23 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Define the Version Controlled System (VCS)-based +:class:`~libbe.storage.base.Storage` and +:class:`~libbe.storage.base.VersionedStorage` implementations. + +There is a base class (:class:`~libbe.storage.vcs.VCS`) translating +Storage language to VCS language, and a number of `VCS` implementations: + +* :class:`~libbe.storage.vcs.arch.Arch` +* :class:`~libbe.storage.vcs.bzr.Bzr` +* :class:`~libbe.storage.vcs.darcs.Darcs` +* :class:`~libbe.storage.vcs.git.Git` +* :class:`~libbe.storage.vcs.hg.Hg` + +The base `VCS` class also serves as a filesystem Storage backend (not +versioning) in the event that a user has no VCS installed. +""" + import base set_preferred_vcs = base.set_preferred_vcs diff --git a/libbe/storage/vcs/arch.py b/libbe/storage/vcs/arch.py index 38b1d02..3a50414 100644 --- a/libbe/storage/vcs/arch.py +++ b/libbe/storage/vcs/arch.py @@ -18,8 +18,9 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -""" -GNU Arch (tla) backend. +"""GNU Arch_ (tla) backend. + +.. _Arch: http://www.gnu.org/software/gnu-arch/ """ import codecs @@ -56,6 +57,8 @@ def new(): return Arch() class Arch(base.VCS): + """:class:`base.VCS` implementation for GNU Arch. + """ name = 'arch' client = client _archive_name = None @@ -90,10 +93,10 @@ class Arch(base.VCS): self._add_project_code(path) def _create_archive(self, path): - """ - Create a temporary Arch archive in the directory PATH. This - archive will be removed by - destroy->_vcs_destroy->_remove_archive + """Create a temporary Arch archive in the directory PATH. This + archive will be removed by:: + + destroy->_vcs_destroy->_remove_archive """ # http://regexps.srparish.net/tutorial-tla/new-archive.html#Creating_a_New_Archive assert self._archive_name == None @@ -109,8 +112,7 @@ class Arch(base.VCS): self._archive_dir, cwd=path) def _invoke_client(self, *args, **kwargs): - """ - Invoke the client on our archive. + """Invoke the client on our archive. """ assert self._archive_name != None command = args[0] @@ -164,16 +166,20 @@ class Arch(base.VCS): return '%s/%s' % (self._archive_name, self._project_name) def _adjust_naming_conventions(self, path): - """ - By default, Arch restricts source code filenames to - ^[_=a-zA-Z0-9].*$ - See - http://regexps.srparish.net/tutorial-tla/naming-conventions.html - Since our bug directory '.be' doesn't satisfy these conventions, - we need to adjust them. + """Adjust `Arch naming conventions`_ so ``.be`` is considered source + code. + + By default, Arch restricts source code filenames to:: + + ^[_=a-zA-Z0-9].*$ - The conventions are specified in - project-root/{arch}/=tagging-method + Since our bug directory ``.be`` doesn't satisfy these conventions, + we need to adjust them. The conventions are specified in:: + + project-root/{arch}/=tagging-method + + .. _Arch naming conventions: + http://regexps.srparish.net/tutorial-tla/naming-conventions.html """ tagpath = os.path.join(path, '{arch}', '=tagging-method') lines_out = [] 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') diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py index 01d9948..5a62968 100644 --- a/libbe/storage/vcs/bzr.py +++ b/libbe/storage/vcs/bzr.py @@ -18,8 +18,9 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -""" -Bazaar (bzr) backend. +"""Bazaar_ (bzr) backend. + +.. _Bazaar: http://bazaar.canonical.com/ """ try: @@ -51,6 +52,8 @@ def new(): return Bzr() class Bzr(base.VCS): + """:class:`base.VCS` implementation for Bazaar. + """ name = 'bzr' client = None # bzrlib module @@ -64,12 +67,18 @@ class Bzr(base.VCS): return bzrlib.__version__ def version_cmp(self, *args): - """ - Compare the installed Bazaar version V_i with another version - V_o (given in *args). Returns - 1 if V_i > V_o, - 0 if V_i == V_o, and - -1 if V_i < V_o + """Compare the installed Bazaar version `V_i` with another version + `V_o` (given in `*args`). Returns + + === =============== + 1 if `V_i > V_o` + 0 if `V_i == V_o` + -1 if `V_i < V_o` + === =============== + + Examples + -------- + >>> b = Bzr(repo='.') >>> b._vcs_version = lambda : "2.3.1 (release)" >>> b.version_cmp(2,3,1) @@ -275,51 +284,54 @@ class Bzr(base.VCS): return cmd.outf.getvalue() def _parse_diff(self, diff_text): - """ - Example diff text: - - === modified file 'dir/changed' - --- dir/changed 2010-01-16 01:54:53 +0000 - +++ dir/changed 2010-01-16 01:54:54 +0000 - @@ -1,3 +1,3 @@ - hi - -there - +everyone and - joe - - === removed file 'dir/deleted' - --- dir/deleted 2010-01-16 01:54:53 +0000 - +++ dir/deleted 1970-01-01 00:00:00 +0000 - @@ -1,3 +0,0 @@ - -in - -the - -beginning - - === removed file 'dir/moved' - --- dir/moved 2010-01-16 01:54:53 +0000 - +++ dir/moved 1970-01-01 00:00:00 +0000 - @@ -1,4 +0,0 @@ - -the - -ants - -go - -marching - - === added file 'dir/moved2' - --- dir/moved2 1970-01-01 00:00:00 +0000 - +++ dir/moved2 2010-01-16 01:54:34 +0000 - @@ -0,0 +1,4 @@ - +the - +ants - +go - +marching - - === added file 'dir/new' - --- dir/new 1970-01-01 00:00:00 +0000 - +++ dir/new 2010-01-16 01:54:54 +0000 - @@ -0,0 +1,2 @@ - +hello - +world - + """_parse_diff(diff_text) -> (new,modified,removed) + + `new`, `modified`, and `removed` are lists of files. + + Example diff text:: + + === modified file 'dir/changed' + --- dir/changed 2010-01-16 01:54:53 +0000 + +++ dir/changed 2010-01-16 01:54:54 +0000 + @@ -1,3 +1,3 @@ + hi + -there + +everyone and + joe + + === removed file 'dir/deleted' + --- dir/deleted 2010-01-16 01:54:53 +0000 + +++ dir/deleted 1970-01-01 00:00:00 +0000 + @@ -1,3 +0,0 @@ + -in + -the + -beginning + + === removed file 'dir/moved' + --- dir/moved 2010-01-16 01:54:53 +0000 + +++ dir/moved 1970-01-01 00:00:00 +0000 + @@ -1,4 +0,0 @@ + -the + -ants + -go + -marching + + === added file 'dir/moved2' + --- dir/moved2 1970-01-01 00:00:00 +0000 + +++ dir/moved2 2010-01-16 01:54:34 +0000 + @@ -0,0 +1,4 @@ + +the + +ants + +go + +marching + + === added file 'dir/new' + --- dir/new 1970-01-01 00:00:00 +0000 + +++ dir/new 2010-01-16 01:54:54 +0000 + @@ -0,0 +1,2 @@ + +hello + +world + """ new = [] modified = [] diff --git a/libbe/storage/vcs/darcs.py b/libbe/storage/vcs/darcs.py index fd8b7d5..4a21888 100644 --- a/libbe/storage/vcs/darcs.py +++ b/libbe/storage/vcs/darcs.py @@ -15,8 +15,9 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -""" -Darcs backend. +"""Darcs_ backend. + +.. _Darcs: http://darcs.net/ """ import codecs @@ -44,6 +45,8 @@ def new(): return Darcs() class Darcs(base.VCS): + """:class:`base.VCS` implementation for Darcs. + """ name='darcs' client='darcs' @@ -57,12 +60,18 @@ class Darcs(base.VCS): return output.strip() def version_cmp(self, *args): - """ - Compare the installed darcs version V_i with another version - V_o (given in *args). Returns - 1 if V_i > V_o, - 0 if V_i == V_o, and - -1 if V_i < V_o + """Compare the installed Darcs version `V_i` with another version + `V_o` (given in `*args`). Returns + + === =============== + 1 if `V_i > V_o` + 0 if `V_i == V_o` + -1 if `V_i < V_o` + === =============== + + Examples + -------- + >>> d = Darcs(repo='.') >>> d._vcs_version = lambda : "2.3.1 (release)" >>> d.version_cmp(2,3,1) @@ -295,44 +304,47 @@ class Darcs(base.VCS): return output def _parse_diff(self, diff_text): - """ - Example diff text: - - Mon Jan 18 15:19:30 EST 2010 None - * Final state - diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/modified new-BEtestgQtDuD/.be/dir/bugs/modified - --- old-BEtestgQtDuD/.be/dir/bugs/modified 2010-01-18 15:19:30.000000000 -0500 - +++ new-BEtestgQtDuD/.be/dir/bugs/modified 2010-01-18 15:19:30.000000000 -0500 - @@ -1 +1 @@ - -some value to be modified - \ No newline at end of file - +a new value - \ No newline at end of file - diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/moved new-BEtestgQtDuD/.be/dir/bugs/moved - --- old-BEtestgQtDuD/.be/dir/bugs/moved 2010-01-18 15:19:30.000000000 -0500 - +++ new-BEtestgQtDuD/.be/dir/bugs/moved 1969-12-31 19:00:00.000000000 -0500 - @@ -1 +0,0 @@ - -this entry will be moved - \ No newline at end of file - diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/moved2 new-BEtestgQtDuD/.be/dir/bugs/moved2 - --- old-BEtestgQtDuD/.be/dir/bugs/moved2 1969-12-31 19:00:00.000000000 -0500 - +++ new-BEtestgQtDuD/.be/dir/bugs/moved2 2010-01-18 15:19:30.000000000 -0500 - @@ -0,0 +1 @@ - +this entry will be moved - \ No newline at end of file - diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/new new-BEtestgQtDuD/.be/dir/bugs/new - --- old-BEtestgQtDuD/.be/dir/bugs/new 1969-12-31 19:00:00.000000000 -0500 - +++ new-BEtestgQtDuD/.be/dir/bugs/new 2010-01-18 15:19:30.000000000 -0500 - @@ -0,0 +1 @@ - +this entry is new - \ No newline at end of file - diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/removed new-BEtestgQtDuD/.be/dir/bugs/removed - --- old-BEtestgQtDuD/.be/dir/bugs/removed 2010-01-18 15:19:30.000000000 -0500 - +++ new-BEtestgQtDuD/.be/dir/bugs/removed 1969-12-31 19:00:00.000000000 -0500 - @@ -1 +0,0 @@ - -this entry will be deleted - \ No newline at end of file - + """_parse_diff(diff_text) -> (new,modified,removed) + + `new`, `modified`, and `removed` are lists of files. + + Example diff text:: + + Mon Jan 18 15:19:30 EST 2010 None + * Final state + diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/modified new-BEtestgQtDuD/.be/dir/bugs/modified + --- old-BEtestgQtDuD/.be/dir/bugs/modified 2010-01-18 15:19:30.000000000 -0500 + +++ new-BEtestgQtDuD/.be/dir/bugs/modified 2010-01-18 15:19:30.000000000 -0500 + @@ -1 +1 @@ + -some value to be modified + \ No newline at end of file + +a new value + \ No newline at end of file + diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/moved new-BEtestgQtDuD/.be/dir/bugs/moved + --- old-BEtestgQtDuD/.be/dir/bugs/moved 2010-01-18 15:19:30.000000000 -0500 + +++ new-BEtestgQtDuD/.be/dir/bugs/moved 1969-12-31 19:00:00.000000000 -0500 + @@ -1 +0,0 @@ + -this entry will be moved + \ No newline at end of file + diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/moved2 new-BEtestgQtDuD/.be/dir/bugs/moved2 + --- old-BEtestgQtDuD/.be/dir/bugs/moved2 1969-12-31 19:00:00.000000000 -0500 + +++ new-BEtestgQtDuD/.be/dir/bugs/moved2 2010-01-18 15:19:30.000000000 -0500 + @@ -0,0 +1 @@ + +this entry will be moved + \ No newline at end of file + diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/new new-BEtestgQtDuD/.be/dir/bugs/new + --- old-BEtestgQtDuD/.be/dir/bugs/new 1969-12-31 19:00:00.000000000 -0500 + +++ new-BEtestgQtDuD/.be/dir/bugs/new 2010-01-18 15:19:30.000000000 -0500 + @@ -0,0 +1 @@ + +this entry is new + \ No newline at end of file + diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/removed new-BEtestgQtDuD/.be/dir/bugs/removed + --- old-BEtestgQtDuD/.be/dir/bugs/removed 2010-01-18 15:19:30.000000000 -0500 + +++ new-BEtestgQtDuD/.be/dir/bugs/removed 1969-12-31 19:00:00.000000000 -0500 + @@ -1 +0,0 @@ + -this entry will be deleted + \ No newline at end of file + """ new = [] modified = [] diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py index c6638bc..4df9bc8 100644 --- a/libbe/storage/vcs/git.py +++ b/libbe/storage/vcs/git.py @@ -17,8 +17,9 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -""" -Git backend. +"""Git_ backend. + +.. _Git: http://git-scm.com/ """ import os @@ -40,6 +41,8 @@ def new(): return Git() class Git(base.VCS): + """:class:`base.VCS` implementation for Git. + """ name='git' client='git' @@ -179,55 +182,58 @@ class Git(base.VCS): return output def _parse_diff(self, diff_text): - """ - Example diff text: - - diff --git a/dir/changed b/dir/changed - index 6c3ea8c..2f2f7c7 100644 - --- a/dir/changed - +++ b/dir/changed - @@ -1,3 +1,3 @@ - hi - -there - +everyone and - joe - diff --git a/dir/deleted b/dir/deleted - deleted file mode 100644 - index 225ec04..0000000 - --- a/dir/deleted - +++ /dev/null - @@ -1,3 +0,0 @@ - -in - -the - -beginning - diff --git a/dir/moved b/dir/moved - deleted file mode 100644 - index 5ef102f..0000000 - --- a/dir/moved - +++ /dev/null - @@ -1,4 +0,0 @@ - -the - -ants - -go - -marching - diff --git a/dir/moved2 b/dir/moved2 - new file mode 100644 - index 0000000..5ef102f - --- /dev/null - +++ b/dir/moved2 - @@ -0,0 +1,4 @@ - +the - +ants - +go - +marching - diff --git a/dir/new b/dir/new - new file mode 100644 - index 0000000..94954ab - --- /dev/null - +++ b/dir/new - @@ -0,0 +1,2 @@ - +hello - +world + """_parse_diff(diff_text) -> (new,modified,removed) + + `new`, `modified`, and `removed` are lists of files. + + Example diff text:: + + diff --git a/dir/changed b/dir/changed + index 6c3ea8c..2f2f7c7 100644 + --- a/dir/changed + +++ b/dir/changed + @@ -1,3 +1,3 @@ + hi + -there + +everyone and + joe + diff --git a/dir/deleted b/dir/deleted + deleted file mode 100644 + index 225ec04..0000000 + --- a/dir/deleted + +++ /dev/null + @@ -1,3 +0,0 @@ + -in + -the + -beginning + diff --git a/dir/moved b/dir/moved + deleted file mode 100644 + index 5ef102f..0000000 + --- a/dir/moved + +++ /dev/null + @@ -1,4 +0,0 @@ + -the + -ants + -go + -marching + diff --git a/dir/moved2 b/dir/moved2 + new file mode 100644 + index 0000000..5ef102f + --- /dev/null + +++ b/dir/moved2 + @@ -0,0 +1,4 @@ + +the + +ants + +go + +marching + diff --git a/dir/new b/dir/new + new file mode 100644 + index 0000000..94954ab + --- /dev/null + +++ b/dir/new + @@ -0,0 +1,2 @@ + +hello + +world """ new = [] modified = [] diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py index 97fc470..9378336 100644 --- a/libbe/storage/vcs/hg.py +++ b/libbe/storage/vcs/hg.py @@ -17,8 +17,9 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -""" -Mercurial (hg) backend. +"""Mercurial_ (hg) backend. + +.. _Mercurial: http://mercurial.selenic.com/ """ try: @@ -58,6 +59,8 @@ def new(): return Hg() class Hg(base.VCS): + """:class:`base.VCS` implementation for Mercurial. + """ name='hg' client=None # mercurial module @@ -177,45 +180,48 @@ class Hg(base.VCS): 'diff', '-r', revision, '--git') def _parse_diff(self, diff_text): - """ - Example diff text: + """_parse_diff(diff_text) -> (new,modified,removed) + + `new`, `modified`, and `removed` are lists of files. + + Example diff text:: - diff --git a/.be/dir/bugs/modified b/.be/dir/bugs/modified - --- a/.be/dir/bugs/modified - +++ b/.be/dir/bugs/modified - @@ -1,1 +1,1 @@ some value to be modified - -some value to be modified - \ No newline at end of file - +a new value - \ No newline at end of file - diff --git a/.be/dir/bugs/moved b/.be/dir/bugs/moved - deleted file mode 100644 - --- a/.be/dir/bugs/moved - +++ /dev/null - @@ -1,1 +0,0 @@ - -this entry will be moved - \ No newline at end of file - diff --git a/.be/dir/bugs/moved2 b/.be/dir/bugs/moved2 - new file mode 100644 - --- /dev/null - +++ b/.be/dir/bugs/moved2 - @@ -0,0 +1,1 @@ - +this entry will be moved - \ No newline at end of file - diff --git a/.be/dir/bugs/new b/.be/dir/bugs/new - new file mode 100644 - --- /dev/null - +++ b/.be/dir/bugs/new - @@ -0,0 +1,1 @@ - +this entry is new - \ No newline at end of file - diff --git a/.be/dir/bugs/removed b/.be/dir/bugs/removed - deleted file mode 100644 - --- a/.be/dir/bugs/removed - +++ /dev/null - @@ -1,1 +0,0 @@ - -this entry will be deleted - \ No newline at end of file + diff --git a/.be/dir/bugs/modified b/.be/dir/bugs/modified + --- a/.be/dir/bugs/modified + +++ b/.be/dir/bugs/modified + @@ -1,1 +1,1 @@ some value to be modified + -some value to be modified + \ No newline at end of file + +a new value + \ No newline at end of file + diff --git a/.be/dir/bugs/moved b/.be/dir/bugs/moved + deleted file mode 100644 + --- a/.be/dir/bugs/moved + +++ /dev/null + @@ -1,1 +0,0 @@ + -this entry will be moved + \ No newline at end of file + diff --git a/.be/dir/bugs/moved2 b/.be/dir/bugs/moved2 + new file mode 100644 + --- /dev/null + +++ b/.be/dir/bugs/moved2 + @@ -0,0 +1,1 @@ + +this entry will be moved + \ No newline at end of file + diff --git a/.be/dir/bugs/new b/.be/dir/bugs/new + new file mode 100644 + --- /dev/null + +++ b/.be/dir/bugs/new + @@ -0,0 +1,1 @@ + +this entry is new + \ No newline at end of file + diff --git a/.be/dir/bugs/removed b/.be/dir/bugs/removed + deleted file mode 100644 + --- a/.be/dir/bugs/removed + +++ /dev/null + @@ -1,1 +0,0 @@ + -this entry will be deleted + \ No newline at end of file """ new = [] modified = [] -- cgit