From 6d4785e75e1552b3f04b1499fede6fdef2732c39 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 22 Nov 2008 16:15:16 -0500 Subject: [PATCH] Created and fixed bug 496edad5-1484-413a-bc68-4b01274a65eb. I figured out why Arch was complaining. For non-Arch users, file system access has been tweaked a bit see the BugDir doc string for details. Also, you should now set BugDir.rcs instead of .rcs_name. .rcs_name automatically tracks changes in .rcs (the reverse of the previous situation), so read from whichever you like. --- .../8d927822-eff9-42c4-9541-8b784b3f7db2/body | 29 +++++ .../values | 21 ++++ .../values | 35 ++++++ libbe/arch.py | 29 ++++- libbe/bug.py | 2 +- libbe/bugdir.py | 113 ++++++++++++++---- libbe/rcs.py | 16 ++- 7 files changed, 211 insertions(+), 34 deletions(-) create mode 100644 .be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/body create mode 100644 .be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/values create mode 100644 .be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/values diff --git a/.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/body b/.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/body new file mode 100644 index 0000000..dfcf82c --- /dev/null +++ b/.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/body @@ -0,0 +1,29 @@ +I was having problems with `python test.py bugdir` with the Arch +backend. Commits were failing with `archive not registered'. + +Adding some trace information to arch.Arch._rcs_init() and +._rcs_cleanup() (the traceback module is great :p), I found +that the problem was coming from bugdir.BugDir.guess_rcs(). + +The Arch backend deletes any auto-created archives when it is cleaned +up (RCS.__del__ -> RCS.cleanup -> Arch._rcs_cleanup). This means that +whatever instance is used to init the archive in guess_rcs() must be +kept around. I had been doing: + * installed_rcs() -> Arch-instance-A + * Arch-instance-A.init() + * store Arch-instnance-A.name as bugdir.rcs_name + * future calls to bugdir.rcs get new instance Arch-instance-B + * eventually Arch-instance-A cleaned up + * archive dissapears & tests crash + +I switched things around so .rcs is the `master attribute' and +.rcs_name follows it. Now just save whichever rcs you used to init +your archive as .rcs. + +In order to implement the fix, I had to tweak the memory/file-system +interaction a bit. Instead of saving the settings *every*time* a +setting_property changed, we now save only if the .be file exists. +This file serves as a 'file-system-bugdir-active' flag. Before it is +created (e.g., by a .save()), the BugDir lives purely in memory, and +can freely go about configuring .rcs, .rcs_name, etc until it get's +to the point where it's ready to go to disk. diff --git a/.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/values b/.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/values new file mode 100644 index 0000000..b19c065 --- /dev/null +++ b/.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/values @@ -0,0 +1,21 @@ + + + +Content-type=text/plain + + + + + + +Date=Sat, 22 Nov 2008 18:53:20 +0000 + + + + + + +From=W. Trevor King + + + diff --git a/.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/values b/.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/values new file mode 100644 index 0000000..96c0708 --- /dev/null +++ b/.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/values @@ -0,0 +1,35 @@ + + + +creator=W. Trevor King + + + + + + +severity=minor + + + + + + +status=fixed + + + + + + +summary=Early del-cleanup with Arch backend + + + + + + +time=Sat, 22 Nov 2008 18:38:32 +0000 + + + diff --git a/libbe/arch.py b/libbe/arch.py index b35a897..6415cef 100644 --- a/libbe/arch.py +++ b/libbe/arch.py @@ -21,7 +21,11 @@ import re import unittest import doctest +import traceback +import sys + import config +from beuuid import uuid_gen from rcs import RCS, RCStestCase, CommandError client = config.get_val("arch_client") @@ -54,6 +58,10 @@ class Arch(RCS): self._create_archive(path) self._create_project(path) self._add_project_code(path) + #print "RCSid:", id(self), "init", self._archive_project_name() + #print "BEGIN_TRACE" + #traceback.print_stack(file=sys.stdout) + #print "END_TRACE" def _create_archive(self, path): # Create a new archive # http://regexps.srparish.net/tutorial-tla/new-archive.html#Creating_a_New_Archive @@ -62,13 +70,13 @@ class Arch(RCS): name, email = self._u_parse_id(id) if email == None: email = "%s@example.com" % name - trailer = "%s-%s" % ("bugs-everywhere-auto", - time.strftime("%Y.%H.%M.%S")) + 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, directory=path) + self._u_invoke_client("archives") def _invoke_client(self, *args, **kwargs): """ Invoke the client on our archive. @@ -145,11 +153,16 @@ class Arch(RCS): self._invoke_client("import", "--summary", "Began versioning", directory=path) def _rcs_cleanup(self): + #print "RCSid:", id(self), "cleaned", self._archive_project_name() + #print "BEGIN_TRACE" + #traceback.print_stack(file=sys.stdout) + #print "END_TRACE" if self._tmp_project == True: self._remove_project() if self._tmp_archive == True: self._remove_archive() + def _rcs_root(self, path): if not os.path.isdir(path): dirname = os.path.dirname(path) @@ -185,6 +198,7 @@ class Arch(RCS): def _rcs_get_user_id(self): try: + self._u_invoke_client("archives") status,output,error = self._u_invoke_client('my-id') return output.rstrip('\n') except Exception, e: @@ -195,11 +209,13 @@ class Arch(RCS): def _rcs_set_user_id(self, value): self._u_invoke_client('my-id', value) def _rcs_add(self, path): + self._u_invoke_client("archives") 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) + self._u_invoke_client("archives") def _list_added(self, root): assert os.path.exists(root) assert os.access(root, os.X_OK) @@ -243,13 +259,16 @@ class Arch(RCS): def _rcs_commit(self, commitfile): summary,body = self._u_parse_commitfile(commitfile) #status,output,error = self._invoke_client("make-log") + self._u_invoke_client("tree-root") + self._u_invoke_client("tree-version") + self._u_invoke_client("archives") if body == None: status,output,error \ - = self._invoke_client("commit","--summary",summary) + = self._u_invoke_client("commit","--summary",summary) else: status,output,error \ - = self._invoke_client("commit","--summary",summary, - "--log-message",body) + = self._u_invoke_client("commit","--summary",summary, + "--log-message",body) revision = None revline = re.compile("[*] committed (.*)") match = revline.search(output) diff --git a/libbe/bug.py b/libbe/bug.py index b1e8d26..6a9a589 100644 --- a/libbe/bug.py +++ b/libbe/bug.py @@ -119,7 +119,7 @@ class Bug(object): self.status = "open" self.severity = "minor" self.assigned = None - self.time = time.time() + self.time = int(time.time()) # only save to second precision self.comment_root = comment.Comment(self, uuid=comment.INVALID_UUID) def __repr__(self): diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 6152e3f..a552b0f 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -56,14 +56,14 @@ class InvalidValue(ValueError): TREE_VERSION_STRING = "Bugs Everywhere Tree 1 0\n" -def setting_property(name, valid=None): +def setting_property(name, valid=None, doc=None): def getter(self): value = self.settings.get(name) if valid is not None: if value not in valid: raise InvalidValue(name, value) return value - + def setter(self, value): if valid is not None: if value not in valid and value is not None: @@ -72,15 +72,29 @@ def setting_property(name, valid=None): del self.settings[name] else: self.settings[name] = value - self.save() - return property(getter, setter) + self._save_settings(self.get_path("settings"), self.settings) + + return property(getter, setter, doc=doc) class BugDir (list): + """ + File-system access: + When rooted in non-bugdir directory, BugDirs live completely in + memory until the first call to .save(). This creates a '.be' + sub-directory containing configurations options, bugs, comments, + etc. Once this sub-directory has been created (possibly by + another BugDir instance) any changes to the BugDir in memory will + be flushed to the file system automatically. However, the BugDir + will only load information from the file system when it loads new + bugs/comments that it doesn't already have in memory, or when it + explicitly asked to do so (e.g. .load() or __init__(loadNow=True)). + """ def __init__(self, root=None, sink_to_existing_root=True, assert_new_BugDir=False, allow_rcs_init=False, loadNow=False, rcs=None): list.__init__(self) + self.settings = {} if root == None: root = os.getcwd() if sink_to_existing_root == True: @@ -92,13 +106,12 @@ class BugDir (list): if loadNow == True: self.load() else: - if assert_new_BugDir: + if assert_new_BugDir == True: if os.path.exists(self.get_path()): raise AlreadyInitialized, self.get_path() if rcs == None: rcs = self.guess_rcs(allow_rcs_init) - self.settings = {"rcs_name": self.rcs_name} - self.rcs_name = rcs.name + self.rcs = rcs def find_root(self, path): """ @@ -132,21 +145,24 @@ class BugDir (list): self.rcs.set_file_contents(self.get_path("version"), TREE_VERSION_STRING) rcs_name = setting_property("rcs_name", - ("None", "bzr", "git", "Arch", "hg")) + ("None", "bzr", "git", "Arch", "hg"), + doc="The name of the current RCS. Kept seperate to make saving/loading settings easy. Don't set this attribute. Set .rcs instead, and .rcs_name will be automatically adjusted.") _rcs = None def _get_rcs(self): - if self._rcs is not None: - if self.rcs_name == self._rcs.name: - return self._rcs - self._rcs = rcs_by_name(self.rcs_name) - self._rcs.root(self.root) return self._rcs - rcs = property(_get_rcs) + def _set_rcs(self, rcs): + if rcs == None: + rcs = rcs_by_name("None") + self._rcs = rcs + rcs.root(self.root) + self.rcs_name = rcs.name - target = setting_property("target") + rcs = property(_get_rcs, _set_rcs, doc="A revision control system (RCS) instance") + + target = setting_property("target", doc="The current project development target") def get_path(self, *args): my_dir = os.path.join(self.root, ".be") @@ -160,12 +176,12 @@ class BugDir (list): if not os.path.exists(deepdir): deepdir = os.path.dirname(deepdir) rcs = detect_rcs(deepdir) + install = False if rcs.name == "None": if allow_rcs_init == True: rcs = installed_rcs() rcs.init(self.root) - self.settings = {"rcs_name": rcs.name} - self.rcs_name = rcs.name + self.rcs = rcs return rcs def load(self): @@ -176,6 +192,7 @@ class BugDir (list): if not os.path.exists(self.get_path()): raise NoBugDir(self.get_path()) self.settings = self._get_settings(self.get_path("settings")) + self.rcs = rcs_by_name(self.rcs_name) self._clear_bugs() for uuid in self.list_uuids(): self._load_bug(uuid) @@ -198,10 +215,17 @@ class BugDir (list): return settings def _save_settings(self, settings_path, settings): + if not os.path.exists(self.get_path()): + # don't save settings until the bug directory has been + # initialized. this initialization happens the first time + # a bug directory is saved (BugDir.save()). If the user + # is just working with a BugDir in memory, we don't want + # to go cluttering up his file system with settings files. + return try: mapfile.map_save(self.rcs, settings_path, settings) except PathNotInRoot, e: - # Handling duplicate bugdir settings, special case + # Special case for configuring duplicate bugdir settings none_rcs = rcs_by_name("None") none_rcs.root(settings_path) mapfile.map_save(none_rcs, settings_path, settings) @@ -209,7 +233,7 @@ class BugDir (list): def duplicate_bugdir(self, revision): duplicate_path = self.rcs.duplicate_repo(revision) - # setup revision RCS as None, since the duplicate may not be versioned + # setup revision RCS as None, since the duplicate may not be initialized for versioning duplicate_settings_path = os.path.join(duplicate_path, ".be", "settings") duplicate_settings = self._get_settings(duplicate_settings_path) if "rcs_name" in duplicate_settings: @@ -228,10 +252,18 @@ class BugDir (list): self.bug_map = map def list_uuids(self): - for uuid in os.listdir(self.get_path("bugs")): - if (uuid.startswith('.')): - continue - yield uuid + uuids = [] + if os.path.exists(self.get_path()): + # list the uuids on disk + for uuid in os.listdir(self.get_path("bugs")): + if not (uuid.startswith('.')): + uuids.append(uuid) + yield uuid + # and the ones that are still just in memory + for bug in self: + if bug.uuid not in uuids: + uuids.append(bug.uuid) + yield bug.uuid def _clear_bugs(self): while len(self) > 0: @@ -341,6 +373,41 @@ class BugDirTestCase(unittest.TestCase): "path %s does not exist" % fullpath) self.assertRaises(AlreadyInitialized, BugDir, self.dir.path, assertNewBugDir=True) + def versionTest(self): + if self.rcs.versioned == False: + return + original = self.bugdir.rcs.commit("Began versioning") + bugA = self.bugdir.bug_from_uuid("a") + bugA.status = "fixed" + self.bugdir.save() + new = self.rcs.commit("Fixed bug a") + dupdir = self.bugdir.duplicate_bugdir(original) + self.failUnless(dupdir.root != self.bugdir.root, "%s, %s" % (dupdir.root, self.bugdir.root)) + bugAorig = dupdir.bug_from_uuid("a") + self.failUnless(bugA != bugAorig, "\n%s\n%s" % (bugA.string(), bugAorig.string())) + bugAorig.status = "fixed" + self.failUnless(bug.cmp_status(bugA, bugAorig)==0, "%s, %s" % (bugA.status, bugAorig.status)) + self.failUnless(bug.cmp_severity(bugA, bugAorig)==0, "%s, %s" % (bugA.severity, bugAorig.severity)) + self.failUnless(bug.cmp_assigned(bugA, bugAorig)==0, "%s, %s" % (bugA.assigned, bugAorig.assigned)) + self.failUnless(bug.cmp_time(bugA, bugAorig)==0, "%s, %s" % (bugA.time, bugAorig.time)) + self.failUnless(bug.cmp_creator(bugA, bugAorig)==0, "%s, %s" % (bugA.creator, bugAorig.creator)) + self.failUnless(bugA == bugAorig, "\n%s\n%s" % (bugA.string(), bugAorig.string())) + self.bugdir.remove_duplicate_bugdir() + self.failUnless(os.path.exists(dupdir.root)==False, str(dupdir.root)) + def testRun(self): + self.bugdir.new_bug(uuid="a", summary="Ant") + self.bugdir.new_bug(uuid="b", summary="Cockroach") + self.bugdir.new_bug(uuid="c", summary="Praying mantis") + length = len(self.bugdir) + self.failUnless(length == 3, "%d != 3 bugs" % length) + uuids = list(self.bugdir.list_uuids()) + self.failUnless(len(uuids) == 3, "%d != 3 uuids" % len(uuids)) + self.failUnless(uuids == ["a","b","c"], str(uuids)) + bugA = self.bugdir.bug_from_uuid("a") + bugAprime = self.bugdir.bug_from_shortname("a") + self.failUnless(bugA == bugAprime, "%s != %s" % (bugA, bugAprime)) + self.bugdir.save() + self.versionTest() unitsuite = unittest.TestLoader().loadTestsFromTestCase(BugDirTestCase) suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/rcs.py b/libbe/rcs.py index abd92cb..10338b9 100644 --- a/libbe/rcs.py +++ b/libbe/rcs.py @@ -24,15 +24,18 @@ import tempfile import shutil import unittest import doctest + + from utility import Dir, search_parent_directories + def _get_matching_rcs(matchfn): """Return the first module for which matchfn(RCS_instance) is true""" import arch import bzr import hg import git - for module in [git, arch, bzr, hg, git]: + for module in [arch, bzr, hg, git]: rcs = module.new() if matchfn(rcs) == True: return rcs @@ -70,7 +73,7 @@ def new(): class RCS(object): """ - Implement the 'no-rcs' interface. + This class implements a 'no-rcs' interface. Support for other RCSs can be added by subclassing this class, and overriding methods _rcs_*() with code appropriate for your RCS. @@ -340,9 +343,9 @@ class RCS(object): def _u_invoke(self, args, expect=(0,), cwd=None): if cwd == None: cwd = self.rootdir + if self.verboseInvoke == True: + print >> sys.stderr, "%s$ %s" % (cwd, " ".join(args)) try : - if self.verboseInvoke == True: - print "%s$ %s" % (cwd, " ".join(args)) if sys.platform != "win32": q = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=cwd) else: @@ -354,8 +357,11 @@ class RCS(object): raise CommandError(strerror, e.args[0]) output, error = q.communicate() status = q.wait() + if self.verboseInvoke == True: + print >> sys.stderr, "%d\n%s%s" % (status, output, error) if status not in expect: - raise CommandError(error, status) + strerror = "%s\nwhile executing %s\n%s" % (args[1], args, error) + raise CommandError(strerror, status) return status, output, error def _u_invoke_client(self, *args, **kwargs): directory = kwargs.get('directory',None) -- 2.26.2