Created and fixed bug 496edad5-1484-413a-bc68-4b01274a65eb.
authorW. Trevor King <wking@drexel.edu>
Sat, 22 Nov 2008 21:15:16 +0000 (16:15 -0500)
committerW. Trevor King <wking@drexel.edu>
Sat, 22 Nov 2008 21:15:16 +0000 (16:15 -0500)
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.

.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/body [new file with mode: 0644]
.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/values [new file with mode: 0644]
.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/values [new file with mode: 0644]
libbe/arch.py
libbe/bug.py
libbe/bugdir.py
libbe/rcs.py

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 (file)
index 0000000..dfcf82c
--- /dev/null
@@ -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 (file)
index 0000000..b19c065
--- /dev/null
@@ -0,0 +1,21 @@
+
+
+
+Content-type=text/plain
+
+
+
+
+
+
+Date=Sat, 22 Nov 2008 18:53:20 +0000
+
+
+
+
+
+
+From=W. Trevor King <wking@drexel.edu>
+
+
+
diff --git a/.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/values b/.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/values
new file mode 100644 (file)
index 0000000..96c0708
--- /dev/null
@@ -0,0 +1,35 @@
+
+
+
+creator=W. Trevor King <wking@drexel.edu>
+
+
+
+
+
+
+severity=minor
+
+
+
+
+
+
+status=fixed
+
+
+
+
+
+
+summary=Early del-cleanup with Arch backend
+
+
+
+
+
+
+time=Sat, 22 Nov 2008 18:38:32 +0000
+
+
+
index b35a897918692ca70b6d7f11c313352228bd04eb..6415cef6b708e95c42b7837261b568f117b17905 100644 (file)
@@ -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)
index b1e8d26f59231289e5a2e50620cc99c8414075a5..6a9a58930847cb7b368d5c748715f428b13e9b6c 100644 (file)
@@ -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):
index 6152e3f6ecac11aa791480374fe5e9fefc577f57..a552b0fecae3ea7c26dc2b03e8f1382807e93e83 100644 (file)
@@ -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()])
index abd92cbfb1e7d5297783541637307fbdc28da459..10338b94a3523936e319a64b255e778d7eea8e69 100644 (file)
@@ -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)