--- /dev/null
+""" Upgrade the bugs to the latest format """
+import os.path
+import errno
+from libbe import bugdir, rcs
+
+def execute(args):
+ root = bugdir.tree_root(".", old_version=True)
+ for uuid in root.list_uuids():
+ old_bug = OldBug(root.bugs_path, uuid)
+
+ new_bug = bugdir.Bug(root.bugs_path, None)
+ new_bug.uuid = old_bug.uuid
+ new_bug.summary = old_bug.summary
+ new_bug.creator = old_bug.creator
+ new_bug.target = old_bug.target
+ new_bug.status = old_bug.status
+ new_bug.severity = old_bug.severity
+
+ new_bug.save()
+ for uuid in root.list_uuids():
+ old_bug = OldBug(root.bugs_path, uuid)
+ old_bug.delete()
+
+ bugdir.set_version(root.dir)
+
+def file_property(name, valid=None):
+ def getter(self):
+ value = self._get_value(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:
+ raise InvalidValue(name, value)
+ return self._set_value(name, value)
+ return property(getter, setter)
+
+
+class OldBug(object):
+ def __init__(self, path, uuid):
+ self.path = os.path.join(path, uuid)
+ self.uuid = uuid
+
+ def get_path(self, file):
+ return os.path.join(self.path, file)
+
+ summary = file_property("summary")
+ creator = file_property("creator")
+ target = file_property("target")
+ status = file_property("status", valid=("open", "closed"))
+ severity = file_property("severity", valid=("wishlist", "minor", "serious",
+ "critical", "fatal"))
+ def delete(self):
+ self.summary = None
+ self.creator = None
+ self.target = None
+ self.status = None
+ self.severity = None
+ self._set_value("name", None)
+
+ def _get_active(self):
+ return self.status == "open"
+
+ active = property(_get_active)
+
+ def _get_value(self, name):
+ try:
+ return file(self.get_path(name), "rb").read().rstrip("\n")
+ except IOError, e:
+ if e.errno == errno.EEXIST:
+ return None
+
+ def _set_value(self, name, value):
+ if value is None:
+ try:
+ rcs.unlink(self.get_path(name))
+ except OSError, e:
+ if e.errno != 2:
+ raise
+ else:
+ rcs.set_file_contents(self.get_path(name), "%s\n" % value)
+
+
+
self.path = path
-def tree_root(dir):
+def tree_root(dir, old_version=False):
rootdir = os.path.realpath(dir)
while (True):
versionfile=os.path.join(rootdir, ".be/version")
if os.path.exists(versionfile):
- test_version(versionfile)
+ if not old_version:
+ test_version(versionfile)
break;
elif rootdir == "/":
raise NoBugDir(dir)
rootdir=os.path.dirname(rootdir)
return BugDir(os.path.join(rootdir, ".be"))
+class BadTreeVersion(Exception):
+ def __init__(self, version):
+ Exception.__init__(self, "Unsupported tree version: %s" % version)
+ self.version = version
+
def test_version(path):
- assert (file(path, "rb").read() == "Bugs Everywhere Tree 0 0\n")
+ tree_version = file(path, "rb").read()
+ if tree_version != TREE_VERSION_STRING:
+ raise BadTreeVersion(tree_version)
+
+def set_version(path):
+ rcs.set_file_contents(os.path.join(path, "version"), TREE_VERSION_STRING)
+
+
+TREE_VERSION_STRING = "Bugs Everywhere Tree 1 0\n"
def create_bug_dir(path):
root = os.path.join(path, ".be")
rcs.mkdir(root)
rcs.mkdir(os.path.join(root, "bugs"))
- rcs.set_file_contents(os.path.join(root, "version"),
- "Bugs Everywhere Tree 0 0\n")
+ set_version(root)
return BugDir(path)
self.dir = dir
self.bugs_path = os.path.join(self.dir, "bugs")
-
def list(self):
+ for uuid in self.list_uuids():
+ yield Bug(self.bugs_path, uuid)
+
+ def list_uuids(self):
for uuid in os.listdir(self.bugs_path):
if (uuid.startswith('.')):
continue
- yield Bug(self.bugs_path, uuid)
+ yield uuid
def new_bug(self):
uuid = names.uuid()
path = os.path.join(self.bugs_path, uuid)
rcs.mkdir(path)
- return Bug(self.bugs_path, uuid)
+ bug = Bug(self.bugs_path, None)
+ bug.uuid = uuid
+ return bug
+
class InvalidValue(Exception):
def __init__(self, name, value):
msg = "Cannot assign value %s to %s" % (value, name)
self.name = name
self.value = value
-def file_property(name, valid=None):
+
+def checked_property(name, valid):
def getter(self):
- value = self._get_value(name)
- if valid is not None:
- if value not in valid:
- raise InvalidValue(name, value)
+ value = self.__getattribute__("_"+name)
+ 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:
- raise InvalidValue(name, value)
- return self._set_value(name, value)
+ if value not in valid:
+ raise InvalidValue(name, value)
+ return self.__setattr__("_"+name, value)
return property(getter, setter)
+
class Bug(object):
+ status = checked_property("status", (None, "open", "closed"))
+ severity = checked_property("severity", (None, "wishlist", "minor",
+ "serious", "critical", "fatal"))
+
def __init__(self, path, uuid):
- self.path = os.path.join(path, uuid)
+ self.path = path
self.uuid = uuid
+ if uuid is not None:
+ dict = mapfile.parse(file(self.get_path("values")))
+ else:
+ dict = {}
- def get_path(self, file):
- return os.path.join(self.path, file)
+ self.summary = dict.get("summary")
+ self.creator = dict.get("creator")
+ self.target = dict.get("target")
+ self.status = dict.get("status")
+ self.severity = dict.get("severity")
- summary = file_property("summary")
- creator = file_property("creator")
- target = file_property("target")
- status = file_property("status", valid=("open", "closed"))
- severity = file_property("severity", valid=("wishlist", "minor", "serious",
- "critical", "fatal"))
+ def get_path(self, file):
+ return os.path.join(self.path, self.uuid, file)
def _get_active(self):
return self.status == "open"
active = property(_get_active)
- def _get_value(self, name):
- try:
- return file(self.get_path(name), "rb").read().rstrip("\n")
- except IOError, e:
- if e.errno == errno.EEXIST:
- return None
-
- def _set_value(self, name, value):
- if value is None:
- rcs.unlink(self.get_path(name))
- rcs.set_file_contents(self.get_path(name), "%s\n" % value)
-
def add_attr(self, map, name):
value = self.__getattribute__(name)
if value is not None:
self.add_attr(map, "status")
self.add_attr(map, "severity")
path = self.get_path("values")
- output = file(path, "wb")
if not os.path.exists(path):
rcs.add_id(path)
- mapfile.generate2(output, map)
-
+ output = file(path, "wb")
+ mapfile.generate(output, map)
self.str += line
-def parse(f):
- """Parses a mapfile, returns a Dictionary
-
- >>> f = FileString("1:q\\n2:q\\n3:q\\n>p\\n4:q\\n5:q\\n6:q\\n")
- >>> parse(f)["q"]
- 'p'
- >>> parse("1:q\\n2:q\\n3:q\\n>r\\n4:q\\n5:q\\n6:q\\n")["q"]
- 'r'
- >>> parse("1:q:5\\n>s\\n2:q:5\\n")["q:5"]
- 's'
- >>> parse("1:q\\n>s\\n2:q\\n1:q\\n>s\\n2:q\\n")
- Traceback (most recent call last):
- File "<stdin>", line 1, in ?
- File "libbe/mapfile.py", line 41, in parse
- assert (lnum == prev_num + 1)
- AssertionError
- >>> parse("1:q\\n>s\\n2:q\\n1:l\\n>s\\n2:l\\n1:q\\n>s\\n2:q\\n")
- Traceback (most recent call last):
- AssertionError
- >>> parse("1:q\\n>s\\n>s\\n2:q\\n")
- Traceback (most recent call last):
- AssertionError
- """
- if isinstance(f, basestring):
- f = FileString(f)
- result = {}
- name = None
- prev_num = None
- for line in f:
- value = None
- # Handle values
- if line.startswith(">"):
- assert (name is not None)
- assert (not result.has_key(name))
- result[name] = line[1:].rstrip("\n")
- # Handle names
- else:
- lname = ":".join(line.split(":")[1:]).rstrip("\n")
- lnum = int(line.split(":")[0])
-
- #special-case the first execution
- if name is None:
- name = lname
-
- #ensure sequential operation
- elif lname == name:
- assert (lnum == prev_num + 1)
- #if name changes, start over at 1
- else:
- if lnum != 1:
- assert(lname != name)
- raise "%i %s %s" % (lnum, lname, name)
- assert (lnum == 1)
- name = lname
- prev_num = lnum
- return result
-
-
-def generate(f, map, context=3):
- """
- >>> f = FileString()
- >>> generate(f, {"q":"p"})
- >>> f.str
- '1:q\\n2:q\\n3:q\\n>p\\n4:q\\n5:q\\n6:q\\n'
- >>> parse(f)["q"]
- 'p'
- >>> f = FileString()
- >>> generate(f, {"a":"b", "c":"d", "e":"f"})
- >>> dict = parse(f)
- >>> dict["a"]
- 'b'
- >>> dict["c"]
- 'd'
- >>> dict["e"]
- 'f'
- """
- assert(context > 0)
- keys = map.keys()
- keys.sort()
- for key in keys:
- for i in range(context):
- f.write("%i:%s\n" % (i+1, key))
- f.write(">%s\n" % map[key])
- for i in range(context):
- f.write("%i:%s\n" % (i+context+1, key))
-
class IllegalKey(Exception):
def __init__(self, key):
Exception.__init__(self, 'Illegal key "%s"' % key)
Exception.__init__(self, 'Illegal value "%s"' % value)
self.value = value
-def generate2(f, map, context=3):
+def generate(f, map, context=3):
"""Generate a format-2 mapfile. This is a simpler format, but should merge
better, because there's no chance of confusion for appends, and lines
are unique for both key and value.
>>> f = FileString()
- >>> generate2(f, {"q":"p"})
+ >>> generate(f, {"q":"p"})
>>> f.str
'\\n\\n\\nq=p\\n\\n\\n\\n'
- >>> generate2(f, {"q=":"p"})
+ >>> generate(f, {"q=":"p"})
Traceback (most recent call last):
IllegalKey: Illegal key "q="
- >>> generate2(f, {"q\\n":"p"})
+ >>> generate(f, {"q\\n":"p"})
Traceback (most recent call last):
IllegalKey: Illegal key "q\\n"
- >>> generate2(f, {"":"p"})
+ >>> generate(f, {"":"p"})
Traceback (most recent call last):
IllegalKey: Illegal key ""
- >>> generate2(f, {">q":"p"})
+ >>> generate(f, {">q":"p"})
Traceback (most recent call last):
IllegalKey: Illegal key ">q"
- >>> generate2(f, {"q":"p\\n"})
+ >>> generate(f, {"q":"p\\n"})
Traceback (most recent call last):
IllegalValue: Illegal value "p\\n"
"""
else:
return f
-def parse2(f):
+def parse(f):
"""
Parse a format-2 mapfile.
- >>> parse2('\\n\\n\\nq=p\\n\\n\\n\\n')['q']
+ >>> parse('\\n\\n\\nq=p\\n\\n\\n\\n')['q']
'p'
- >>> parse2('\\n\\nq=\\'p\\'\\n\\n\\n\\n')['q']
+ >>> parse('\\n\\nq=\\'p\\'\\n\\n\\n\\n')['q']
"\'p\'"
>>> f = FileString()
- >>> generate2(f, {"a":"b", "c":"d", "e":"f"})
- >>> dict = parse2(f)
+ >>> generate(f, {"a":"b", "c":"d", "e":"f"})
+ >>> dict = parse(f)
>>> dict["a"]
'b'
>>> dict["c"]