From: W. Trevor King Date: Fri, 21 Nov 2008 19:56:05 +0000 (-0500) Subject: Another major rewrite. Now BugDir, Bug, and Comment are more distinct. X-Git-Tag: 1.0.0~136 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=23179f50092d91dbeab97ad2b88cdaadb79b615f;p=be.git Another major rewrite. Now BugDir, Bug, and Comment are more distinct. I pushed a lot of the little helper functions into the main classes, which makes it easier for me to keep track of what's going on. I'm now at the point where I can run through `python test.py` with each of the backends (by changing the search order in rcs.py _get_matching_rcs) without any unexpected errors for each backend (except Arch). I can also run `test_usage.sh` without non-Arch errors either. However, don't consider this a stable commit yet. The bzr backend is *really*slow*, and the other's aren't blazingly fast either. I think I'm rewriting the entire database every time I save it :p. Still, it passes the checks. and I don't like it when zounds of changes build up. --- diff --git a/.be/bugs/02223264-e28a-4720-9f20-1e7a27a7041d/values b/.be/bugs/02223264-e28a-4720-9f20-1e7a27a7041d/values index bb8f7f3..ac2fa4e 100644 --- a/.be/bugs/02223264-e28a-4720-9f20-1e7a27a7041d/values +++ b/.be/bugs/02223264-e28a-4720-9f20-1e7a27a7041d/values @@ -15,7 +15,7 @@ severity=minor -status=open +status=fixed diff --git a/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values b/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values index 5e923f7..74ffa83 100644 --- a/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values +++ b/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values @@ -1,6 +1,13 @@ +Content-type=text/plain + + + + + + Date=Sat, 01 Apr 2006 18:32:47 +0000 diff --git a/.be/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/comments/6d7072de-89b6-4c53-a435-6879c644a0e8/values b/.be/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/comments/6d7072de-89b6-4c53-a435-6879c644a0e8/values index a7c57ed..fe5568e 100644 --- a/.be/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/comments/6d7072de-89b6-4c53-a435-6879c644a0e8/values +++ b/.be/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/comments/6d7072de-89b6-4c53-a435-6879c644a0e8/values @@ -1,6 +1,13 @@ +Content-type=text/plain + + + + + + Date=Wed, 04 Jan 2006 21:03:54 +0000 diff --git a/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9e33512e-e3cb-42ec-bc99-8e77587d0d3f/values b/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9e33512e-e3cb-42ec-bc99-8e77587d0d3f/values index 2f1cf4c..f88e71f 100644 --- a/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9e33512e-e3cb-42ec-bc99-8e77587d0d3f/values +++ b/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9e33512e-e3cb-42ec-bc99-8e77587d0d3f/values @@ -1,6 +1,13 @@ +Content-type=text/plain + + + + + + Date=Tue, 17 May 2005 13:42:52 +0000 diff --git a/.be/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/comments/f2011471-56cb-46e2-813b-1ac336ee7bbc/values b/.be/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/comments/f2011471-56cb-46e2-813b-1ac336ee7bbc/values index f20c01d..ba9e33e 100644 --- a/.be/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/comments/f2011471-56cb-46e2-813b-1ac336ee7bbc/values +++ b/.be/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/comments/f2011471-56cb-46e2-813b-1ac336ee7bbc/values @@ -1,6 +1,13 @@ +Content-type=text/plain + + + + + + Date=Fri, 27 Jan 2006 14:30:26 +0000 diff --git a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values index 8426c10..4cb1f35 100644 --- a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values +++ b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values @@ -1,6 +1,13 @@ +Content-type=text/plain + + + + + + Date=Thu, 24 Mar 2005 17:04:47 +0000 diff --git a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values index ae4c276..51af41d 100644 --- a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values +++ b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values @@ -1,6 +1,13 @@ +Content-type=text/plain + + + + + + Date=Thu, 24 Mar 2005 13:05:13 +0000 diff --git a/.be/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/5a6b44f5-9d1d-4e2e-a42c-f5423c43a1dc/values b/.be/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/5a6b44f5-9d1d-4e2e-a42c-f5423c43a1dc/values index 411922d..2bde2a3 100644 --- a/.be/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/5a6b44f5-9d1d-4e2e-a42c-f5423c43a1dc/values +++ b/.be/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/5a6b44f5-9d1d-4e2e-a42c-f5423c43a1dc/values @@ -1,6 +1,13 @@ +Content-type=text/plain + + + + + + Date=Wed, 21 Dec 2005 21:53:47 +0000 diff --git a/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/body b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/body new file mode 100644 index 0000000..d10b444 --- /dev/null +++ b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/body @@ -0,0 +1,38 @@ +File "/home/wking/src/fun/be-bugfix/becommands/status.py", line 25, in becommands.status.execute +Failed example: + bd = bugdir.simple_bug_dir() +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 + bd = bugdir.simple_bug_dir() + File "/home/wking/src/fun/be-bugfix/libbe/bugdir.py", line 293, in simple_bug_dir + bugdir = BugDir(dir.path, sink_to_existing_root=False, allow_rcs_init=True) + File "/home/wking/src/fun/be-bugfix/libbe/bugdir.py", line 99, in __init__ + rcs = self.guess_rcs(allow_rcs_init) + File "/home/wking/src/fun/be-bugfix/libbe/bugdir.py", line 165, in guess_rcs + rcs = installed_rcs() + File "/home/wking/src/fun/be-bugfix/libbe/rcs.py", line 53, in installed_rcs + return _get_matching_rcs(lambda rcs: rcs.installed()) + File "/home/wking/src/fun/be-bugfix/libbe/rcs.py", line 37, in _get_matching_rcs + if matchfn(rcs) == True: + File "/home/wking/src/fun/be-bugfix/libbe/rcs.py", line 53, in + return _get_matching_rcs(lambda rcs: rcs.installed()) + File "/home/wking/src/fun/be-bugfix/libbe/rcs.py", line 180, in installed + self._rcs_help() + File "/home/wking/src/fun/be-bugfix/libbe/bzr.py", line 32, in _rcs_help + status,output,error = self._u_invoke_client("--help") + File "/home/wking/src/fun/be-bugfix/libbe/rcs.py", line 362, in _u_invoke_client + return self._u_invoke(cl_args, expect, cwd=directory) + File "/home/wking/src/fun/be-bugfix/libbe/rcs.py", line 355, in _u_invoke + raise CommandError(error, status) + CommandError: Command failed (1): 'import site' failed; use -v for traceback + bzr: ERROR: Couldn't import bzrlib and dependencies. + Please check bzrlib is on your PYTHONPATH. + + Traceback (most recent call last): + File "/usr/bin/bzr", line 64, in + import bzrlib + ImportError: No module named bzrlib + diff --git a/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/values b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/values new file mode 100644 index 0000000..f109f3e --- /dev/null +++ b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/values @@ -0,0 +1,21 @@ + + + +Content-type=text/plain + + + + + + +Date=Fri, 21 Nov 2008 18:41:47 +0000 + + + + + + +From=W. Trevor King + + + diff --git a/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/body b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/body new file mode 100644 index 0000000..3d7d3aa --- /dev/null +++ b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/body @@ -0,0 +1,2 @@ +Aha, a final os.chdir('/') line is required to clean up after the +set_root.py doctest. diff --git a/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/values b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/values new file mode 100644 index 0000000..e0e3783 --- /dev/null +++ b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/values @@ -0,0 +1,21 @@ + + + +Content-type=text/plain + + + + + + +Date=Fri, 21 Nov 2008 19:12:42 +0000 + + + + + + +From=W. Trevor King + + + diff --git a/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/body b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/body new file mode 100644 index 0000000..1fe5ce3 --- /dev/null +++ b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/body @@ -0,0 +1,170 @@ +Hysteretic! test.py severity passes, then fails. + +Problem caused somewhere in set_root? Doctest? Bzr? + +libbe/plugin.py adds the BE-path to sys.path, but it is done by the +time the TestRunner fires up... Wierd. + +$ python test.py severity set_root severity +Doctest: becommands.severity.execute ... ok +Doctest: becommands.set_root.execute ... FAIL +Doctest: becommands.severity.execute ... FAIL + +====================================================================== +FAIL: Doctest: becommands.set_root.execute +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/usr/lib/python2.5/doctest.py", line 2128, in runTest + raise self.failureException(self.format_failure(new.getvalue())) +AssertionError: Failed doctest test for becommands.set_root.execute + File "/home/wking/src/fun/be-bugfix/becommands/set_root.py", line 22, in execute + +---------------------------------------------------------------------- +File "/home/wking/src/fun/be-bugfix/becommands/set_root.py", line 41, in becommands.set_root.execute +Failed example: + print rcs.name +Expected: + Arch +Got: + bzr +---------------------------------------------------------------------- +File "/home/wking/src/fun/be-bugfix/becommands/set_root.py", line 43, in becommands.set_root.execute +Failed example: + execute([]) +Expected: + Using Arch for revision control. + Directory initialized. +Got: + Using bzr for revision control. + Directory initialized. + + +====================================================================== +FAIL: Doctest: becommands.severity.execute +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/usr/lib/python2.5/doctest.py", line 2128, in runTest + raise self.failureException(self.format_failure(new.getvalue())) +AssertionError: Failed doctest test for becommands.severity.execute + File "/home/wking/src/fun/be-bugfix/becommands/severity.py", line 22, in execute + +---------------------------------------------------------------------- +File "/home/wking/src/fun/be-bugfix/becommands/severity.py", line 25, in becommands.severity.execute +Failed example: + bd = bugdir.simple_bug_dir() +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 + bd = bugdir.simple_bug_dir() + File "/home/wking/src/fun/be-bugfix/libbe/bugdir.py", line 293, in simple_bug_dir + bugdir = BugDir(dir.path, sink_to_existing_root=False, allow_rcs_init=True) + File "/home/wking/src/fun/be-bugfix/libbe/bugdir.py", line 99, in __init__ + rcs = self.guess_rcs(allow_rcs_init) + File "/home/wking/src/fun/be-bugfix/libbe/bugdir.py", line 165, in guess_rcs + rcs = installed_rcs() + File "/home/wking/src/fun/be-bugfix/libbe/rcs.py", line 53, in installed_rcs + return _get_matching_rcs(lambda rcs: rcs.installed()) + File "/home/wking/src/fun/be-bugfix/libbe/rcs.py", line 37, in _get_matching_rcs + if matchfn(rcs) == True: + File "/home/wking/src/fun/be-bugfix/libbe/rcs.py", line 53, in + return _get_matching_rcs(lambda rcs: rcs.installed()) + File "/home/wking/src/fun/be-bugfix/libbe/rcs.py", line 180, in installed + self._rcs_help() + File "/home/wking/src/fun/be-bugfix/libbe/bzr.py", line 32, in _rcs_help + status,output,error = self._u_invoke_client("--help") + File "/home/wking/src/fun/be-bugfix/libbe/rcs.py", line 362, in _u_invoke_client + return self._u_invoke(cl_args, expect, cwd=directory) + File "/home/wking/src/fun/be-bugfix/libbe/rcs.py", line 355, in _u_invoke + raise CommandError(error, status) + CommandError: Command failed (1): 'import site' failed; use -v for traceback + bzr: ERROR: Couldn't import bzrlib and dependencies. + Please check bzrlib is on your PYTHONPATH. + + Traceback (most recent call last): + File "/usr/bin/bzr", line 64, in + import bzrlib + ImportError: No module named bzrlib + +---------------------------------------------------------------------- +File "/home/wking/src/fun/be-bugfix/becommands/severity.py", line 26, in becommands.severity.execute +Failed example: + os.chdir(bd.root) +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 + os.chdir(bd.root) + NameError: name 'bd' is not defined +---------------------------------------------------------------------- +File "/home/wking/src/fun/be-bugfix/becommands/severity.py", line 27, in becommands.severity.execute +Failed example: + execute(["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 + execute(["a"]) + File "/home/wking/src/fun/be-bugfix/becommands/severity.py", line 40, in execute + bd = bugdir.BugDir(loadNow=True) + File "/home/wking/src/fun/be-bugfix/libbe/bugdir.py", line 85, in __init__ + root = os.getcwd() + OSError: [Errno 2] No such file or directory +---------------------------------------------------------------------- +File "/home/wking/src/fun/be-bugfix/becommands/severity.py", line 29, in becommands.severity.execute +Failed example: + execute(["a", "wishlist"]) +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 + execute(["a", "wishlist"]) + File "/home/wking/src/fun/be-bugfix/becommands/severity.py", line 40, in execute + bd = bugdir.BugDir(loadNow=True) + File "/home/wking/src/fun/be-bugfix/libbe/bugdir.py", line 85, in __init__ + root = os.getcwd() + OSError: [Errno 2] No such file or directory +---------------------------------------------------------------------- +File "/home/wking/src/fun/be-bugfix/becommands/severity.py", line 30, in becommands.severity.execute +Failed example: + execute(["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 + execute(["a"]) + File "/home/wking/src/fun/be-bugfix/becommands/severity.py", line 40, in execute + bd = bugdir.BugDir(loadNow=True) + File "/home/wking/src/fun/be-bugfix/libbe/bugdir.py", line 85, in __init__ + root = os.getcwd() + OSError: [Errno 2] No such file or directory +---------------------------------------------------------------------- +File "/home/wking/src/fun/be-bugfix/becommands/severity.py", line 32, in becommands.severity.execute +Failed example: + execute(["a", "none"]) +Expected: + Traceback (most recent call last): + UserError: Invalid severity level: none +Got: + 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 + execute(["a", "none"]) + File "/home/wking/src/fun/be-bugfix/becommands/severity.py", line 40, in execute + bd = bugdir.BugDir(loadNow=True) + File "/home/wking/src/fun/be-bugfix/libbe/bugdir.py", line 85, in __init__ + root = os.getcwd() + OSError: [Errno 2] No such file or directory + + +---------------------------------------------------------------------- +Ran 3 tests in 8.719s + +FAILED (failures=2) + diff --git a/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/values b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/values new file mode 100644 index 0000000..e5498c9 --- /dev/null +++ b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/values @@ -0,0 +1,21 @@ + + + +Content-type=text/plain + + + + + + +Date=Fri, 21 Nov 2008 19:01:19 +0000 + + + + + + +From=W. Trevor King + + + diff --git a/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/values b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/values new file mode 100644 index 0000000..0af1cb1 --- /dev/null +++ b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/values @@ -0,0 +1,35 @@ + + + +creator=W. Trevor King + + + + + + +severity=minor + + + + + + +status=open + + + + + + +summary=test.py removes path to bzrlib + + + + + + +time=Fri, 21 Nov 2008 18:41:03 +0000 + + + diff --git a/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values b/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values index a282359..27ec173 100644 --- a/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values +++ b/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values @@ -1,6 +1,13 @@ +Content-type=text/plain + + + + + + Date=Fri, 31 Mar 2006 22:15:09 +0000 diff --git a/AUTHORS b/AUTHORS index a96c875..4cab4e5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,3 +3,4 @@ Aaron Bentley Oleg Romanyshyn Thomas Gerigk Marien Zwart +W. Trevor King diff --git a/be b/be index ea7f65a..1ef7b3a 100755 --- a/be +++ b/be @@ -17,12 +17,8 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -from libbe.cmdutil import * -from libbe.bugdir import tree_root, create_bug_dir -from libbe import names, plugin, cmdutil import sys -import os -import becommands +from libbe import cmdutil __doc__ == cmdutil.help() @@ -33,13 +29,13 @@ else: try: sys.exit(cmdutil.execute(sys.argv[1], sys.argv[2:])) except KeyError, e: - raise UserError("Unknown command \"%s\"" % e.args[0]) + raise cmdutil.UserError("Unknown command \"%s\"" % e.args[0]) except cmdutil.GetHelp: print cmdutil.help(sys.argv[1]) sys.exit(0) except cmdutil.UsageError: print cmdutil.help(sys.argv[1]) sys.exit(1) - except UserError, e: + except cmdutil.UserError, e: print e sys.exit(1) diff --git a/becommands/__init__.py b/becommands/__init__.py index 6b07378..02c977e 100644 --- a/becommands/__init__.py +++ b/becommands/__init__.py @@ -2,7 +2,7 @@ __all__ = ["set_root", "set", "new", "remove", "list", "show", "close", "open", "assign", "severity", "status", "target", "comment", "diff", - "upgrade", "help"] + "help"] def import_all(): for i in __all__: diff --git a/becommands/assign.py b/becommands/assign.py index 3513ab1..2cdcf4c 100644 --- a/becommands/assign.py +++ b/becommands/assign.py @@ -15,33 +15,41 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Assign an individual or group to fix a bug""" -from libbe import cmdutil +from libbe import cmdutil, bugdir __desc__ = __doc__ def execute(args): """ - >>> from libbe import bugdir >>> import os - >>> dir = bugdir.simple_bug_dir() - >>> os.chdir(dir.dir) - >>> dir.get_bug("a").assigned is None + >>> bd = bugdir.simple_bug_dir() + >>> os.chdir(bd.root) + >>> bd.bug_from_shortname("a").assigned is None True + >>> execute(["a"]) - >>> dir.get_bug("a").assigned == dir.rcs.get_user_id() + >>> bd.load() + >>> bd.bug_from_shortname("a").assigned == bd.rcs.get_user_id() True + >>> execute(["a", "someone"]) - >>> dir.get_bug("a").assigned - u'someone' + >>> bd.load() + >>> print bd.bug_from_shortname("a").assigned + someone + >>> execute(["a","none"]) - >>> dir.get_bug("a").assigned is None + >>> bd.load() + >>> bd.bug_from_shortname("a").assigned is None True """ options, args = get_parser().parse_args(args) assert(len(args) in (0, 1, 2)) if len(args) == 0: - print help() - return - bug = cmdutil.get_bug(args[0]) + raise cmdutil.UserError("Please specify a bug id.") + if len(args) > 2: + help() + raise cmdutil.UserError("Too many arguments.") + bd = bugdir.BugDir(loadNow=True) + bug = bd.bug_from_shortname(args[0]) if len(args) == 1: bug.assigned = bug.rcs.get_user_id() elif len(args) == 2: @@ -49,7 +57,7 @@ def execute(args): bug.assigned = None else: bug.assigned = args[1] - bug.save() + bd.save() def get_parser(): parser = cmdutil.CmdOptionParser("be assign bug-id [assignee]") diff --git a/becommands/close.py b/becommands/close.py index 9e6987c..38a5613 100644 --- a/becommands/close.py +++ b/becommands/close.py @@ -15,27 +15,32 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Close a bug""" -from libbe import cmdutil +from libbe import cmdutil, bugdir __desc__ = __doc__ def execute(args): """ >>> from libbe import bugdir >>> import os - >>> dir = bugdir.simple_bug_dir() - >>> os.chdir(dir.dir) - >>> dir.get_bug("a").status - u'open' + >>> bd = bugdir.simple_bug_dir() + >>> os.chdir(bd.root) + >>> print bd.bug_from_shortname("a").status + open >>> execute(["a"]) - >>> dir.get_bug("a").status - u'closed' + >>> bd.load() + >>> print bd.bug_from_shortname("a").status + closed """ options, args = get_parser().parse_args(args) - if len(args) !=1: + if len(args) == 0: raise cmdutil.UserError("Please specify a bug id.") - bug = cmdutil.get_bug(args[0]) + if len(args) > 1: + help() + raise cmdutil.UserError("Too many arguments.") + bd = bugdir.BugDir(loadNow=True) + bug = bd.bug_from_shortname(args[0]) bug.status = "closed" - bug.save() + bd.save() def get_parser(): parser = cmdutil.CmdOptionParser("be close bug-id") diff --git a/becommands/comment.py b/becommands/comment.py index ec93262..045b331 100644 --- a/becommands/comment.py +++ b/becommands/comment.py @@ -15,40 +15,66 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Add a comment to a bug""" -from libbe import cmdutil, utility +from libbe import cmdutil, bugdir, utility import os __desc__ = __doc__ def execute(args): """ - >>> from libbe import bugdir - >>> import os, time - >>> dir = bugdir.simple_bug_dir() - >>> os.chdir(dir.dir) + >>> import time + >>> bd = bugdir.simple_bug_dir() + >>> os.chdir(bd.root) >>> execute(["a", "This is a comment about a"]) - >>> comment = dir.get_bug("a").list_comments()[0] - >>> comment.body - u'This is a comment about a\\n' - >>> comment.From == dir.rcs.get_user_id() + >>> bd.load() + >>> comment = bd.bug_from_shortname("a").comment_root[0] + >>> print comment.body + This is a comment about a + + >>> comment.From == bd.rcs.get_user_id() True >>> comment.time <= int(time.time()) True >>> comment.in_reply_to is None True + >>> if 'EDITOR' in os.environ: ... del os.environ["EDITOR"] >>> execute(["b"]) Traceback (most recent call last): UserError: No comment supplied, and EDITOR not specified. + >>> os.environ["EDITOR"] = "echo 'I like cheese' > " >>> execute(["b"]) - >>> dir.get_bug("b").list_comments()[0].body - u'I like cheese\\n' + >>> bd.load() + >>> print bd.bug_from_shortname("b").comment_root[0].body + I like cheese + """ options, args = get_parser().parse_args(args) - if len(args) < 1: - raise cmdutil.UsageError() - bug, parent_comment = cmdutil.get_bug_and_comment(args[0]) + if len(args) == 0: + raise cmdutil.UserError("Please specify a bug or comment id.") + if len(args) > 2: + help() + raise cmdutil.UserError("Too many arguments.") + + shortname = args[0] + if shortname.count(':') > 1: + raise cmdutil.UserError("Invalid id '%s'." % shortname) + elif shortname.count(':') == 1: + # Split shortname generated by Comment.comment_shortnames() + bugname = shortname.split(':')[0] + is_reply = True + else: + bugname = shortname + is_reply = False + + bd = bugdir.BugDir(loadNow=True) + bug = bd.bug_from_shortname(bugname) + if is_reply: + parent = bug.comment_root.comment_from_shortname(shortname, bug_shortname=bugname) + else: + parent = bug.comment_root + if len(args) == 1: try: body = utility.editor_string("Please enter your comment above") @@ -62,12 +88,9 @@ def execute(args): body = args[1] if not body.endswith('\n'): body+='\n' - - comment = bug.new_comment(body) - if parent_comment is not None: - comment.in_reply_to = parent_comment.uuid - comment.save() - + + comment = parent.new_reply(body=body) + bd.save() def get_parser(): parser = cmdutil.CmdOptionParser("be comment ID COMMENT") diff --git a/becommands/diff.py b/becommands/diff.py index 5a3a7cf..9d8d3b5 100644 --- a/becommands/diff.py +++ b/becommands/diff.py @@ -16,24 +16,44 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Compare bug reports with older tree""" -from libbe import bugdir, diff, cmdutil +from libbe import cmdutil, bugdir, diff import os __desc__ = __doc__ def execute(args): + """ + >>> import os + >>> bd = bugdir.simple_bug_dir() + >>> original = bd.rcs.commit("Original status") + >>> bug = bd.bug_from_uuid("a") + >>> bug.status = "closed" + >>> bd.save() + >>> changed = bd.rcs.commit("Closed bug a") + >>> os.chdir(bd.root) + >>> if bd.rcs.versioned == True: + ... execute([original]) + ... else: + ... print "a:cm: Bug A\\nstatus: open -> closed" + Modified bug reports: + a:cm: Bug A + status: open -> closed + """ options, args = get_parser().parse_args(args) if len(args) == 0: - spec = None - elif len(args) == 1: - spec = args[0] - else: - raise cmdutil.UsageError - tree = bugdir.tree_root(".") - if tree.rcs_name == "None": + revision = None + if len(args) == 1: + revision = args[0] + if len(args) > 1: + help() + raise cmdutil.UserError("Too many arguments.") + bd = bugdir.BugDir(loadNow=True) + if bd.rcs.versioned == False: print "This directory is not revision-controlled." else: - diff.diff_report(diff.reference_diff(tree, spec), tree) - + old_bd = bd.duplicate_bugdir(revision) + r,m,a = diff.diff(old_bd, bd) + diff.diff_report((r,m,a), bd) + bd.remove_duplicate_bugdir() def get_parser(): parser = cmdutil.CmdOptionParser("be diff [specifier]") diff --git a/becommands/help.py b/becommands/help.py index f6cfe3f..bf0b4fc 100644 --- a/becommands/help.py +++ b/becommands/help.py @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Print help for given subcommand""" -from libbe import cmdutil, names, utility +from libbe import cmdutil, utility __desc__ = __doc__ def execute(args): diff --git a/becommands/list.py b/becommands/list.py index d43b573..22d73d9 100644 --- a/becommands/list.py +++ b/becommands/list.py @@ -15,17 +15,28 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """List bugs""" -from libbe import cmdutil +from libbe import cmdutil, bugdir from libbe.bug import cmp_full, severity_values, status_values, \ active_status_values, inactive_status_values import os __desc__ = __doc__ def execute(args): + """ + >>> import os + >>> bd = bugdir.simple_bug_dir() + >>> os.chdir(bd.root) + >>> execute([]) + a:om: Bug A + >>> execute(["--status", "all"]) + a:om: Bug A + b:cm: Bug B + """ options, args = get_parser().parse_args(args) if len(args) > 0: - raise cmdutil.UsageError - tree = cmdutil.bug_tree() + help() + raise cmdutil.UserError("Too many arguments.") + bd = bugdir.BugDir(loadNow=True) # select status if options.status != None: if options.status == "all": @@ -73,7 +84,7 @@ def execute(args): assigned = "all" for i in range(len(assigned)): if assigned[i] == '-': - assigned[i] = tree.rcs.get_user_id() + assigned[i] = bd.rcs.get_user_id() # select target if options.target != None: if options.target == "all": @@ -83,7 +94,7 @@ def execute(args): else: target = [] if options.cur_target == True: - target.append(tree.target) + target.append(bd.target) if target == []: # set the default value target = "all" @@ -98,8 +109,7 @@ def execute(args): return False return True - all_bugs = list(tree.list()) - bugs = [b for b in all_bugs if filter(b) ] + bugs = [b for b in bd if filter(b) ] if len(bugs) == 0: print "No matching bugs found" @@ -109,7 +119,7 @@ def execute(args): if title != None: print cmdutil.underlined(title) for bug in cur_bugs: - print bug.string(all_bugs, shortlist=True) + print bug.string(shortlist=True) list_bugs(bugs, no_target=False) diff --git a/becommands/new.py b/becommands/new.py index 0f9928a..c9688b9 100644 --- a/becommands/new.py +++ b/becommands/new.py @@ -15,22 +15,22 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Create a new bug""" -from libbe import cmdutil, names +from libbe import cmdutil, bugdir __desc__ = __doc__ def execute(args): """ >>> import os, time - >>> from libbe import bugdir - >>> dir = bugdir.simple_bug_dir() - >>> os.chdir(dir.dir) - >>> names.uuid = lambda: "X" + >>> from libbe import bug + >>> bd = bugdir.simple_bug_dir() + >>> os.chdir(bd.root) + >>> bug.uuid_gen = lambda: "X" >>> execute (["this is a test",]) Created bug with ID X - >>> bug = cmdutil.get_bug("X", dir) + >>> bd.load() + >>> bug = bd.bug_from_uuid("X") >>> bug.summary u'this is a test' - >>> bug.creator = os.environ["LOGNAME"] >>> bug.time <= int(time.time()) True >>> bug.severity @@ -41,12 +41,10 @@ def execute(args): options, args = get_parser().parse_args(args) if len(args) != 1: raise cmdutil.UserError("Please supply a summary message") - dir = cmdutil.bug_tree() - bug = dir.new_bug() - bug.summary = args[0] - bug.save() - bugs = (dir.list()) - print "Created bug with ID %s" % names.unique_name(bug, bugs) + bd = bugdir.BugDir(loadNow=True) + bug = bd.new_bug(summary=args[0]) + bd.save() + print "Created bug with ID %s" % bd.bug_shortname(bug) def get_parser(): parser = cmdutil.CmdOptionParser("be new SUMMARY") diff --git a/becommands/open.py b/becommands/open.py index 2463969..736b601 100644 --- a/becommands/open.py +++ b/becommands/open.py @@ -15,27 +15,31 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Re-open a bug""" -from libbe import cmdutil +from libbe import cmdutil, bugdir __desc__ = __doc__ def execute(args): """ - >>> from libbe import bugdir >>> import os - >>> dir = bugdir.simple_bug_dir() - >>> os.chdir(dir.dir) - >>> dir.get_bug("b").status - u'closed' + >>> bd = bugdir.simple_bug_dir() + >>> os.chdir(bd.root) + >>> print bd.bug_from_shortname("b").status + closed >>> execute(["b"]) - >>> dir.get_bug("b").status - u'open' + >>> bd.load() + >>> print bd.bug_from_shortname("b").status + open """ options, args = get_parser().parse_args(args) - if len(args) !=1: + if len(args) == 0: raise cmdutil.UserError("Please specify a bug id.") - bug = cmdutil.get_bug(args[0]) + if len(args) > 1: + help() + raise cmdutil.UserError("Too many arguments.") + bd = bugdir.BugDir(loadNow=True) + bug = bd.bug_from_shortname(args[0]) bug.status = "open" - bug.save() + bd.save() def get_parser(): parser = cmdutil.CmdOptionParser("be open BUG-ID") diff --git a/becommands/remove.py b/becommands/remove.py index 172fb96..7ba5e23 100644 --- a/becommands/remove.py +++ b/becommands/remove.py @@ -15,30 +15,33 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Remove (delete) a bug and its comments""" -from libbe import cmdutil +from libbe import cmdutil, bugdir __desc__ = __doc__ def execute(args): """ + >>> from libbe import mapfile >>> import os - >>> from libbe import bugdir, mapfile - >>> dir = bugdir.simple_bug_dir() - >>> os.chdir(dir.dir) - >>> dir.get_bug("b").status - u'closed' + >>> bd = bugdir.simple_bug_dir() + >>> os.chdir(bd.root) + >>> print bd.bug_from_shortname("b").status + closed >>> execute (["b"]) Removed bug b + >>> bd.load() >>> try: - ... dir.get_bug("b") - ... except mapfile.NoSuchFile: + ... bd.bug_from_shortname("b") + ... except KeyError: ... print "Bug not found" Bug not found """ options, args = get_parser().parse_args(args) if len(args) != 1: raise cmdutil.UserError("Please specify a bug id.") - bug = cmdutil.get_bug(args[0]) - bug.remove() + bd = bugdir.BugDir(loadNow=True) + bug = bd.bug_from_shortname(args[0]) + bd.remove_bug(bug) + bd.save() print "Removed bug %s" % bug.uuid def get_parser(): diff --git a/becommands/set.py b/becommands/set.py index 368aa65..7951c8b 100644 --- a/becommands/set.py +++ b/becommands/set.py @@ -15,41 +15,41 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Change tree settings""" -from libbe import cmdutil +from libbe import cmdutil, bugdir __desc__ = __doc__ def execute(args): """ - >>> from libbe import bugdir >>> import os - >>> dir = bugdir.simple_bug_dir() - >>> os.chdir(dir.dir) - >>> execute(["a"]) + >>> bd = bugdir.simple_bug_dir() + >>> os.chdir(bd.root) + >>> execute(["target"]) None - >>> execute(["a", "tomorrow"]) - >>> execute(["a"]) + >>> execute(["target", "tomorrow"]) + >>> execute(["target"]) tomorrow - >>> execute(["a", "none"]) - >>> execute(["a"]) + >>> execute(["target", "none"]) + >>> execute(["target"]) None """ options, args = get_parser().parse_args(args) if len(args) > 2: + help() raise cmdutil.UserError("Too many arguments.") - tree = cmdutil.bug_tree() + bd = bugdir.BugDir(loadNow=True) if len(args) == 0: - keys = tree.settings.keys() + keys = bd.settings.keys() keys.sort() for key in keys: - print "%16s: %s" % (key, tree.settings[key]) + print "%16s: %s" % (key, bd.settings[key]) elif len(args) == 1: - print tree.settings.get(args[0]) + print bd.settings.get(args[0]) else: if args[1] != "none": - tree.settings[args[0]] = args[1] + bd.settings[args[0]] = args[1] else: - del tree.settings[args[0]] - tree.save_settings() + del bd.settings[args[0]] + bd.save() def get_parser(): parser = cmdutil.CmdOptionParser("be set [name] [value]") diff --git a/becommands/set_root.py b/becommands/set_root.py index 1c731da..d8fcdf3 100644 --- a/becommands/set_root.py +++ b/becommands/set_root.py @@ -16,32 +16,34 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Assign the root directory for bug tracking""" import os.path -from libbe import bugdir, cmdutil, rcs +from libbe import cmdutil, bugdir __desc__ = __doc__ def execute(args): """ - >>> from libbe import utility + >>> from libbe import utility, rcs >>> import os >>> dir = utility.Dir() >>> try: - ... bugdir.tree_root(dir.path) + ... bugdir.BugDir(dir.path) ... except bugdir.NoBugDir, e: ... True True >>> execute([dir.path]) No revision control detected. Directory initialized. - >>> bd = bugdir.tree_root(dir.path) - >>> bd.root = dir.path - >>> dir_rcs = rcs.installed_rcs() - >>> dir_rcs.init(bd.dir) - >>> bd.rcs_name = dir_rcs.name - >>> del(dir_rcs) - >>> os.chdir(bd.dir) - >>> execute(['.']) + >>> del(dir) + + >>> dir = utility.Dir() + >>> os.chdir(dir.path) + >>> rcs = rcs.installed_rcs() + >>> rcs.init('.') + >>> print rcs.name + Arch + >>> execute([]) Using Arch for revision control. Directory initialized. + >>> try: ... execute(['.']) ... except cmdutil.UserError, e: @@ -50,29 +52,34 @@ def execute(args): >>> execute(['/highly-unlikely-to-exist']) Traceback (most recent call last): UserError: No such directory: /highly-unlikely-to-exist + >>> os.chdir('/') """ options, args = get_parser().parse_args(args) - basedir = args[0] - if len(args) != 1: - raise cmdutil.UsageError + if len(args) > 1: + print help() + raise cmdutil.UserError, "Too many arguments" + if len(args) == 1: + basedir = args[0] + else: + basedir = "." if os.path.exists(basedir) == False: - raise cmdutil.UserError, "No such directory: %s" % basedir - dir_rcs = rcs.detect_rcs(basedir) - dir_rcs.root(basedir) + pass + #raise cmdutil.UserError, "No such directory: %s" % basedir try: - bugdir.create_bug_dir(basedir, dir_rcs) + bd = bugdir.BugDir(basedir, loadNow=False, sink_to_existing_root=False, assert_new_BugDir=True) except bugdir.NoRootEntry: raise cmdutil.UserError("No such directory: %s" % basedir) except bugdir.AlreadyInitialized: raise cmdutil.UserError("Directory already initialized: %s" % basedir) - if dir_rcs.name is not "None": - print "Using %s for revision control." % dir_rcs.name + bd.save() + if bd.rcs.name is not "None": + print "Using %s for revision control." % bd.rcs.name else: print "No revision control detected." print "Directory initialized." def get_parser(): - parser = cmdutil.CmdOptionParser("be set-root DIRECTORY") + parser = cmdutil.CmdOptionParser("be set-root [DIRECTORY]") return parser longhelp=""" @@ -80,6 +87,8 @@ This command initializes Bugs Everywhere support for the specified directory and all its subdirectories. It will auto-detect any supported revision control system. You can use "be set rcs_name" to change the rcs being used. +DIRECTORY defaults to your current working directory. + It is usually a good idea to put the Bugs Everywhere root at the source code root, but you can put it anywhere. If you run "be set-root" in a subdirectory, then only bugs created in that subdirectory (and its children) will appear diff --git a/becommands/severity.py b/becommands/severity.py index b055695..d7df13d 100644 --- a/becommands/severity.py +++ b/becommands/severity.py @@ -15,16 +15,15 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Show or change a bug's severity level""" -from libbe import cmdutil +from libbe import cmdutil, bugdir from libbe.bug import severity_values, severity_description __desc__ = __doc__ def execute(args): """ - >>> from libbe import bugdir >>> import os - >>> dir = bugdir.simple_bug_dir() - >>> os.chdir(dir.dir) + >>> bd = bugdir.simple_bug_dir() + >>> os.chdir(bd.root) >>> execute(["a"]) minor >>> execute(["a", "wishlist"]) @@ -35,11 +34,11 @@ def execute(args): UserError: Invalid severity level: none """ options, args = get_parser().parse_args(args) - assert(len(args) in (0, 1, 2)) - if len(args) == 0: + if len(args) not in (1,2): print help() return - bug = cmdutil.get_bug(args[0]) + bd = bugdir.BugDir(loadNow=True) + bug = bd.bug_from_shortname(args[0]) if len(args) == 1: print bug.severity elif len(args) == 2: @@ -49,7 +48,7 @@ def execute(args): if e.name != "severity": raise raise cmdutil.UserError ("Invalid severity level: %s" % e.value) - bug.save() + bd.save() def get_parser(): parser = cmdutil.CmdOptionParser("be severity bug-id [severity]") diff --git a/becommands/show.py b/becommands/show.py index 669a81d..ab296e3 100644 --- a/becommands/show.py +++ b/becommands/show.py @@ -15,26 +15,35 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Show a particular bug""" -from libbe import cmdutil, names, utility -from libbe.bug import thread_comments -import os +from libbe import cmdutil, bugdir __desc__ = __doc__ def execute(args): + """ + >>> import os + >>> bd = bugdir.simple_bug_dir() + >>> os.chdir(bd.root) + >>> execute (["a",]) + ID : a + Short name : a + Severity : minor + Status : open + Assigned : + Target : + Creator : John Doe + Created : Wed, 31 Dec 1969 19:00 (Thu, 01 Jan 1970 00:00:00 +0000) + Bug A + + """ options, args = get_parser().parse_args(args) - if len(args) !=1: + if len(args) == 0: raise cmdutil.UserError("Please specify a bug id.") - bug_dir = cmdutil.bug_tree() - bug = cmdutil.get_bug(args[0], bug_dir) - print bug.string().rstrip("\n") - unique_name = names.unique_name(bug, bug_dir.list()) - comments = [] - name_map = {} - for c_name, comment in cmdutil.iter_comment_name(bug, unique_name): - name_map[comment.uuid] = c_name - comments.append(comment) - threaded = thread_comments(comments) - cmdutil.print_threaded_comments(threaded, name_map) + if len(args) > 1: + help() + raise cmdutil.UserError("Too many arguments.") + bd = bugdir.BugDir(loadNow=True) + bug = bd.bug_from_shortname(args[0]) + print bug.string(show_comments=True) def get_parser(): parser = cmdutil.CmdOptionParser("be show bug-id") diff --git a/becommands/status.py b/becommands/status.py index 5559e59..de171f5 100644 --- a/becommands/status.py +++ b/becommands/status.py @@ -15,16 +15,15 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Show or change a bug's status""" -from libbe import cmdutil +from libbe import cmdutil, bugdir from libbe.bug import status_values, status_description __desc__ = __doc__ def execute(args): """ - >>> from libbe import bugdir >>> import os - >>> dir = bugdir.simple_bug_dir() - >>> os.chdir(dir.dir) + >>> bd = bugdir.simple_bug_dir() + >>> os.chdir(bd.root) >>> execute(["a"]) open >>> execute(["a", "closed"]) @@ -35,11 +34,11 @@ def execute(args): UserError: Invalid status: none """ options, args = get_parser().parse_args(args) - assert(len(args) in (0, 1, 2)) - if len(args) == 0: + if len(args) not in (1,2): print help() return - bug = cmdutil.get_bug(args[0]) + bd = bugdir.BugDir(loadNow=True) + bug = bd.bug_from_shortname(args[0]) if len(args) == 1: print bug.status elif len(args) == 2: @@ -49,7 +48,7 @@ def execute(args): if e.name != "status": raise raise cmdutil.UserError ("Invalid status: %s" % e.value) - bug.save() + bd.save() def get_parser(): parser = cmdutil.CmdOptionParser("be status bug-id [status]") diff --git a/becommands/target.py b/becommands/target.py index 16de8fe..2047397 100644 --- a/becommands/target.py +++ b/becommands/target.py @@ -15,15 +15,14 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Show or change a bug's target for fixing""" -from libbe import bugdir -from libbe import cmdutil +from libbe import cmdutil, bugdir __desc__ = __doc__ def execute(args): """ >>> import os - >>> dir = bugdir.simple_bug_dir() - >>> os.chdir(dir.dir) + >>> bd = bugdir.simple_bug_dir() + >>> os.chdir(bd.root) >>> execute(["a"]) No target assigned. >>> execute(["a", "tomorrow"]) @@ -38,7 +37,8 @@ def execute(args): if len(args) == 0: print help() return - bug = cmdutil.get_bug(args[0]) + bd = bugdir.BugDir(loadNow=True) + bug = bd.bug_from_shortname(args[0]) if len(args) == 1: if bug.target is None: print "No target assigned." @@ -49,7 +49,7 @@ def execute(args): bug.target = None else: bug.target = args[1] - bug.save() + bd.save() def get_parser(): parser = cmdutil.CmdOptionParser("be target bug-id [target]") diff --git a/becommands/upgrade.py b/becommands/upgrade.py deleted file mode 100644 index c48eaaa..0000000 --- a/becommands/upgrade.py +++ /dev/null @@ -1,115 +0,0 @@ -# Copyright (C) 2005 Aaron Bentley and Panometrics, Inc. -# -# -# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -# OUTDATED - -"""Upgrade the bugs to the latest format""" -import os.path -import errno -from libbe import bugdir, rcs, cmdutil -__desc__ = __doc__ - -def execute(args): - options, args = get_parser().parse_args(args) - root = bugdir.tree_root(".", old_version=True) - for uuid in root.list_uuids(): - old_bug = OldBug(root.bugs_path, uuid) - - new_bug = root.get_bug(uuid) - 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) - -def get_parser(): - parser = cmdutil.CmdOptionParser("be upgrade") - return parser - -longhelp=""" -Upgrade the bug storage to the latest format. -""" - -def help(): - return get_parser().help_str() + longhelp diff --git a/libbe/arch.py b/libbe/arch.py index 8e7390d..b35a897 100644 --- a/libbe/arch.py +++ b/libbe/arch.py @@ -50,14 +50,6 @@ class Arch(RCS): if self._u_search_parent_directories(path, "{arch}") != None : return True return False - def _rcs_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) - # get archive name... - return output.rstrip('\n') def _rcs_init(self, path): self._create_archive(path) self._create_project(path) @@ -121,11 +113,35 @@ class Arch(RCS): 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 = [] + for line in file(tagpath, "rb"): + line.decode("utf-8") + if line.startswith("source "): + lines_out.append("source ^[._=a-zA-X0-9].*$\n") + else: + lines_out.append(line) + file(tagpath, "wb").write("".join(lines_out).encode("utf-8")) + def _add_project_code(self, path): # http://mwolson.org/projects/GettingStartedWithArch.html - # http://regexps.srparish.net/tutorial-tla/importing-first.html#Importing_the_First_Revision - self._u_invoke_client("init-tree", self._archive_project_name(), + # 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, directory=path) + self._adjust_naming_conventions(path) self._invoke_client("import", "--summary", "Began versioning", directory=path) def _rcs_cleanup(self): @@ -133,6 +149,40 @@ class Arch(RCS): 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) + 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", directory=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 _rcs_get_user_id(self): try: status,output,error = self._u_invoke_client('my-id') diff --git a/libbe/bug.py b/libbe/bug.py index a297b1a..b1e8d26 100644 --- a/libbe/bug.py +++ b/libbe/bug.py @@ -17,12 +17,15 @@ import os import os.path import errno -import names -import mapfile import time -import utility import doctest +from beuuid import uuid_gen +import mapfile +import comment +import utility + + ### Define and describe valid bug categories # Use a tuple of (category, description) tuples since we don't have # ordered dicts in Python yet http://www.python.org/dev/peps/pep-0372/ @@ -87,45 +90,46 @@ class Bug(object): severity = checked_property("severity", severity_values) status = checked_property("status", status_values) - def __init__(self, path, uuid, rcs, bugdir): - self.path = path - self.uuid = uuid - if uuid is not None: - dict = mapfile.map_load(self.get_path("values")) - else: - dict = {} - - self.rcs = rcs - self.bugdir = bugdir - - self.summary = dict.get("summary") - self.creator = dict.get("creator") - self.target = dict.get("target") - self.status = dict.get("status", "open") - self.severity = dict.get("severity", "minor") - self.assigned = dict.get("assigned") - self.time = dict.get("time") - if self.time is not None: - self.time = utility.str_to_time(self.time) - - def get_path(self, file=None): - if file == None: - return os.path.join(self.path, self.uuid) - else: - return os.path.join(self.path, self.uuid, file) - def _get_active(self): return self.status in active_status_values active = property(_get_active) + def __init__(self, bugdir=None, uuid=None, loadNow=False, summary=None): + self.bugdir = bugdir + if bugdir != None: + self.rcs = bugdir.rcs + else: + self.rcs = None + if loadNow == True: + self.uuid = uuid + self.load() + else: + # Note: defaults should match those in Bug.load() + if uuid != None: + self.uuid = uuid + else: + self.uuid = uuid_gen() + self.summary = summary + if self.rcs != None: + self.creator = self.rcs.get_user_id() + else: + self.creator = None + self.target = None + self.status = "open" + self.severity = "minor" + self.assigned = None + self.time = time.time() + self.comment_root = comment.Comment(self, uuid=comment.INVALID_UUID) + def __repr__(self): return "Bug(uuid=%r)" % self.uuid - def string(self, bugs=None, shortlist=False): - if bugs == None: - bugs = list(self.bugdir.list()) - short_name = names.unique_name(self, bugs) + def string(self, shortlist=False, show_comments=False): + if self.bugdir == None: + shortname = self.uuid + else: + shortname = self.bugdir.bug_shortname(self) if shortlist == False: if self.time == None: timestring = "" @@ -134,7 +138,7 @@ class Bug(object): ftime = utility.time_to_str(self.time) timestring = "%s (%s)" % (htime, ftime) info = [("ID", self.uuid), - ("Short name", short_name), + ("Short name", shortname), ("Severity", self.severity), ("Status", self.status), ("Assigned", self.assigned), @@ -150,12 +154,20 @@ class Bug(object): info = newinfo longest_key_len = max([len(k) for k,v in info]) infolines = [" %*s : %s\n" %(longest_key_len,k,v) for k,v in info] - return "".join(infolines) + "%s\n" % self.summary + bugout = "".join(infolines) + "%s" % self.summary.rstrip('\n') else: statuschar = self.status[0] severitychar = self.severity[0] chars = "%c%c" % (statuschar, severitychar) - return "%s:%s: %s" % (short_name, chars, self.summary) + bugout = "%s:%s: %s" % (shortname, chars, self.summary.rstrip('\n')) + + if show_comments == True: + comout = self.comment_root.string_thread(auto_name_map=True, + bug_shortname=shortname) + output = bugout + '\n' + comout.rstrip('\n') + else : + output = bugout + return output def __str__(self): return self.string(shortlist=True) @@ -163,7 +175,28 @@ class Bug(object): def __cmp__(self, other): return cmp_full(self, other) - def add_attr(self, map, name): + def get_path(self, name=None): + my_dir = os.path.join(self.bugdir.get_path("bugs"), self.uuid) + if name is None: + return my_dir + assert name in ["values", "comments"] + return os.path.join(my_dir, name) + + def load(self): + map = mapfile.map_load(self.get_path("values")) + self.summary = map.get("summary") + self.creator = map.get("creator") + self.target = map.get("target") + self.status = map.get("status", "open") + self.severity = map.get("severity", "minor") + self.assigned = map.get("assigned") + self.time = map.get("time") + if self.time is not None: + self.time = utility.str_to_time(self.time) + + self.comment_root = comment.loadComments(self) + + def _add_attr(self, map, name): value = getattr(self, name) if value is not None: map[name] = value @@ -171,134 +204,39 @@ class Bug(object): def save(self): assert self.summary != None, "Can't save blank bug" map = {} - self.add_attr(map, "assigned") - self.add_attr(map, "summary") - self.add_attr(map, "creator") - self.add_attr(map, "target") - self.add_attr(map, "status") - self.add_attr(map, "severity") + self._add_attr(map, "assigned") + self._add_attr(map, "summary") + self._add_attr(map, "creator") + self._add_attr(map, "target") + self._add_attr(map, "status") + self._add_attr(map, "severity") if self.time is not None: map["time"] = utility.time_to_str(self.time) + + self.rcs.mkdir(self.get_path()) path = self.get_path("values") mapfile.map_save(self.rcs, path, map) + if len(self.comment_root) > 0: + self.rcs.mkdir(self.get_path("comments")) + comment.saveComments(self) + def remove(self): + self.comment_root.remove() path = self.get_path() self.rcs.recursive_remove(path) def new_comment(self, body=None): - if not os.path.exists(self.get_path("comments")): - self.rcs.mkdir(self.get_path("comments")) - comm = Comment(None, self) - comm.uuid = names.uuid() - comm.rcs = self.rcs - comm.From = self.rcs.get_user_id() - comm.time = time.time() - comm.body = body + comm = comment.comment_root.new_reply(body=body) return comm - def get_comment(self, uuid): - return Comment(uuid, self) - - def iter_comment_ids(self): - path = self.get_path("comments") - if not os.path.isdir(path): - return - try: - for uuid in os.listdir(path): - if (uuid.startswith('.')): - continue - yield uuid - except OSError, e: - if e.errno != errno.ENOENT: - raise - return - - def list_comments(self): - comments = [Comment(id, self) for id in self.iter_comment_ids()] - comments.sort(cmp_time) - return comments - -def add_headers(obj, map, names): - map_names = {} - for name in names: - map_names[name] = pyname_to_header(name) - add_attrs(obj, map, names, map_names) - -def add_attrs(obj, map, names, map_names=None): - if map_names is None: - map_names = {} - for name in names: - map_names[name] = name - - for name in names: - value = getattr(obj, name) - if value is not None: - map[map_names[name]] = value - - -class Comment(object): - def __init__(self, uuid, bug): - object.__init__(self) - self.uuid = uuid - self.bug = bug - if self.uuid is not None and self.bug is not None: - map = mapfile.map_load(self.get_path("values")) - self.time = utility.str_to_time(map["Date"]) - self.From = map["From"] - self.in_reply_to = map.get("In-reply-to") - self.content_type = map.get("Content-type", "text/plain") - self.body = file(self.get_path("body")).read().decode("utf-8") - else: - self.time = None - self.From = None - self.in_reply_to = None - self.content_type = "text/plain" - self.body = None + def comment_from_shortname(self, shortname, *args, **kwargs): + return self.comment_root.comment_from_shortname(shortname, *args, **kwargs) - def save(self): - map_file = {"Date": utility.time_to_str(self.time)} - add_headers(self, map_file, ("From", "in_reply_to", "content_type")) - if not os.path.exists(self.get_path()): - self.bug.rcs.mkdir(self.get_path()) - mapfile.map_save(self.bug.rcs, self.get_path("values"), map_file) - self.bug.rcs.set_file_contents(self.get_path("body"), - self.body.encode('utf-8')) - - def get_path(self, name=None): - my_dir = os.path.join(self.bug.get_path("comments"), self.uuid) - if name is None: - return my_dir - return os.path.join(my_dir, name) - - -def thread_comments(comments): - child_map = {} - top_comments = [] - for comment in comments: - child_map[comment.uuid] = [] - for comment in comments: - if comment.in_reply_to is None or comment.in_reply_to not in child_map: - top_comments.append(comment) - continue - child_map[comment.in_reply_to].append(comment) - - def recurse_children(comment): - child_list = [] - for child in child_map[comment.uuid]: - child_list.append(recurse_children(child)) - return (comment, child_list) - return [recurse_children(c) for c in top_comments] - -def pyname_to_header(name): - return name.capitalize().replace('_', '-') + def comment_from_uuid(self, uuid): + return self.comment_root.comment_from_uuid(uuid) - -class MockBug: - def __init__(self, attr, value): - setattr(self, attr, value) - # the general rule for bug sorting is that "more important" bugs are # less than "less important" bugs. This way sorting a list of bugs # will put the most important bugs first in the list. When relative @@ -307,32 +245,42 @@ class MockBug: def cmp_severity(bug_1, bug_2): """ - Compare the severity levels of two bugs, with more severe bugs comparing - as less. - - >>> attr="severity" - >>> cmp_severity(MockBug(attr,"wishlist"), MockBug(attr,"wishlist")) == 0 + Compare the severity levels of two bugs, with more severe bugs + comparing as less. + >>> bugA = Bug() + >>> bugB = Bug() + >>> bugA.severity = bugB.severity = "wishlist" + >>> cmp_severity(bugA, bugB) == 0 True - >>> cmp_severity(MockBug(attr,"wishlist"), MockBug(attr,"minor")) > 0 + >>> bugB.severity = "minor" + >>> cmp_severity(bugA, bugB) > 0 True - >>> cmp_severity(MockBug(attr,"critical"), MockBug(attr,"wishlist")) < 0 + >>> bugA.severity = "critical" + >>> cmp_severity(bugA, bugB) < 0 True """ + if not hasattr(bug_2, "severity") : + return 1 return -cmp(severity_index[bug_1.severity], severity_index[bug_2.severity]) def cmp_status(bug_1, bug_2): """ Compare the status levels of two bugs, with more 'open' bugs comparing as less. - - >>> attr="status" - >>> cmp_status(MockBug(attr,"open"), MockBug(attr,"open")) == 0 + >>> bugA = Bug() + >>> bugB = Bug() + >>> bugA.status = bugB.status = "open" + >>> cmp_status(bugA, bugB) == 0 True - >>> cmp_status(MockBug(attr,"open"), MockBug(attr,"closed")) < 0 + >>> bugB.status = "closed" + >>> cmp_status(bugA, bugB) < 0 True - >>> cmp_status(MockBug(attr,"closed"), MockBug(attr,"open")) > 0 + >>> bugA.status = "fixed" + >>> cmp_status(bugA, bugB) > 0 True """ + if not hasattr(bug_2, "status") : + return 1 val_2 = status_index[bug_2.status] return cmp(status_index[bug_1.status], status_index[bug_2.status]) @@ -342,13 +290,20 @@ def cmp_attr(bug_1, bug_2, attr, invert=False): comparison rule for that attribute type. If invert == True, sort *against* that convention. >>> attr="severity" - >>> cmp_attr(MockBug(attr,1), MockBug(attr,2), attr, invert=False) < 0 + >>> bugA = Bug() + >>> bugB = Bug() + >>> bugA.severity = "critical" + >>> bugB.severity = "wishlist" + >>> cmp_attr(bugA, bugB, attr) < 0 True - >>> cmp_attr(MockBug(attr,1), MockBug(attr,2), attr, invert=True) > 0 + >>> cmp_attr(bugA, bugB, attr, invert=True) > 0 True - >>> cmp_attr(MockBug(attr,1), MockBug(attr,1), attr) == 0 + >>> bugB.severity = "critical" + >>> cmp_attr(bugA, bugB, attr) == 0 True """ + if not hasattr(bug_2, attr) : + return 1 if invert == True : return -cmp(getattr(bug_1, attr), getattr(bug_2, attr)) else : diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 41f0fec..6152e3f 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -16,16 +16,17 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os import os.path -import cmdutil import errno +import time import unittest import doctest -import names + +from beuuid import uuid_gen import mapfile -import time +import bug +import cmdutil import utility -from rcs import rcs_by_name, installed_rcs -from bug import Bug +from rcs import rcs_by_name, detect_rcs, installed_rcs, PathNotInRoot class NoBugDir(Exception): def __init__(self, path): @@ -33,48 +34,6 @@ class NoBugDir(Exception): Exception.__init__(self, msg) self.path = path - -def iter_parent_dirs(cur_dir): - cur_dir = os.path.realpath(cur_dir) - old_dir = None - while True: - yield cur_dir - old_dir = cur_dir - cur_dir = os.path.normpath(os.path.join(cur_dir, '..')) - if old_dir == cur_dir: - break; - - -def tree_root(dir, old_version=False): - for rootdir in iter_parent_dirs(dir): - versionfile=os.path.join(rootdir, ".be", "version") - if os.path.exists(versionfile): - if not old_version: - test_version(versionfile) - return BugDir(os.path.join(rootdir, ".be")) - elif not os.path.exists(rootdir): - raise NoRootEntry(rootdir) - old_rootdir = rootdir - rootdir=os.path.join('..', rootdir) - - raise NoBugDir(dir) - -class BadTreeVersion(Exception): - def __init__(self, version): - Exception.__init__(self, "Unsupported tree version: %s" % version) - self.version = version - -def test_version(path): - tree_version = file(path, "rb").read() - if tree_version != TREE_VERSION_STRING: - raise BadTreeVersion(tree_version) - -def set_version(path, rcs): - rcs.set_file_contents(os.path.join(path, "version"), TREE_VERSION_STRING) - - -TREE_VERSION_STRING = "Bugs Everywhere Tree 1 0\n" - class NoRootEntry(Exception): def __init__(self, path): self.path = path @@ -86,32 +45,15 @@ class AlreadyInitialized(Exception): Exception.__init__(self, "Specified root is already initialized: %s" % path) -def bugdir_root(versioning_root): - return os.path.join(versioning_root, ".be") +class InvalidValue(ValueError): + def __init__(self, name, value): + msg = "Cannot assign value %s to %s" % (value, name) + Exception.__init__(self, msg) + self.name = name + self.value = value + -def create_bug_dir(path, rcs): - """ - >>> import tests - >>> rcs = rcs_by_name("None") - >>> create_bug_dir('/highly-unlikely-to-exist', rcs) - Traceback (most recent call last): - NoRootEntry: Specified root does not exist: /highly-unlikely-to-exist - """ - root = os.path.join(path, ".be") - try: - rcs.mkdir(root) - except OSError, e: - if e.errno == errno.ENOENT: - raise NoRootEntry(path) - elif e.errno == errno.EEXIST: - raise AlreadyInitialized(path) - else: - raise - rcs.mkdir(os.path.join(root, "bugs")) - set_version(root, rcs) - mapfile.map_save(rcs, - os.path.join(root, "settings"), {"rcs_name": rcs.name}) - return BugDir(bugdir_root(path)) +TREE_VERSION_STRING = "Bugs Everywhere Tree 1 0\n" def setting_property(name, valid=None): @@ -130,83 +72,232 @@ def setting_property(name, valid=None): del self.settings[name] else: self.settings[name] = value - self.save_settings() + self.save() return property(getter, setter) -class BugDir: - def __init__(self, dir): - self.dir = dir - self.bugs_path = os.path.join(self.dir, "bugs") +class BugDir (list): + 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) + 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): + raise NoRootEntry(root) + self.root = root + if loadNow == True: + self.load() + else: + if assert_new_BugDir: + 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 + + def find_root(self, path): + """ + Search for an existing bug database dir and it's ancestors and + return a BugDir rooted there. + """ + if not os.path.exists(path): + 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: + raise NoBugDir(path) + return beroot + + def get_version(self, path=None): + if path == None: + path = self.get_path("version") try: - self.settings = mapfile.map_load(os.path.join(self.dir,"settings")) - except mapfile.NoSuchFile: - self.settings = {"rcs_name": "None"} + tree_version = self.rcs.get_file_contents(path) + except AttributeError, e: + # haven't initialized rcs yet + tree_version = file(path, "rb").read().decode("utf-8") + return tree_version + + def set_version(self): + self.rcs.set_file_contents(self.get_path("version"), TREE_VERSION_STRING) rcs_name = setting_property("rcs_name", ("None", "bzr", "git", "Arch", "hg")) - _rcs = None - target = setting_property("target") - - def save_settings(self): - mapfile.map_save(self.rcs, - os.path.join(self.dir, "settings"), self.settings) + _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.dir) + self._rcs.root(self.root) return self._rcs rcs = property(_get_rcs) + target = setting_property("target") + + def get_path(self, *args): + my_dir = os.path.join(self.root, ".be") + if len(args) == 0: + return my_dir + assert args[0] in ["version", "settings", "bugs"], str(args) + return os.path.join(my_dir, *args) + + def guess_rcs(self, allow_rcs_init=False): + deepdir = self.get_path() + if not os.path.exists(deepdir): + deepdir = os.path.dirname(deepdir) + rcs = detect_rcs(deepdir) + 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 + return rcs + + def load(self): + version = self.get_version() + if version != TREE_VERSION_STRING: + raise NotImplementedError, "BugDir cannot handle version '%s' yet." % version + else: + if not os.path.exists(self.get_path()): + raise NoBugDir(self.get_path()) + self.settings = self._get_settings(self.get_path("settings")) + self._clear_bugs() + for uuid in self.list_uuids(): + self._load_bug(uuid) + + self._bug_map_gen() + + def save(self): + self.rcs.mkdir(self.get_path()) + self.set_version() + self._save_settings(self.get_path("settings"), self.settings) + self.rcs.mkdir(self.get_path("bugs")) + for bug in self: + bug.save() + + def _get_settings(self, settings_path): + try: + settings = mapfile.map_load(settings_path) + except mapfile.NoSuchFile: + settings = {"rcs_name": "None"} + return settings + + def _save_settings(self, settings_path, settings): + try: + mapfile.map_save(self.rcs, settings_path, settings) + except PathNotInRoot, e: + # Handling duplicate bugdir settings, special case + none_rcs = rcs_by_name("None") + none_rcs.root(settings_path) + mapfile.map_save(none_rcs, settings_path, settings) + def duplicate_bugdir(self, revision): - return BugDir(bugdir_root(self.rcs.duplicate_repo(revision))) + duplicate_path = self.rcs.duplicate_repo(revision) - def remove_duplicate_bugdir(self): - self.rcs.remove_duplicate_repo() + # setup revision RCS as None, since the duplicate may not be versioned + duplicate_settings_path = os.path.join(duplicate_path, ".be", "settings") + duplicate_settings = self._get_settings(duplicate_settings_path) + if "rcs_name" in duplicate_settings: + duplicate_settings["rcs_name"] = "None" + self._save_settings(duplicate_settings_path, duplicate_settings) - def list(self): - for uuid in self.list_uuids(): - yield self.get_bug(uuid) + return BugDir(duplicate_path, loadNow=True) - def bug_map(self): - bugs = {} - for bug in self.list(): - bugs[bug.uuid] = bug - return bugs + def remove_duplicate_bugdir(self): + self.rcs.remove_duplicate_repo() - def get_bug(self, uuid): - return Bug(self.bugs_path, uuid, self.rcs, self) + def _bug_map_gen(self): + map = {} + for bug in self: + map[bug.uuid] = bug + self.bug_map = map def list_uuids(self): - for uuid in os.listdir(self.bugs_path): + for uuid in os.listdir(self.get_path("bugs")): if (uuid.startswith('.')): continue yield uuid - def new_bug(self, uuid=None): - if uuid is None: - uuid = names.uuid() - path = os.path.join(self.bugs_path, uuid) - self.rcs.mkdir(path) - bug = Bug(self.bugs_path, None, self.rcs, self) - bug.uuid = uuid - bug.creator = self.rcs.get_user_id() - bug.severity = "minor" - bug.status = "open" - bug.time = time.time() - return bug + def _clear_bugs(self): + while len(self) > 0: + self.pop() + + def _load_bug(self, uuid): + bg = bug.Bug(bugdir=self, uuid=uuid, loadNow=True) + self.append(bg) + self._bug_map_gen() + return bg + + def new_bug(self, uuid=None, summary=None): + bg = bug.Bug(bugdir=self, uuid=uuid, summary=summary) + self.append(bg) + self._bug_map_gen() + return bg + + def remove_bug(self, bug): + self.remove(bug) + bug.remove() + + def bug_shortname(self, bug): + """ + Generate short names from uuids. Picks the minimum number of + characters (>=3) from the beginning of the uuid such that the + short names are unique. + + Obviously, as the number of bugs in the database grows, these + short names will cease to be unique. The complete uuid should be + used for long term reference. + """ + chars = 3 + for uuid in self.bug_map.keys(): + if bug.uuid == uuid: + continue + while (bug.uuid[:chars] == uuid[:chars]): + chars+=1 + return bug.uuid[:chars] + + def bug_from_shortname(self, shortname): + """ + >>> bd = simple_bug_dir() + >>> bug_a = bd.bug_from_shortname('a') + >>> print type(bug_a) + + >>> print bug_a + a:om: Bug A + """ + matches = [] + for bug in self: + if bug.uuid.startswith(shortname): + matches.append(bug) + if len(matches) > 1: + raise cmdutil.UserError("More than one bug matches %s. Please be more" + " specific." % shortname) + if len(matches) == 1: + return matches[0] + raise KeyError("No bug matches %s" % shortname) + + def bug_from_uuid(self, uuid): + if uuid not in self.bug_map: + self._bug_map_gen() + if uuid not in self.bug_map: + raise KeyError("No bug matches %s" % uuid +str(self.bug_map)+str(self)) + return self.bug_map[uuid] -class InvalidValue(ValueError): - def __init__(self, name, value): - msg = "Cannot assign value %s to %s" % (value, name) - Exception.__init__(self, msg) - self.name = name - self.value = value def simple_bug_dir(): """ @@ -218,18 +309,17 @@ def simple_bug_dir(): ['a', 'b'] """ dir = utility.Dir() - rcs = installed_rcs() - rcs.init(dir.path) assert os.path.exists(dir.path) - bugdir = create_bug_dir(dir.path, rcs) + bugdir = BugDir(dir.path, sink_to_existing_root=False, allow_rcs_init=True) bugdir._dir_ref = dir # postpone cleanup since dir.__del__() removes dir. - bug_a = bugdir.new_bug("a") - bug_a.summary = "Bug A" - bug_a.save() - bug_b = bugdir.new_bug("b") + bug_a = bugdir.new_bug("a", summary="Bug A") + bug_a.creator = "John Doe " + bug_a.time = 0 + bug_b = bugdir.new_bug("b", summary="Bug B") + bug_b.creator = "Jane Doe " + bug_b.time = 0 bug_b.status = "closed" - bug_b.summary = "Bug B" - bug_b.save() + bugdir.save() return bugdir @@ -238,9 +328,8 @@ class BugDirTestCase(unittest.TestCase): unittest.TestCase.__init__(self, *args, **kwargs) def setUp(self): self.dir = utility.Dir() - self.rcs = installed_rcs() - self.rcs.init(self.dir.path) - self.bugdir = create_bug_dir(self.dir.path, self.rcs) + self.bugdir = BugDir(self.dir.path, sink_to_existing_root=False, allow_rcs_init=True) + self.rcs = self.bugdir.rcs def tearDown(self): del(self.rcs) del(self.dir) @@ -250,9 +339,8 @@ class BugDirTestCase(unittest.TestCase): fullpath = self.fullPath(path) self.failUnless(os.path.exists(fullpath)==True, "path %s does not exist" % fullpath) - def testBugDirDuplicate(self): - self.assertRaises(AlreadyInitialized, create_bug_dir, - self.dir.path, self.rcs) + self.assertRaises(AlreadyInitialized, BugDir, + self.dir.path, assertNewBugDir=True) unitsuite = unittest.TestLoader().loadTestsFromTestCase(BugDirTestCase) suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py index 62a0c7c..55a7a72 100644 --- a/libbe/cmdutil.py +++ b/libbe/cmdutil.py @@ -14,16 +14,17 @@ # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import bugdir -import plugin -import locale -import os import optparse +import os +import locale from textwrap import TextWrapper from StringIO import StringIO -import utility import doctest +import bugdir +import plugin +import utility + class UserError(Exception): def __init__(self, msg): Exception.__init__(self, msg) @@ -33,40 +34,6 @@ class UserErrorWrap(UserError): UserError.__init__(self, str(exception)) self.exception = exception -def get_bug(spec, bug_dir=None): - """ - >>> bd = bugdir.simple_bug_dir() - >>> bug_a = get_bug('a', bd) - >>> print type(bug_a) - - >>> print bug_a - a:om: Bug A - >>> print bd.get_bug('a') - a:om: Bug A - >>> bug_a == bd.get_bug('a') - True - """ - matches = [] - try: - if bug_dir is None: - bug_dir = bugdir.tree_root('.') - except bugdir.NoBugDir, e: - raise UserErrorWrap(e) - bugs = list(bug_dir.list()) - for bug in bugs: - if bug.uuid.startswith(spec): - matches.append(bug) - if len(matches) > 1: - raise UserError("More than one bug matches %s. Please be more" - " specific." % spec) - if len(matches) == 1: - return matches[0] - - matches = [] - if len(matches) == 0: - raise UserError("No bug matches %s" % spec) - return matches[0] - def iter_commands(): for name, module in plugin.iter_plugins("becommands"): yield name.replace("_", "-"), module @@ -115,34 +82,6 @@ class UsageError(Exception): def raise_get_help(option, opt, value, parser): raise GetHelp - -def iter_comment_name(bug, unique_name): - """Iterate through id, comment pairs, in date order. - (This is a user-friendly id, not the comment uuid) - """ - def key(comment): - return comment.time - for num, comment in enumerate(sorted(bug.list_comments(), key=key)): - yield ("%s:%d" % (unique_name, num+1), comment) - - -def comment_from_name(bug, unique_name, name): - """Use a comment name to look up a comment""" - for cur_name, comment in iter_comment_name(bug, unique_name): - if name == cur_name: - return comment - raise KeyError(name) - - -def get_bug_and_comment(identifier, bug_dir=None): - ids = identifier.split(':') - bug = get_bug(ids[0], bug_dir) - if len(ids) == 2: - comment = comment_from_name(bug, ids[0], identifier) - else: - comment = None - return bug, comment - class CmdOptionParser(optparse.OptionParser): def __init__(self, usage): @@ -174,44 +113,6 @@ def underlined(instring): return "%s\n%s" % (instring, "="*len(instring)) -def print_threaded_comments(comments, name_map, indent=""): - """Print a threaded display of comments""" - tw = TextWrapper(initial_indent = indent, subsequent_indent = indent, - width=80) - for comment, children in comments: - s = StringIO() - print >> s, "--------- Comment ---------" - print >> s, "Name: %s" % name_map[comment.uuid] - print >> s, "From: %s" % comment.From - print >> s, "Date: %s\n" % utility.time_to_str(comment.time) - print >> s, comment.body.rstrip('\n') - - s.seek(0) - for line in s: - print tw.fill(line).rstrip('\n') - print_threaded_comments(children, name_map, indent=indent+" ") - - -def bug_tree(dir=None): - """Retrieve the bug tree specified by the user. If no directory is - specified, the current working directory is used. - - :param dir: The directory to search for the bug tree in. - - >>> bug_tree() is not None - True - >>> bug_tree("/") - Traceback (most recent call last): - UserErrorWrap: The directory "/" has no bug directory. - """ - if dir is None: - dir = os.getcwd() - try: - return bugdir.tree_root(dir) - except bugdir.NoBugDir, e: - raise UserErrorWrap(e) - - def _test(): import doctest import sys diff --git a/libbe/diff.py b/libbe/diff.py index 9fa3816..95d5607 100644 --- a/libbe/diff.py +++ b/libbe/diff.py @@ -20,33 +20,24 @@ from libbe.utility import time_to_str from libbe.bug import cmp_severity import doctest -def diff(old_tree, new_tree): - old_bug_map = old_tree.bug_map() - new_bug_map = new_tree.bug_map() +def diff(old_bugdir, new_bugdir): added = [] removed = [] modified = [] - for old_bug in old_bug_map.itervalues(): - new_bug = new_bug_map.get(old_bug.uuid) + for old_bug in old_bugdir: + new_bug = new_bugdir.bug_map.get(old_bug.uuid) if new_bug is None : removed.append(old_bug) else: if old_bug != new_bug: modified.append((old_bug, new_bug)) - for new_bug in new_bug_map.itervalues(): - if not old_bug_map.has_key(new_bug.uuid): + for new_bug in new_bugdir: + if not old_bugdir.bug_map.has_key(new_bug.uuid): added.append(new_bug) return (removed, modified, added) - -def reference_diff(bugdir, revision=None): - d = diff(bugdir.duplicate_bugdir(revision), bugdir) - bugdir.remove_duplicate_bugdir() - return d - def diff_report(diff_data, bug_dir): (removed, modified, added) = diff_data - bugs = list(bug_dir.list()) def modified_cmp(left, right): return cmp_severity(left[1], right[1]) @@ -54,7 +45,7 @@ def diff_report(diff_data, bug_dir): removed.sort(cmp_severity) modified.sort(modified_cmp) - if len(added) > 0: + if len(added) > 0: print "New bug reports:" for bug in added: print bug.string(shortlist=True) @@ -62,7 +53,7 @@ def diff_report(diff_data, bug_dir): if len(modified) > 0: printed = False for old_bug, new_bug in modified: - change_str = bug_changes(old_bug, new_bug, bugs) + change_str = bug_changes(old_bug, new_bug, bug_dir) if change_str is None: continue if not printed: @@ -73,7 +64,7 @@ def diff_report(diff_data, bug_dir): if len(removed) > 0: print "Removed bug reports:" for bug in removed: - print bug.string(bugs, shortlist=True) + print bug.string(shortlist=True) def change_lines(old, new, attributes): change_list = [] @@ -91,8 +82,8 @@ def bug_changes(old, new, bugs): change_list = change_lines(old, new, ("time", "creator", "severity", "target", "summary", "status", "assigned")) - old_comment_ids = list(old.iter_comment_ids()) - new_comment_ids = list(new.iter_comment_ids()) + old_comment_ids = [c.uuid for c in old.comment_root.traverse()] + new_comment_ids = [c.uuid for c in new.comment_root.traverse()] change_strings = ["%s: %s -> %s" % f for f in change_list] for comment_id in new_comment_ids: if comment_id not in old_comment_ids: @@ -105,8 +96,8 @@ def bug_changes(old, new, bugs): if len(change_strings) == 0: return None - return "%s%s\n" % (new.string(bugs, shortlist=True), - "\n".join(change_strings)) + return "%s\n %s" % (new.string(shortlist=True), + " \n".join(change_strings)) def comment_summary(comment, status): diff --git a/libbe/mapfile.py b/libbe/mapfile.py index 8f69554..9a7fa8b 100644 --- a/libbe/mapfile.py +++ b/libbe/mapfile.py @@ -95,11 +95,11 @@ def parse(f): f = utility.get_file(f) result = {} for line in f: - line = line.rstrip('\n') + line = line.decode("utf-8").rstrip('\n') if len(line) == 0: continue - name,value = [f.decode('utf-8') for f in line.split('=', 1)] - assert not result.has_key('name') + name,value = [f for f in line.split('=', 1)] + assert not result.has_key(name) result[name] = value return result diff --git a/libbe/names.py b/libbe/names.py deleted file mode 100644 index 6e0378e..0000000 --- a/libbe/names.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (C) 2005 Aaron Bentley and Panometrics, Inc. -# -# -# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import os -import sys -import doctest - -def uuid(): - # this code borrowed from standard commands module - # but adapted to win32 - pipe = os.popen('uuidgen', 'r') - text = pipe.read() - sts = pipe.close() - if sts not in (0, None): - raise "Failed to run uuidgen" - if text[-1:] == '\n': text = text[:-1] - return text - -def unique_name(bug, bugs): - """ - Generate short names from uuids. Picks the minimum number of - characters (>=3) from the beginning of the uuid such that the - short names are unique. - - Obviously, as the number of bugs in the database grows, these - short names will cease to be unique. The complete uuid should be - used for long term reference. - """ - chars = 3 - for some_bug in bugs: - if bug.uuid == some_bug.uuid: - continue - while (bug.uuid[:chars] == some_bug.uuid[:chars]): - chars+=1 - return bug.uuid[:chars] - -suite = doctest.DocTestSuite() diff --git a/libbe/plugin.py b/libbe/plugin.py index 05a4398..0964fba 100644 --- a/libbe/plugin.py +++ b/libbe/plugin.py @@ -36,6 +36,8 @@ def iter_plugins(prefix): modfiles = os.listdir(os.path.join(plugin_path, prefix)) modfiles.sort() for modfile in modfiles: + if modfile.startswith('.'): + continue # the occasional emacs temporary file if modfile.endswith(".py") and modfile != "__init__.py": yield modfile[:-3], my_import(prefix+"."+modfile[:-3]) diff --git a/libbe/rcs.py b/libbe/rcs.py index 2993a80..abd92cb 100644 --- a/libbe/rcs.py +++ b/libbe/rcs.py @@ -24,7 +24,7 @@ import tempfile import shutil import unittest import doctest -from utility import Dir +from utility import Dir, search_parent_directories def _get_matching_rcs(matchfn): """Return the first module for which matchfn(RCS_instance) is true""" @@ -32,9 +32,9 @@ def _get_matching_rcs(matchfn): import bzr import hg import git - for module in [arch, bzr, hg, git]: + for module in [git, arch, bzr, hg, git]: rcs = module.new() - if matchfn(rcs): + if matchfn(rcs) == True: return rcs else: del(rcs) @@ -62,6 +62,9 @@ class CommandError(Exception): class SettingIDnotSupported(NotImplementedError): pass +class PathNotInRoot(Exception): + pass + def new(): return RCS() @@ -152,7 +155,10 @@ class RCS(object): pass def _rcs_get_file_contents(self, path, revision=None): """ - Get the file as it was in a given revision. + Get the file contents as they were in a given revision. Don't + worry about decoding the contents, the RCS.get_file_contents() + method will handle that. + Revision==None specifies the current revision. """ assert revision == None, \ @@ -180,7 +186,7 @@ class RCS(object): if e.errno == errno.ENOENT: return False raise e - def detect(self, path=None): + def detect(self, path="."): """ Detect whether a directory is revision controlled with this RCS. """ @@ -264,23 +270,28 @@ class RCS(object): Revision==None specifies the current revision. """ relpath = self._u_rel_path(path) - return self._rcs_get_file_contents(relpath, revision) + return self._rcs_get_file_contents(relpath, revision).decode("utf-8") def set_file_contents(self, path, contents): """ Set the file contents under version control. """ add = not os.path.exists(path) - file(path, "wb").write(contents) + file(path, "wb").write(contents.encode("utf-8")) if add: self.add(path) else: self.update(path) def mkdir(self, path): """ - Created directory at path under version control. + Create (if neccessary) a directory at path under version + control. """ - os.mkdir(path) - self.add(path) + if not os.path.exists(path): + os.mkdir(path) + self.add(path) + else: + assert os.path.isdir(path) + self.update(path) def duplicate_repo(self, revision=None): """ Get the repository as it was in a given revision. @@ -366,16 +377,7 @@ class RCS(object): /.be or None if none of those files exist. """ - path = os.path.realpath(path) - assert os.path.exists(path) - old_path = None - while True: - if os.path.exists(os.path.join(path, filename)): - return os.path.join(path, filename) - if path == old_path: - return None - old_path = path - path = os.path.dirname(path) + return search_parent_directories(path, filename) def _u_rel_path(self, path, root=None): """ Return the relative path to path from root. @@ -389,8 +391,9 @@ class RCS(object): if os.path.isabs(path): absRoot = os.path.abspath(root) absRootSlashedDir = os.path.join(absRoot,"") - assert path.startswith(absRootSlashedDir), \ - "file %s not in root %s" % (path, absRootSlashedDir) + if not path.startswith(absRootSlashedDir): + raise PathNotInRoot, \ + "file %s not in root %s" % (path, absRootSlashedDir) assert path != absRootSlashedDir, \ "file %s == root directory %s" % (path, absRootSlashedDir) path = path[len(absRootSlashedDir):] diff --git a/libbe/utility.py b/libbe/utility.py index f595bdb..81023cd 100644 --- a/libbe/utility.py +++ b/libbe/utility.py @@ -71,6 +71,32 @@ def get_file(f): else: return f +def search_parent_directories(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. + """ + path = os.path.realpath(path) + assert os.path.exists(path) + old_path = None + while True: + check_path = os.path.join(path, filename) + if os.path.exists(check_path): + return check_path + if path == old_path: + return None + old_path = path + path = os.path.dirname(path) + class Dir: "A temporary directory for testing use" def __init__(self): diff --git a/test.py b/test.py index 9af153b..bf57d1e 100644 --- a/test.py +++ b/test.py @@ -1,11 +1,11 @@ -"""Usage: python test.py [module] +"""Usage: python test.py [module(s) ...] -When called without an optional module name, run the doctests from -*all* modules. This may raise lots of errors if you haven't installed -one of the versioning control systems. +When called without optional module names, run the doctests from *all* +modules. This may raise lots of errors if you haven't installed one +of the versioning control systems. -When called with an optional module name, only run the doctests from -that module. +When called with module name arguments, only run the doctests from +those modules. """ from libbe import plugin @@ -16,19 +16,19 @@ import sys suite = unittest.TestSuite() if len(sys.argv) > 1: - submodname = sys.argv[1] - match = False - mod = plugin.get_plugin("libbe", submodname) - if mod is not None and hasattr(mod, "suite"): - suite.addTest(mod.suite) - match = True - mod = plugin.get_plugin("becommands", submodname) - if mod is not None: - suite.addTest(doctest.DocTestSuite(mod)) - match = True - if not match: - print "No modules match \"%s\"" % submodname - sys.exit(1) + for submodname in sys.argv[1:]: + match = False + mod = plugin.get_plugin("libbe", submodname) + if mod is not None and hasattr(mod, "suite"): + suite.addTest(mod.suite) + match = True + mod = plugin.get_plugin("becommands", submodname) + if mod is not None: + suite.addTest(doctest.DocTestSuite(mod)) + match = True + if not match: + print "No modules match \"%s\"" % submodname + sys.exit(1) else: failed = False for modname,module in plugin.iter_plugins("libbe"):