diff options
736 files changed, 32709 insertions, 16 deletions
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/body new file mode 100644 index 0000000..ff42ab3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/body @@ -0,0 +1,8 @@ +The FSF offices are no longer at Temple Place, and there is a revised +text of the GPLv2 giving the correct address and other textual +clean-ups. + +The COPYING file should be updated to the new GPLv2 text, and the +copyright notices throughout the working tree should be updated for +the new boilerplate how-to-use-the-GPL text. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/values new file mode 100644 index 0000000..02ee559 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/values @@ -0,0 +1,8 @@ +Author: benf + + +Content-type: text/plain + + +Date: Fri, 18 Apr 2008 11:21:03 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/body new file mode 100644 index 0000000..19b1cf5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/body @@ -0,0 +1 @@ +We could add this functionality to update_copyright.sh diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/values new file mode 100644 index 0000000..beec197 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 11 Jul 2009 14:08:45 +0000 + + +In-reply-to: 4be73baf-e46b-4acb-a58e-4719e57c550b + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/00f26f04-9202-4288-8744-b29abc2342d6/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/00f26f04-9202-4288-8744-b29abc2342d6/values new file mode 100644 index 0000000..2094f46 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/00f26f04-9202-4288-8744-b29abc2342d6/values @@ -0,0 +1,17 @@ +assigned: Ben Finney <ben+python@benfinney.id.au> + + +creator: benf + + +severity: minor + + +status: fixed + + +summary: Address is outdated for FSF offices + + +time: Fri, 18 Apr 2008 11:18:58 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01c9a900-61f9-41f7-9b2f-dd8f89e25b1b/comments/b8e5c376-32a4-42ea-b6b2-adbee069384a/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01c9a900-61f9-41f7-9b2f-dd8f89e25b1b/comments/b8e5c376-32a4-42ea-b6b2-adbee069384a/body new file mode 100644 index 0000000..6af098a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01c9a900-61f9-41f7-9b2f-dd8f89e25b1b/comments/b8e5c376-32a4-42ea-b6b2-adbee069384a/body @@ -0,0 +1,19 @@ +On Wed, Jan 20, 2010 at 01:24:25PM -0500, W. Trevor King wrote: +> Of course, incorperating interactive functionality in command output +> (i.e. changing the bug target from the bug-show page), doesn't fit +> into this model. To do that, we'd have to abstract the default +> command output the way we've already abstracted the commands and their +> input... + +Does anyone know of any output-abstraction implementations to look at +for inspiration. + * How would we handle the options we currently pass through + (shortlist, show_comments, etc.)? + * Would standard arguments know how to display themselves? + class Status (Argument): + def str(self, ui, command, *args, **kwargs): + ui.display_status(self, command, *args, **kwargs) + class Bug (Argument): + def str(self, ui, command, *args, **kwargs): + ui.display_bug(self, command, *args, **kwargs) + ... diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01c9a900-61f9-41f7-9b2f-dd8f89e25b1b/comments/b8e5c376-32a4-42ea-b6b2-adbee069384a/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01c9a900-61f9-41f7-9b2f-dd8f89e25b1b/comments/b8e5c376-32a4-42ea-b6b2-adbee069384a/values new file mode 100644 index 0000000..378cc67 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01c9a900-61f9-41f7-9b2f-dd8f89e25b1b/comments/b8e5c376-32a4-42ea-b6b2-adbee069384a/values @@ -0,0 +1,14 @@ +Alt-id: <20100120183646.GC14791@mjolnir> + + +Author: '"W. Trevor King" <wking@drexel.edu>' + + +Content-type: text/plain + + +Date: Wed, 20 Jan 2010 18:36:46 +0000 + + +In-reply-to: <20100120182425.GB14791@mjolnir> + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01c9a900-61f9-41f7-9b2f-dd8f89e25b1b/comments/f5139012-e20b-4d24-90a5-10d969ddd364/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01c9a900-61f9-41f7-9b2f-dd8f89e25b1b/comments/f5139012-e20b-4d24-90a5-10d969ddd364/body new file mode 100644 index 0000000..636137c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01c9a900-61f9-41f7-9b2f-dd8f89e25b1b/comments/f5139012-e20b-4d24-90a5-10d969ddd364/body @@ -0,0 +1,76 @@ +On Wed, Jan 20, 2010 at 09:34:44AM -0500, W. Trevor King wrote: +> On Sun, Dec 06, 2009 at 04:47:23AM -0500, W. Trevor King wrote: +> > Steve, I've caught my CFBE branch up to my current pre-trunk BE and +> > added dependency links to the bug page, so you should be all set once +> > you get back to CFBE. +> +> And I haven't pulled it up to date with my recent reorganization. As +> far as release tarballs go though, we don't have to port to Bazaar at +> all, we can stuff a recent CFBE snapshot into the BE tarball. How +> do people feel about that? + +Ok, I've got CFBE working with my BE head: + http://www.physics.drexel.edu/~wking/code/hg/cfbe/ +However, I haven't reworked CFBE to take advantage of the new command +structure. + +We'll need to extend libbe.command.base.Argument a bit as we work this +out, but I expect we can auto-generate handlers for various commands +with something along the lines of: + +<snip web.py> + +class CommandHandler (object): + def __init__(self, command): + self.command = command + def __call__(self, *args, **kwargs): + if GET: + template = self.env.get_template('command.html') + return template.render(command=self.command) + else: + try: + ret = libbe.ui.command_line.dispatch( + self.command.ui, self.command, *args, **kwargs) + except libbe.command.UserError, e: + HANDLE ERROR + stdout = self.command.ui.get_stdout() + DISPLAY STDOUT OR REDIRECT... + +class WebInterface (libbe.command.UserInterface): + ... + def add_commands(self): + for command_name in libbe.command.commands(): + Class = libbe.command.get_command_class( + command_name=command_name) + command = Class(ui=self) + self.command_name = cherrypy.expose( + CommandHandler(command)) + +</snip web.py> + +<snip command.html> + +<form id="command-form" action="/command" method="post"> + <fieldset> + {% for option in command.options %} + {{ option_form_html(option) }} + {% endfor %} + {% for argument in command.args %} + {{ argument_form_html(argument) }} + {% endfor %} + </fieldset> +</form> + +{{ command.help() }} + +</snip command.html> + +Of course, incorperating interactive functionality in command output +(i.e. changing the bug target from the bug-show page), doesn't fit +into this model. To do that, we'd have to abstract the default +command output the way we've already abstracted the commands and their +input... This sounds like a lot of work, and it is, but the goal is +that BE adds functionality (new commands, option, etc.), and CFBE, +be-handle-mail, etc. automatically incorperate the new stuff. + +Thoughts? diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01c9a900-61f9-41f7-9b2f-dd8f89e25b1b/comments/f5139012-e20b-4d24-90a5-10d969ddd364/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01c9a900-61f9-41f7-9b2f-dd8f89e25b1b/comments/f5139012-e20b-4d24-90a5-10d969ddd364/values new file mode 100644 index 0000000..fb6ab4e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01c9a900-61f9-41f7-9b2f-dd8f89e25b1b/comments/f5139012-e20b-4d24-90a5-10d969ddd364/values @@ -0,0 +1,14 @@ +Alt-id: <20100120182425.GB14791@mjolnir> + + +Author: '"W. Trevor King" <wking@drexel.edu>' + + +Content-type: text/plain + + +Date: Wed, 20 Jan 2010 18:24:25 +0000 + + +In-reply-to: <20100120143444.GA14451@mjolnir> + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01c9a900-61f9-41f7-9b2f-dd8f89e25b1b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01c9a900-61f9-41f7-9b2f-dd8f89e25b1b/values new file mode 100644 index 0000000..d1b7cbe --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01c9a900-61f9-41f7-9b2f-dd8f89e25b1b/values @@ -0,0 +1,14 @@ +creator: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: open + + +summary: Need command output abstraction for flexible UIs + + +time: Wed, 20 Jan 2010 20:35:12 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01e7151c-6113-4c8f-9fc5-4d594431bd2b/comments/2f9beed6-4008-442a-8d44-a45cb7ce0a36/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01e7151c-6113-4c8f-9fc5-4d594431bd2b/comments/2f9beed6-4008-442a-8d44-a45cb7ce0a36/body new file mode 100644 index 0000000..d20af30 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01e7151c-6113-4c8f-9fc5-4d594431bd2b/comments/2f9beed6-4008-442a-8d44-a45cb7ce0a36/body @@ -0,0 +1,9 @@ +I'm not sure that changing the URLs is a good idea. I'd rather use +.htaccess and mod_rewrite to redirect short URLs to their permanent +long equivalents. Nobody else seems to mind though, so I've merged +Gianluca's solution with a few changes: + * Since we're truncating bug IDs, truncate comment IDs too. + * Use libbe.util.id._truncate to generate the short IDs, so that `be + html` truncation is consistent with general BE truncation. + * Updated cross-linking code to match. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01e7151c-6113-4c8f-9fc5-4d594431bd2b/comments/2f9beed6-4008-442a-8d44-a45cb7ce0a36/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01e7151c-6113-4c8f-9fc5-4d594431bd2b/comments/2f9beed6-4008-442a-8d44-a45cb7ce0a36/values new file mode 100644 index 0000000..c7365f5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01e7151c-6113-4c8f-9fc5-4d594431bd2b/comments/2f9beed6-4008-442a-8d44-a45cb7ce0a36/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 20 Feb 2010 18:10:42 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01e7151c-6113-4c8f-9fc5-4d594431bd2b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01e7151c-6113-4c8f-9fc5-4d594431bd2b/values new file mode 100644 index 0000000..9efe209 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/01e7151c-6113-4c8f-9fc5-4d594431bd2b/values @@ -0,0 +1,17 @@ +creator: Gianluca Montecchi <gian@grys.it> + + +reporter: Gianluca Montecchi <gian@grys.it> + + +severity: minor + + +status: fixed + + +summary: Short the files name used by the be html command + + +time: Tue, 09 Feb 2010 23:03:33 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/02223264-e28a-4720-9f20-1e7a27a7041d/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/02223264-e28a-4720-9f20-1e7a27a7041d/values new file mode 100644 index 0000000..afa7cb1 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/02223264-e28a-4720-9f20-1e7a27a7041d/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: Needs more test cases + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/144c238c-75d1-40f1-82c1-647668bcf2bc/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/144c238c-75d1-40f1-82c1-647668bcf2bc/body new file mode 100644 index 0000000..3b5e0e7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/144c238c-75d1-40f1-82c1-647668bcf2bc/body @@ -0,0 +1 @@ +Merged from bug 597a7386-643f-4559-8dc4-6871924229b6
\ No newline at end of file diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/144c238c-75d1-40f1-82c1-647668bcf2bc/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/144c238c-75d1-40f1-82c1-647668bcf2bc/values new file mode 100644 index 0000000..34b6514 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/144c238c-75d1-40f1-82c1-647668bcf2bc/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 13:35:41 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/body new file mode 100644 index 0000000..21fb43d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/body @@ -0,0 +1 @@ +Add *support*, damnit! diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/values new file mode 100644 index 0000000..66a9f19 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/values @@ -0,0 +1,8 @@ +Author: abentley + + +Content-type: text/rst + + +Date: Thu, 06 Apr 2006 16:47:25 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/eff20807-07f0-444d-8992-f69ab3f526c5/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/eff20807-07f0-444d-8992-f69ab3f526c5/body new file mode 100644 index 0000000..9106d37 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/eff20807-07f0-444d-8992-f69ab3f526c5/body @@ -0,0 +1,7 @@ +This is an *rst* comment.
+Which means newlines don't matter, except when they gang up.
+
+lala
+
+ - Bullet
+ - Bullet
\ No newline at end of file diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values new file mode 100644 index 0000000..3f1fb15 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values @@ -0,0 +1,11 @@ +Author: abentley + + +Content-type: text/restructured + + +Date: Thu, 06 Apr 2006 16:54:57 +0000 + + +In-reply-to: 144c238c-75d1-40f1-82c1-647668bcf2bc + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/values new file mode 100644 index 0000000..bb41d02 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: Support RST + + +time: Thu, 06 Apr 2006 16:45:52 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0ca2d112-b5bb-4df1-8ac0-e46db6cdd442/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0ca2d112-b5bb-4df1-8ac0-e46db6cdd442/values new file mode 100644 index 0000000..e710dd8 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0ca2d112-b5bb-4df1-8ac0-e46db6cdd442/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: Organize list by target, and whether it's assigned to current be id + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/16ba77d3-dfc9-4732-8d08-0e471f400d85/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/16ba77d3-dfc9-4732-8d08-0e471f400d85/body new file mode 100644 index 0000000..595381c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/16ba77d3-dfc9-4732-8d08-0e471f400d85/body @@ -0,0 +1,34 @@ +When I try to do set-root on a git repository, I get: +# be set-root . +Traceback (most recent call last): + File "/usr/local/bin/be", line 55, in <module> + sys.exit(execute(sys.argv[1], sys.argv[2:])) + File "/usr/lib/python2.5/site-packages/libbe/cmdutil.py", line 105, in execute + File "/usr/lib/python2.5/site-packages/becommands/set_root.py", line 57, in execute + File "/usr/lib/python2.5/site-packages/libbe/bugdir.py", line 110, in create_bug_dir + File "/usr/lib/python2.5/site-packages/libbe/bugdir.py", line 70, in set_version + File "/usr/lib/python2.5/site-packages/libbe/git.py", line 51, in set_file_contents + File "/usr/lib/python2.5/site-packages/libbe/git.py", line 38, in add_id + File "/usr/lib/python2.5/site-packages/libbe/git.py", line 33, in invoke_client + File "/usr/lib/python2.5/site-packages/libbe/rcs.py", line 63, in invoke + File "/usr/lib/python2.5/subprocess.py", line 594, in __init__ + errread, errwrite) + File "/usr/lib/python2.5/subprocess.py", line 1147, in _execute_child + raise child_exception +OSError: [Errno 2] No such file or directory: '' + +because the cwd argument for Popen is set to '' (the empty string). + +The following patch fixes the issue: +--- libbe/git.py 2008-06-22 19:52:14.000000000 -0400 ++++ libbe/git.py 2008-06-23 00:53:39.000000000 -0400 +@@ -26,7 +26,7 @@ + return filename + + def invoke_client(*args, **kwargs): +- directory = kwargs['directory'] ++ directory = kwargs['directory'] or None + expect = kwargs.get('expect', (0, 1)) + cl_args = ["git"] + cl_args.extend(args) + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/16ba77d3-dfc9-4732-8d08-0e471f400d85/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/16ba77d3-dfc9-4732-8d08-0e471f400d85/values new file mode 100644 index 0000000..8dc0882 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/16ba77d3-dfc9-4732-8d08-0e471f400d85/values @@ -0,0 +1,8 @@ +Author: hubert + + +Content-type: text/plain + + +Date: Mon, 23 Jun 2008 05:02:22 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/17a2217e-fc1d-4d7a-a569-4fd2a4a2261e/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/17a2217e-fc1d-4d7a-a569-4fd2a4a2261e/body new file mode 100644 index 0000000..49fe1fb --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/17a2217e-fc1d-4d7a-a569-4fd2a4a2261e/body @@ -0,0 +1,29 @@ +It looks like the problems with the git backend are more than just in the +site-init command. It looks like several places expect that git_dir_for_path +and git_repo_for_path return absolute paths, while in the current +implementation, it may not be the case. Here is an updated patch to fix this. +This replaces the previous patch that I gave in this bug. It seems to work for +me, but I haven't heavily tested it. + +--- libbe/git.py 2008-06-22 19:52:14.000000000 -0400 ++++ /libbe/git.py 2008-06-23 22:39:17.000000000 -0400 +@@ -102,11 +102,16 @@ + """Find the root of the deepest repository containing path.""" + # Assume that nothing funny is going on; in particular, that we aren't + # dealing with a bare repo. +- return os.path.dirname(git_dir_for_path(path)) ++ # "git rev-parse --show-cdup" gives the relative path to the top-level ++ # directory of the repository. We then join that to the requested path, ++ # and then use realpath to turn it into an absolute path and to get rid of ++ # ".." components. ++ return os.path.realpath(os.path.join(path,invoke_client("rev-parse", "--show-cdup", directory=path)[1].rstrip())) + + def git_dir_for_path(path): + """Find the git-dir of the deepest repo containing path.""" +- return invoke_client("rev-parse", "--git-dir", directory=path)[1].rstrip() ++ repo = git_repo_for_path(path) ++ return os.path.join(repo,invoke_client("rev-parse", "--git-dir", directory=repo)[1].rstrip()) + + def export(spec, bug_dir, revision_dir): + """Check out commit 'spec' from the git repo containing bug_dir into + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/17a2217e-fc1d-4d7a-a569-4fd2a4a2261e/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/17a2217e-fc1d-4d7a-a569-4fd2a4a2261e/values new file mode 100644 index 0000000..176ae7f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/17a2217e-fc1d-4d7a-a569-4fd2a4a2261e/values @@ -0,0 +1,8 @@ +Author: hubert + + +Content-type: text/plain + + +Date: Tue, 24 Jun 2008 02:45:18 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/202e0dc6-61bf-4b17-a8bd-f8a27482cb68/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/202e0dc6-61bf-4b17-a8bd-f8a27482cb68/body new file mode 100644 index 0000000..ccc18ea --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/202e0dc6-61bf-4b17-a8bd-f8a27482cb68/body @@ -0,0 +1,10 @@ +Fixed another bug in git.strip_git(). lstrip() wasn't what I had thought. + +>>> "/a.b/.be/x/y".lstrip("/a.b/") +'e/x/y' + +So I went back to just droping the first N chars + +>>> "/a.b/.be/x/y"[len("/a.b/"):] +'.be/x/y' + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/202e0dc6-61bf-4b17-a8bd-f8a27482cb68/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/202e0dc6-61bf-4b17-a8bd-f8a27482cb68/values new file mode 100644 index 0000000..9cfd081 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/202e0dc6-61bf-4b17-a8bd-f8a27482cb68/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 16 Nov 2008 20:36:20 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/6a0080c4-d684-4c2c-afaa-c15cc43d68ad/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/6a0080c4-d684-4c2c-afaa-c15cc43d68ad/body new file mode 100644 index 0000000..c889a38 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/6a0080c4-d684-4c2c-afaa-c15cc43d68ad/body @@ -0,0 +1 @@ +Fixed with a simpler patch. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/6a0080c4-d684-4c2c-afaa-c15cc43d68ad/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/6a0080c4-d684-4c2c-afaa-c15cc43d68ad/values new file mode 100644 index 0000000..9e40714 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/6a0080c4-d684-4c2c-afaa-c15cc43d68ad/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 13 Nov 2008 19:31:04 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/7e733393-8ba0-4345-a0e3-4140101d32f0/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/7e733393-8ba0-4345-a0e3-4140101d32f0/body new file mode 100644 index 0000000..7c07a0f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/7e733393-8ba0-4345-a0e3-4140101d32f0/body @@ -0,0 +1,23 @@ +Oops, missed a case. I now see what Hubert was saying about absolute +paths :p. In git.strip_git(), the output of git_repo_for_path('.') +was being subtracted from an absolute path. Obviously, if the path +was returning '.', you'd get things like + +filename= +/home/wking/src/fun/testbe/.be/bugs/c3bf839b-88f9-4609-89a2-6a5b75c415b8/values + +stripping 2 chars ('.' and '/')], returns +ome/wking/src/fun/testbe/.be/bugs/c3bf839b-88f9-4609-89a2-6a5b75c415b8/values + + +Now we convert the git_repo_for_path output to an absolute path and get + +filename= +/home/wking/src/fun/testbe/.be/bugs/c3bf839b-88f9-4609-89a2-6a5b75c415b8/values +absRepoPath= +/home/wking/src/fun/testbe +absRepoSlashedDir= +/home/wking/src/fun/testbe/ +returns +.be/bugs/c3bf839b-88f9-4609-89a2-6a5b75c415b8/values + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/7e733393-8ba0-4345-a0e3-4140101d32f0/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/7e733393-8ba0-4345-a0e3-4140101d32f0/values new file mode 100644 index 0000000..ce0ab73 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/7e733393-8ba0-4345-a0e3-4140101d32f0/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 13 Nov 2008 20:18:02 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/values new file mode 100644 index 0000000..1f20dfb --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/values @@ -0,0 +1,14 @@ +creator: hubert + + +severity: minor + + +status: fixed + + +summary: set-root in git repository fails + + +time: Mon, 23 Jun 2008 04:57:22 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0e0c806c-5443-4839-aa60-9615c8c10853/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0e0c806c-5443-4839-aa60-9615c8c10853/values new file mode 100644 index 0000000..3237861 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/0e0c806c-5443-4839-aa60-9615c8c10853/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: fix up command listings + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/3646e056-a2df-46e5-b877-88608c7cc5af/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/3646e056-a2df-46e5-b877-88608c7cc5af/body new file mode 100644 index 0000000..861fb1d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/3646e056-a2df-46e5-b877-88608c7cc5af/body @@ -0,0 +1,14 @@ +> We also have the unfortunate situation of duplicate UUIDs from the old +> be merge +> implemtation. This means that id-to-path is not a well defined +> mapping with single-uuid ids. That's ok though, we get a bit uglier +> and send the long_user() id into the storage backend instead. While +> not so elegant, this will avoid the need for the cached id/path table. + +The situation is worse than just the old `be merge` effects, because +the existence, children, and parents of a particular UUID may be +revision dependent. A UUID will always refer to the same +bugdir/bug/comment, but that bugdir/bug/comment may have different +relatives. Another point in favor of long_user()-style storage ids, +but that just pushes relation-tracking up to the command level. I'm +still figuring out a good way to deal with this... diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/3646e056-a2df-46e5-b877-88608c7cc5af/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/3646e056-a2df-46e5-b877-88608c7cc5af/values new file mode 100644 index 0000000..65e4472 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/3646e056-a2df-46e5-b877-88608c7cc5af/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 28 Dec 2009 12:12:45 +0000 + + +In-reply-to: bd1207ef-f97e-4078-8c5d-046072012082 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/7812d2e5-9d4b-4621-b071-22e91e8757d2/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/7812d2e5-9d4b-4621-b071-22e91e8757d2/body new file mode 100644 index 0000000..df5b8c5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/7812d2e5-9d4b-4621-b071-22e91e8757d2/body @@ -0,0 +1,14 @@ +> The situation is worse than just the old `be merge` effects, because +> the existence, children, and parents of a particular UUID may be +> revision dependent. A UUID will always refer to the same +> bugdir/bug/comment, but that bugdir/bug/comment may have different +> relatives. + +I'm not sure how to support .children(revision) in the Arch backend +or the older versions of Darcs without checking out a pristine tree +for the revision in question. That's how we used to support + BugDir.duplicate_bugdir() +but it doesn't fit well with the new Storage system. Since I don't +feel strongly about tla or old Darcs support, I'm leaving that +functionality unimplemented. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/7812d2e5-9d4b-4621-b071-22e91e8757d2/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/7812d2e5-9d4b-4621-b071-22e91e8757d2/values new file mode 100644 index 0000000..d21650d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/7812d2e5-9d4b-4621-b071-22e91e8757d2/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 29 Dec 2009 16:20:06 +0000 + + +In-reply-to: 3646e056-a2df-46e5-b877-88608c7cc5af + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/bb406a33-92b6-46dd-950c-c7cfb5440e7b/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/bb406a33-92b6-46dd-950c-c7cfb5440e7b/body new file mode 100644 index 0000000..abb898c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/bb406a33-92b6-46dd-950c-c7cfb5440e7b/body @@ -0,0 +1,30 @@ +Rather than all the hackery that goes on with email-bugs, the email +interface, etc., it would be nice for distribution if be provided a +uniform issue/bug tracking library and a number of interfaces and +backends. + +Current backends: + filesystem (with assorted VCSs) +Current UIs: + command line (be) + email (be-handle-mail) + web (CFBE) + +Future backend architecture: + be --repo REPO ... +where --repo REPO replaces and extends the current --dir DIR. Example +REPOs could be + path/to/repo (the current DIR) + http://some-server.com:port/path/to/repo (http interface) + mysql://user@server:port/?db=db-name;pwd=password + ... +Each repo would have to support a few get/set commands at the bugdir, +bug, and comment level. + +The UIs would all load BugDir(REPO), and thus be backend agnostic. +This way a GUI app that let you work on your own machine could also be +used to work on a public repository. Setting up a public repository +would just consist of exposing one of the wire-capable REPO formats +(e.g. http via a future `be serve MY-URL`) with public write +permissions. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/bb406a33-92b6-46dd-950c-c7cfb5440e7b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/bb406a33-92b6-46dd-950c-c7cfb5440e7b/values new file mode 100644 index 0000000..d2e65d3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/bb406a33-92b6-46dd-950c-c7cfb5440e7b/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 08 Dec 2009 01:06:12 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/bd1207ef-f97e-4078-8c5d-046072012082/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/bd1207ef-f97e-4078-8c5d-046072012082/body new file mode 100644 index 0000000..21170a2 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/bd1207ef-f97e-4078-8c5d-046072012082/body @@ -0,0 +1,45 @@ +Some additional thoughts, as I've been developing this idea: + +Different BE storage versions will be difficult to handle. +We currently do disk upgrades via + libbe.storage.util.upgrade +which browses through the .be/ directory, making appropriate changes. + +The new formats know very little about paths, which brought on the +whole libbe.storage.vcs.base.CachedPathID bit. Still, most VCSs +seem to be able to handle renames, e.g. + $ bzr cat -r 200 ./libbe/command/new.py +works, when as of revision 200, the file was + ./becommands/new.py +In fact, bzr recognizes both names: + $ diff <(bzr cat -r 200 ./becommands/new.py) \ + <(bzr cat -r 200 ./libbe/commands/new.py) +returns nothing. Still, I'm not sure this is something we should +require in a storage backend. Which means we'd need to have a +version-dependent id-to-path(version) function. + +We also have the unfortunate situation of duplicate UUIDs from the old + be merge +implemtation. This means that id-to-path is not a well defined +mapping with single-uuid ids. That's ok though, we get a bit uglier +and send the long_user() id into the storage backend instead. While +not so elegant, this will avoid the need for the cached id/path table. + +Ok, you say, we're fine if we have the compound bugdir/bug/comment ids +going out to storage, with the upgrader upgrading the file +appropriately for each file type. Almost. You'll still run into +trouble with upgrades like dir format v1.2 to 1.3 where targets +moved from a per-bug string to a seperate-bugs-with-dependencies. +Now you need to create virtual-target-bugs on the fly when you're +loading the old bugs. Yuck. + +All of this makes me wonder how much we care about being able to +see bug diffs for any repository format older than the current one. +I think that we don't really care ;). After all, the on-disk +format should settle down as BE matures :p. When you _do_ want +to see the long-term history of a particular bug, there's always + bzr log .be/123/bugs/456/values +or the equivalent for your VCS. If access to the raw log ends +up being important, it should be very easy to add + libbe.storage.base.VersionedStorage.log(id) + libbe.command.log diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/bd1207ef-f97e-4078-8c5d-046072012082/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/bd1207ef-f97e-4078-8c5d-046072012082/values new file mode 100644 index 0000000..f0af48d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/comments/bd1207ef-f97e-4078-8c5d-046072012082/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 15 Dec 2009 12:21:11 +0000 + + +In-reply-to: bb406a33-92b6-46dd-950c-c7cfb5440e7b + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/values new file mode 100644 index 0000000..f4b1032 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/1100c966-9671-4bc6-8b68-6d408a910da1/values @@ -0,0 +1,17 @@ +creator: W. Trevor King <wking@drexel.edu> + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: fixed + + +summary: Reoranize BE for more flexible backend / frontend + + +time: Tue, 08 Dec 2009 00:48:27 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/0f60a148-7024-44bd-bbed-377cbece9d1b/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/0f60a148-7024-44bd-bbed-377cbece9d1b/body new file mode 100644 index 0000000..d3f00e7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/0f60a148-7024-44bd-bbed-377cbece9d1b/body @@ -0,0 +1,25 @@ +Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes: + +> the basic idea is to take a look at all public branches (for exaple +> all on lp/bitbucket/github) in order to tell the user of a +> webinterface that bug foo is fixed in branch xyz, and if its merged to +> the main branch + +I don't understand. The state of the bug in the main branch is right +there in the main branch; if it's not fixed there, it's not fixed there. +If it's merged in from a different branch, the bug state follows all the +other changes when they come in. + +Can you give an example of what would be done differently? + +-- + \ “The basic fact about human existence is not that it is a | + `\ tragedy, but that it is a bore.” —Henry L. Mencken | +_o__) | +Ben Finney + + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/0f60a148-7024-44bd-bbed-377cbece9d1b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/0f60a148-7024-44bd-bbed-377cbece9d1b/values new file mode 100644 index 0000000..4c93931 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/0f60a148-7024-44bd-bbed-377cbece9d1b/values @@ -0,0 +1,14 @@ +Alt-id: <874otjmjhr.fsf@benfinney.id.au> + + +Author: Ben Finney <bignose+hates-spam@benfinney.id.au> + + +Content-type: text/plain + + +Date: Sat, 11 Jul 2009 23:34:08 +1000 + + +In-reply-to: 88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/13012b22-2d02-444c-87c0-8cf0f17137ae/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/13012b22-2d02-444c-87c0-8cf0f17137ae/body new file mode 100644 index 0000000..1f6d84b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/13012b22-2d02-444c-87c0-8cf0f17137ae/body @@ -0,0 +1,28 @@ +On Sat, Jul 11, 2009 at 01:54:54PM +0200, Ronny Pfannschmidt wrote: +> 1. is there any way to aggregate over multiple public branches in order +> to get the complete bug state + +Keeping the bug data with the source helps synchronize bug state and +source code. Bug state in branch A may not apply to branch B. Some +people like to weaken this source-bug linkage by keeping their bugs in +a branch all by themselves (ditz [http://ditz.rubyforge.org/] +currently supports this workflow). It sounds like you want to move +from "bugs with code" to "bugs and code in separate branches". We +don't have an easy way to do that in BE at the moment, since +version-control systems like Git have a single working branch at a +time (I think :p). What VCS are you using as a backend? + +> 2. is there any model for storing bigger files at a central place (for +> some of my bugs i have multi-megabyte tarballs attached) + + be comment ID "See the tarball at http://yourpage/something.tar.gz" +Then to grab the tarball, you'd use: + wget `be show COMMENT-ID | sed -n 's/ *See the tarball at //p'` +to grab it. + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/13012b22-2d02-444c-87c0-8cf0f17137ae/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/13012b22-2d02-444c-87c0-8cf0f17137ae/values new file mode 100644 index 0000000..a73aeeb --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/13012b22-2d02-444c-87c0-8cf0f17137ae/values @@ -0,0 +1,14 @@ +Alt-id: <20090711125030.GA18185@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 11 Jul 2009 08:50:30 -0400 + + +In-reply-to: 88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/1f9f60de-ba37-42bc-a1c0-dc062ef255e1/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/1f9f60de-ba37-42bc-a1c0-dc062ef255e1/body new file mode 100644 index 0000000..bd9e63a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/1f9f60de-ba37-42bc-a1c0-dc062ef255e1/body @@ -0,0 +1,25 @@ +Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes: + +> 1. is there any way to aggregate over multiple public branches in +> order to get the complete bug state + +The bug state is as complete as the source code state. It's exactly as +aggregated as the rest of the source code; the “complete bug state” +would be the integration branch where you merge all the feature branches +and bug-fix branches together. + +If instead you want bugs to *not* be tightly linked with the rest of the +source code state, it seems you don't want a distributed bug tracker +like Bugs Everywhere. + +-- + \ “I cannot conceive that anybody will require multiplications at | + `\ the rate of 40,000 or even 4,000 per hour …” —F. H. Wales, 1936 | +_o__) | +Ben Finney + + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/1f9f60de-ba37-42bc-a1c0-dc062ef255e1/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/1f9f60de-ba37-42bc-a1c0-dc062ef255e1/values new file mode 100644 index 0000000..55621fb --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/1f9f60de-ba37-42bc-a1c0-dc062ef255e1/values @@ -0,0 +1,14 @@ +Alt-id: <878wivmjm1.fsf@benfinney.id.au> + + +Author: Ben Finney <bignose+hates-spam@benfinney.id.au> + + +Content-type: text/plain + + +Date: Sat, 11 Jul 2009 23:31:34 +1000 + + +In-reply-to: 88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/30a8b841-98ae-41b7-9ef2-6af7cffca8da/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/30a8b841-98ae-41b7-9ef2-6af7cffca8da/body new file mode 100644 index 0000000..11f344c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/30a8b841-98ae-41b7-9ef2-6af7cffca8da/body @@ -0,0 +1,93 @@ +On Mon, Jul 13, 2009 at 09:05:34AM +0200, Ronny Pfannschmidt wrote: +> On Sun, 2009-07-12 at 19:55 -0400, W. Trevor King wrote: +> > On Sun, Jul 12, 2009 at 11:20:10PM +0200, Ronny Pfannschmidt wrote: +> > > On Sat, 2009-07-11 at 11:25 -0400, W. Trevor King wrote: +> > > > On Sat, Jul 11, 2009 at 03:13:05PM +0200, Ronny Pfannschmidt wrote: +> > > > > On Sat, 2009-07-11 at 08:50 -0400, W. Trevor King wrote: +> > > > > > On Sat, Jul 11, 2009 at 01:54:54PM +0200, Ronny Pfannschmidt wrote: +> > > > > > > 2. is there any model for storing bigger files at a central place (for +> > > > > > > some of my bugs i have multi-megabyte tarballs attached) +> > > > > > +> > > > > > be comment ID "See the tarball at http://yourpage/something.tar.gz" +> > > > > > Then to grab the tarball, you'd use: +> > > > > > wget `be show COMMENT-ID | sed -n 's/ *See the tarball at //p'` +> > > > > > to grab it. +> > > > > +> > > > > so the basic idea is to do it completely self-managed +> > > > > and have have heterogenous sources of extended data? +> > > > +> > > > I assume "extended data" here refers to your tarballs. What sort of +> > > > homogenous source did you have in mind? The comment body is currently +> > > > just a binary blob for non-text/* types, otherwise it's text in +> > > > whatever encoding you've configured. +> > > +> > > some kind of common upload target for a single project in order to have +> > > more reliable sources of stuff thats related to bugs but doesnt fit into +> > > the normal repository +> > +> > Sorry, I'm still having trouble with "doesn't fit into the normal +> > repository". It's going to be large wherever you keep it. You +> > worried about multiple branches all having these big tarballs in them +> > and want a "lightweight" checkout without all the big +> > tarballs/whatever? I still think having some sort of "resources" +> > directory on an http server somewhere that you link to from comments +> > is the best plan. If you use the +> > be show --xml ID | be-xml-to-mbox | catmutt +> > approach, you can even write your comments in text/html and get +> > clickable links ;). A "push big file to remote and commit comment +> > linking to it" script would be pretty simple and keep everything +> > consistent. +> +> thats probably what i want to do + +#!/bin/bash +REMOTE_DIR="you@webhost:./public_html/bigfiles" +REMOTE_LINK="http://www.webhost.com/bigfiles" +if [ $# -ne 2 ]; then + echo "usage: $0 ID BIGFILE" + exit 1 +fi +ID="$1" +BIGFILE="$2" +be comment "$ID" "Large file stored at ${REMOTE_LINK}/${BIGFILE}" && scp "$BIGFILE" "${REMOTE_DIR}" + +> > > > On Sun, Jul 12, 2009 at 12:57:35AM +1000, Ben Finney wrote: +> > > > > Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes: +> > > > > +> > > > > > i want to see the combination of the bug data of all branches +> > > > > +> > > > > How is a tool to determine the set of “all branches”? The distributed +> > > > > VCS model means that set is indeterminate. +> > > > +> > > > He could just make a list of branches he likes. +> > > > +> > > > Ronny, are you looking to check bug status across several repos on the +> > > > fly, or periodically run something (with cron, etc.) to update a +> > > > static multi-repo summary? +> > > +> > > on the fly access +> > +> > Then listing bugs in a remote repo will either involve httping tons of +> > tiny values files for each bug (slow?) or running some hypothetical +> > BE-server locally for each repo speaking some BE-specific protocol +> > (complicated?). And how would you handle e.g. headless git repos, +> > where nothing's even checked out? +> > +> > You could always run the cron job every 15 minutes, and rely on +> > whatever VCS you're using having some intelligent protocol/procedure +> > to keep bandwidth down ;). If you need faster / more-efficient +> > updates, you'll probably need to throw out polling altogether and +> > setup all involved repos with a "push to central-repo on commit" hook +> > or some such. +> +> its intended to run on the place where i publish the repositories anyway + +Oh, you mean all the repos you want to cover are all _already_ on the +same host? + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/30a8b841-98ae-41b7-9ef2-6af7cffca8da/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/30a8b841-98ae-41b7-9ef2-6af7cffca8da/values new file mode 100644 index 0000000..bb2305f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/30a8b841-98ae-41b7-9ef2-6af7cffca8da/values @@ -0,0 +1,14 @@ +Alt-id: <20090713104715.GA13723@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 13 Jul 2009 06:47:15 -0400 + + +In-reply-to: 6dcc910a-ce15-4eeb-b49b-4747719748ed + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/46937fd4-b0bc-4eed-8033-d699445441ea/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/46937fd4-b0bc-4eed-8033-d699445441ea/body new file mode 100644 index 0000000..cf3c990 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/46937fd4-b0bc-4eed-8033-d699445441ea/body @@ -0,0 +1,32 @@ +On Sat, Jul 11, 2009 at 11:25:07AM -0400, W. Trevor King wrote: +> The easiest implementation I can think of would be to keep local +> branches (on whatever computer is hosting your web interface) +> following your favorite repos. +> proxectX/ +> |-- repoA +> |-- repoB +> `-- repoC +> You'd pull upstream changes with a cron job. +> Listing bugs would be something along the lines of +> projectX$ for repo in * +> do +> pushd $repo +> be list +> popd +> done | sort | uniq +> ... + +I've reworked option handling for be, so my branch now supports + projectX$ for repo in * + do + be --dir $repo list + done | sort | uniq +etc. This also makes it easy to use your uninstalled development +version of be on any bug directory on your local machine. + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/46937fd4-b0bc-4eed-8033-d699445441ea/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/46937fd4-b0bc-4eed-8033-d699445441ea/values new file mode 100644 index 0000000..60c80a1 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/46937fd4-b0bc-4eed-8033-d699445441ea/values @@ -0,0 +1,14 @@ +Alt-id: <20090713115734.GA13788@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 13 Jul 2009 07:57:34 -0400 + + +In-reply-to: bd98f525-95ec-446a-84e8-34c7d6fa5b40 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/4d192c6c-a4a8-4844-b083-2dd5926bd2d9/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/4d192c6c-a4a8-4844-b083-2dd5926bd2d9/body new file mode 100644 index 0000000..c22de06 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/4d192c6c-a4a8-4844-b083-2dd5926bd2d9/body @@ -0,0 +1,73 @@ +On Sun, Jul 12, 2009 at 11:20:10PM +0200, Ronny Pfannschmidt wrote: +> On Sat, 2009-07-11 at 11:25 -0400, W. Trevor King wrote: +> > On Sat, Jul 11, 2009 at 03:13:05PM +0200, Ronny Pfannschmidt wrote: +> > > On Sat, 2009-07-11 at 08:50 -0400, W. Trevor King wrote: +> > > > On Sat, Jul 11, 2009 at 01:54:54PM +0200, Ronny Pfannschmidt wrote: +> > > > > 2. is there any model for storing bigger files at a central place (for +> > > > > some of my bugs i have multi-megabyte tarballs attached) +> > > > +> > > > be comment ID "See the tarball at http://yourpage/something.tar.gz" +> > > > Then to grab the tarball, you'd use: +> > > > wget `be show COMMENT-ID | sed -n 's/ *See the tarball at //p'` +> > > > to grab it. +> > > +> > > so the basic idea is to do it completely self-managed +> > > and have have heterogenous sources of extended data? +> > +> > I assume "extended data" here refers to your tarballs. What sort of +> > homogenous source did you have in mind? The comment body is currently +> > just a binary blob for non-text/* types, otherwise it's text in +> > whatever encoding you've configured. +> +> some kind of common upload target for a single project in order to have +> more reliable sources of stuff thats related to bugs but doesnt fit into +> the normal repository + +Sorry, I'm still having trouble with "doesn't fit into the normal +repository". It's going to be large wherever you keep it. You +worried about multiple branches all having these big tarballs in them +and want a "lightweight" checkout without all the big +tarballs/whatever? I still think having some sort of "resources" +directory on an http server somewhere that you link to from comments +is the best plan. If you use the + be show --xml ID | be-xml-to-mbox | catmutt +approach, you can even write your comments in text/html and get +clickable links ;). A "push big file to remote and commit comment +linking to it" script would be pretty simple and keep everything +consistent. + +> > On Sun, Jul 12, 2009 at 12:57:35AM +1000, Ben Finney wrote: +> > > Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes: +> > > +> > > > i want to see the combination of the bug data of all branches +> > > +> > > How is a tool to determine the set of “all branches”? The distributed +> > > VCS model means that set is indeterminate. +> > +> > He could just make a list of branches he likes. +> > +> > Ronny, are you looking to check bug status across several repos on the +> > fly, or periodically run something (with cron, etc.) to update a +> > static multi-repo summary? +> +> on the fly access + +Then listing bugs in a remote repo will either involve httping tons of +tiny values files for each bug (slow?) or running some hypothetical +BE-server locally for each repo speaking some BE-specific protocol +(complicated?). And how would you handle e.g. headless git repos, +where nothing's even checked out? + +You could always run the cron job every 15 minutes, and rely on +whatever VCS you're using having some intelligent protocol/procedure +to keep bandwidth down ;). If you need faster / more-efficient +updates, you'll probably need to throw out polling altogether and +setup all involved repos with a "push to central-repo on commit" hook +or some such. + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/4d192c6c-a4a8-4844-b083-2dd5926bd2d9/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/4d192c6c-a4a8-4844-b083-2dd5926bd2d9/values new file mode 100644 index 0000000..b5ebf31 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/4d192c6c-a4a8-4844-b083-2dd5926bd2d9/values @@ -0,0 +1,14 @@ +Alt-id: <20090712235502.GA10782@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 12 Jul 2009 19:55:02 -0400 + + +In-reply-to: 8ffc90d7-0be7-4b00-88e6-9ae1b65f7957 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/624a4542-92e9-442e-b71c-a14da4fe55cf/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/624a4542-92e9-442e-b71c-a14da4fe55cf/body new file mode 100644 index 0000000..e7b48e0 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/624a4542-92e9-442e-b71c-a14da4fe55cf/body @@ -0,0 +1,83 @@ +I read + http://weblog.masukomi.org/2008/1/3/distributed-bug-tracking +yesterday, and the section on bug visibility got me thinking about +bug 12c (Multi-repo meta-BE?) some more. + +We already have interfaces like this email/html mashup: + +On Sun, Sep 13, 2009 at 07:04:05AM -0400, W. Trevor King wrote: +> Since the non-bzr interfaces to BE are coming along nicely, I've put +> up a non-bzr interface to my be-rr branch. +> http://www.physics.drexel.edu/~wking/code/be +> It uses nightly builds of Gianluca's static html from my devel branch +> to provide read-only browsing, and accepts changes from the general +> public through my email interface into a public branch. I handle the +> synchronization of these two branches manually. + +These interfaces provide a means for remote users to access a BE +repository without bzr or the command line. As far as users are +concerned, this exposed repository looks pretty much like a +centralized bugtracking system (e.g. bugzilla, ...). + +However, with BE we have more bug information living off in other +branches that haven't yet been merged with the exposed repo. The +problem is two-fold: + 1) how to keep up to date within a distributed community. + 2) how do users find branches/patches that fix bug XYZ. + +For (2), I think the best solution at the moment are along the lines +of my little scripts (discussed in the bug 12c comments). With the +addition of the `be diff --dir DIR` option, it's now even easier to +find more information on bug 565 (or whatever UUID): + be/be.wtk$ for repo in ../*; do \ + if [ $repo == "be.wtk" ]; then continue; fi; \ + diff=$(be diff --dir $repo --subscribe 565:all); \ + if [ -n "$diff" ]; then \ + echo "Changed from $repo:"; echo "$diff"; \ + fi; \ + done + Changed from ../be.html: + New bugs: + 565:fm: be email-bugs for bug submission from bzr-less users + Changed from ../be.trunk: + New bugs: + 565:fm: be email-bugs for bug submission from bzr-less users + Changed from ../cherryflavoredbugseverywhere: + New bugs: + 565:fm: be email-bugs for bug submission from bzr-less users +where the --dir and --subscribe options to `be diff` are new. If +people don't like the command line, this would be easy to bundle into +a web-frontend (CFBE?) if you wanted, with a cron job pulling updates +into the tracked branches. + +I was starting into a solution for (1) when I did this: + +On Mon, Jul 27, 2009 at 08:42:19AM -0400, W. Trevor King wrote: +> My email interface now supports subscription: +> be subscribe DIR # see any changes to the bug directory. +> be subscribe BUG-ID # see changes to a particular bug. +> See +> be subscribe --help +> for more details. + +The idea was that a dev/user would subscribe to whatever issues they +wanted to track, and they would get email notifications whenever some +action affected any of those issues. These subscriptions would +percolate through the distributed branches as a result of the usual +mergers. For example, my subscription to all changes has made it into +the trunk branch (see .be/settings). + +This subscription mechanism was setup to work through interactive +public interfaces (my email interface, eventually CFBE, ...), but +it doesn't work for changes made via the command-line interface, +so I browsed around a bit and ran across some interesting workflows +in the bzr documentation + doc/developers/HACKING.txt, "Communicating and Coordinating" +which points out the following plugins + * email (http://doc.bazaar-vcs.org/plugins/en/email-plugin.html) + * dbus (http://doc.bazaar-vcs.org/plugins/en/dbus-plugin.html) +which send automatic notification messages after commits, etc. If +people want this sort of functionality, it would be easy enough to rig +a hook for `be commit' that sent a diff email to subscribers, which +could include be-devel. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/624a4542-92e9-442e-b71c-a14da4fe55cf/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/624a4542-92e9-442e-b71c-a14da4fe55cf/values new file mode 100644 index 0000000..adb1ae5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/624a4542-92e9-442e-b71c-a14da4fe55cf/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 05 Dec 2009 22:39:07 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/6dcc910a-ce15-4eeb-b49b-4747719748ed/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/6dcc910a-ce15-4eeb-b49b-4747719748ed/body new file mode 100644 index 0000000..6b7d3eb --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/6dcc910a-ce15-4eeb-b49b-4747719748ed/body @@ -0,0 +1,70 @@ +On Sun, 2009-07-12 at 19:55 -0400, W. Trevor King wrote: +> On Sun, Jul 12, 2009 at 11:20:10PM +0200, Ronny Pfannschmidt wrote: +> > On Sat, 2009-07-11 at 11:25 -0400, W. Trevor King wrote: +> > > On Sat, Jul 11, 2009 at 03:13:05PM +0200, Ronny Pfannschmidt wrote: +> > > > On Sat, 2009-07-11 at 08:50 -0400, W. Trevor King wrote: +> > > > > On Sat, Jul 11, 2009 at 01:54:54PM +0200, Ronny Pfannschmidt wrote: +> > > > > > 2. is there any model for storing bigger files at a central place (for +> > > > > > some of my bugs i have multi-megabyte tarballs attached) +> > > > > +> > > > > be comment ID "See the tarball at http://yourpage/something.tar.gz" +> > > > > Then to grab the tarball, you'd use: +> > > > > wget `be show COMMENT-ID | sed -n 's/ *See the tarball at //p'` +> > > > > to grab it. +> > > > +> > > > so the basic idea is to do it completely self-managed +> > > > and have have heterogenous sources of extended data? +> > > +> > > I assume "extended data" here refers to your tarballs. What sort of +> > > homogenous source did you have in mind? The comment body is currently +> > > just a binary blob for non-text/* types, otherwise it's text in +> > > whatever encoding you've configured. +> > +> > some kind of common upload target for a single project in order to have +> > more reliable sources of stuff thats related to bugs but doesnt fit into +> > the normal repository +> +> Sorry, I'm still having trouble with "doesn't fit into the normal +> repository". It's going to be large wherever you keep it. You +> worried about multiple branches all having these big tarballs in them +> and want a "lightweight" checkout without all the big +> tarballs/whatever? I still think having some sort of "resources" +> directory on an http server somewhere that you link to from comments +> is the best plan. If you use the +> be show --xml ID | be-xml-to-mbox | catmutt +> approach, you can even write your comments in text/html and get +> clickable links ;). A "push big file to remote and commit comment +> linking to it" script would be pretty simple and keep everything +> consistent. +thats probably what i want to do + +> +> > > On Sun, Jul 12, 2009 at 12:57:35AM +1000, Ben Finney wrote: +> > > > Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes: +> > > > +> > > > > i want to see the combination of the bug data of all branches +> > > > +> > > > How is a tool to determine the set of “all branches”? The distributed +> > > > VCS model means that set is indeterminate. +> > > +> > > He could just make a list of branches he likes. +> > > +> > > Ronny, are you looking to check bug status across several repos on the +> > > fly, or periodically run something (with cron, etc.) to update a +> > > static multi-repo summary? +> > +> > on the fly access +> +> Then listing bugs in a remote repo will either involve httping tons of +> tiny values files for each bug (slow?) or running some hypothetical +> BE-server locally for each repo speaking some BE-specific protocol +> (complicated?). And how would you handle e.g. headless git repos, +> where nothing's even checked out? +> +> You could always run the cron job every 15 minutes, and rely on +> whatever VCS you're using having some intelligent protocol/procedure +> to keep bandwidth down ;). If you need faster / more-efficient +> updates, you'll probably need to throw out polling altogether and +> setup all involved repos with a "push to central-repo on commit" hook +> or some such. +its intended to run on the place where i publish the repositories anyway diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/6dcc910a-ce15-4eeb-b49b-4747719748ed/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/6dcc910a-ce15-4eeb-b49b-4747719748ed/values new file mode 100644 index 0000000..bbeacb6 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/6dcc910a-ce15-4eeb-b49b-4747719748ed/values @@ -0,0 +1,14 @@ +Alt-id: <1247468734.7189.1.camel@localhost> + + +Author: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> + + +Content-type: text/plain + + +Date: Mon, 13 Jul 2009 09:05:34 +0200 + + +In-reply-to: 4d192c6c-a4a8-4844-b083-2dd5926bd2d9 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7/body new file mode 100644 index 0000000..2f2c16e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7/body @@ -0,0 +1,15 @@ +Hi, + +1. is there any way to aggregate over multiple public branches in order +to get the complete bug state + +2. is there any model for storing bigger files at a central place (for +some of my bugs i have multi-megabyte tarballs attached) + +Regards Ronny + + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7/values new file mode 100644 index 0000000..a9cd364 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7/values @@ -0,0 +1,11 @@ +Alt-id: <1247313294.7701.60.camel@localhost> + + +Author: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> + + +Content-type: text/plain + + +Date: Sat, 11 Jul 2009 13:54:54 +0200 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/8ffc90d7-0be7-4b00-88e6-9ae1b65f7957/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/8ffc90d7-0be7-4b00-88e6-9ae1b65f7957/body new file mode 100644 index 0000000..debd486 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/8ffc90d7-0be7-4b00-88e6-9ae1b65f7957/body @@ -0,0 +1,95 @@ +On Sat, 2009-07-11 at 11:25 -0400, W. Trevor King wrote: +> On Sat, Jul 11, 2009 at 03:13:05PM +0200, Ronny Pfannschmidt wrote: +> > On Sat, 2009-07-11 at 08:50 -0400, W. Trevor King wrote: +> > > On Sat, Jul 11, 2009 at 01:54:54PM +0200, Ronny Pfannschmidt wrote: +> > > > 1. is there any way to aggregate over multiple public branches in order +> > > > to get the complete bug state +> > > +> > > Keeping the bug data with the source helps synchronize bug state and +> > > source code. Bug state in branch A may not apply to branch B. Some +> > > people like to weaken this source-bug linkage by keeping their bugs in +> > > a branch all by themselves (ditz [http://ditz.rubyforge.org/] +> > > currently supports this workflow). It sounds like you want to move +> > > from "bugs with code" to "bugs and code in separate branches". We +> > > don't have an easy way to do that in BE at the moment, since +> > > version-control systems like Git have a single working branch at a +> > > time (I think :p). What VCS are you using as a backend? +> > +> > the basic idea is to take a look at all public branches (for exaple all +> > on lp/bitbucket/github) in order to tell the user of a webinterface that +> > bug foo is fixed in branch xyz, and if its merged to the main branch +> +> Hmm. +> +> > > > 2. is there any model for storing bigger files at a central place (for +> > > > some of my bugs i have multi-megabyte tarballs attached) +> > > +> > > be comment ID "See the tarball at http://yourpage/something.tar.gz" +> > > Then to grab the tarball, you'd use: +> > > wget `be show COMMENT-ID | sed -n 's/ *See the tarball at //p'` +> > > to grab it. +> > so the basic idea is to do it completely self-managed +> +> Well, it's going to be managed by somebody ;). So far I'm not +> convinced enough for the manager to be me, so I'm suggesting it be you +> :p. +> +> > and have have heterogenous sources of extended data? +> +> I assume "extended data" here refers to your tarballs. What sort of +> homogenous source did you have in mind? The comment body is currently +> just a binary blob for non-text/* types, otherwise it's text in +> whatever encoding you've configured. +some kind of common upload target for a single project in order to have +more reliable sources of stuff thats related to bugs but doesnt fit into +the normal repository + + +> +> On Sun, Jul 12, 2009 at 12:57:35AM +1000, Ben Finney wrote: +> > Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes: +> > +> > > i want to see the combination of the bug data of all branches +> > +> > How is a tool to determine the set of “all branches”? The distributed +> > VCS model means that set is indeterminate. +> +> He could just make a list of branches he likes. +> +> Ronny, are you looking to check bug status across several repos on the +> fly, or periodically run something (with cron, etc.) to update a +> static multi-repo summary? +on the fly access + +> +> The easiest implementation I can think of would be to keep local +> branches (on whatever computer is hosting your web interface) +> following your favorite repos. +> proxectX/ +> |-- repoA +> |-- repoB +> `-- repoC +> You'd pull upstream changes with a cron job. +> Listing bugs would be something along the lines of +> projectX$ for repo in * +> do +> pushd $repo +> be list +> popd +> done | sort | uniq +> Then to show bug status you would have something like +> projectX$ for repo in * +> do +> echo $repo +> pushd $repo +> be show ${BUGID} +> popd +> done +> For a web frontend, you'd want to translate that to python/libbe. +> + +yes, the idea is to get a web fontend for multiple branches +and maybe a local gtk fontend for local multi-branch setups + +for some of my projects i have n local and m remote repos, and merging +is not always intended soonish diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/8ffc90d7-0be7-4b00-88e6-9ae1b65f7957/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/8ffc90d7-0be7-4b00-88e6-9ae1b65f7957/values new file mode 100644 index 0000000..5a64ee0 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/8ffc90d7-0be7-4b00-88e6-9ae1b65f7957/values @@ -0,0 +1,14 @@ +Alt-id: <1247433610.14803.3.camel@localhost> + + +Author: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> + + +Content-type: text/plain + + +Date: Sun, 12 Jul 2009 23:20:10 +0200 + + +In-reply-to: bd98f525-95ec-446a-84e8-34c7d6fa5b40 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/bd98f525-95ec-446a-84e8-34c7d6fa5b40/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/bd98f525-95ec-446a-84e8-34c7d6fa5b40/body new file mode 100644 index 0000000..5f55127 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/bd98f525-95ec-446a-84e8-34c7d6fa5b40/body @@ -0,0 +1,87 @@ +On Sat, Jul 11, 2009 at 03:13:05PM +0200, Ronny Pfannschmidt wrote: +> On Sat, 2009-07-11 at 08:50 -0400, W. Trevor King wrote: +> > On Sat, Jul 11, 2009 at 01:54:54PM +0200, Ronny Pfannschmidt wrote: +> > > 1. is there any way to aggregate over multiple public branches in order +> > > to get the complete bug state +> > +> > Keeping the bug data with the source helps synchronize bug state and +> > source code. Bug state in branch A may not apply to branch B. Some +> > people like to weaken this source-bug linkage by keeping their bugs in +> > a branch all by themselves (ditz [http://ditz.rubyforge.org/] +> > currently supports this workflow). It sounds like you want to move +> > from "bugs with code" to "bugs and code in separate branches". We +> > don't have an easy way to do that in BE at the moment, since +> > version-control systems like Git have a single working branch at a +> > time (I think :p). What VCS are you using as a backend? +> +> the basic idea is to take a look at all public branches (for exaple all +> on lp/bitbucket/github) in order to tell the user of a webinterface that +> bug foo is fixed in branch xyz, and if its merged to the main branch + +Hmm. + +> > > 2. is there any model for storing bigger files at a central place (for +> > > some of my bugs i have multi-megabyte tarballs attached) +> > +> > be comment ID "See the tarball at http://yourpage/something.tar.gz" +> > Then to grab the tarball, you'd use: +> > wget `be show COMMENT-ID | sed -n 's/ *See the tarball at //p'` +> > to grab it. +> so the basic idea is to do it completely self-managed + +Well, it's going to be managed by somebody ;). So far I'm not +convinced enough for the manager to be me, so I'm suggesting it be you +:p. + +> and have have heterogenous sources of extended data? + +I assume "extended data" here refers to your tarballs. What sort of +homogenous source did you have in mind? The comment body is currently +just a binary blob for non-text/* types, otherwise it's text in +whatever encoding you've configured. + +On Sun, Jul 12, 2009 at 12:57:35AM +1000, Ben Finney wrote: +> Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes: +> +> > i want to see the combination of the bug data of all branches +> +> How is a tool to determine the set of “all branches”? The distributed +> VCS model means that set is indeterminate. + +He could just make a list of branches he likes. + +Ronny, are you looking to check bug status across several repos on the +fly, or periodically run something (with cron, etc.) to update a +static multi-repo summary? + +The easiest implementation I can think of would be to keep local +branches (on whatever computer is hosting your web interface) +following your favorite repos. + proxectX/ + |-- repoA + |-- repoB + `-- repoC +You'd pull upstream changes with a cron job. +Listing bugs would be something along the lines of + projectX$ for repo in * + do + pushd $repo + be list + popd + done | sort | uniq +Then to show bug status you would have something like + projectX$ for repo in * + do + echo $repo + pushd $repo + be show ${BUGID} + popd + done +For a web frontend, you'd want to translate that to python/libbe. + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/bd98f525-95ec-446a-84e8-34c7d6fa5b40/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/bd98f525-95ec-446a-84e8-34c7d6fa5b40/values new file mode 100644 index 0000000..dbdb347 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/bd98f525-95ec-446a-84e8-34c7d6fa5b40/values @@ -0,0 +1,14 @@ +Alt-id: <20090711152507.GA18461@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 11 Jul 2009 11:25:07 -0400 + + +In-reply-to: e520239c-8d69-4ff6-b1bd-0c2f74366200 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/c8283e08-967c-4a7b-b953-3ec62c83fb9f/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/c8283e08-967c-4a7b-b953-3ec62c83fb9f/body new file mode 100644 index 0000000..cc3cff3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/c8283e08-967c-4a7b-b953-3ec62c83fb9f/body @@ -0,0 +1,37 @@ +On Sun, Jul 12, 2009 at 12:57:35AM +1000, Ben Finney wrote: +> Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes: +> +> > i want to see the combination of the bug data of all branches +> +> What is your definition of ???all branches???? When I'm working with +> distributed VCS, I create branches wherever I feel like, and the VCS +> tool doesn't have some central registry of branches to keep up to date. +> +> How is a tool to determine the set of ???all branches???? The distributed +> VCS model means that set is indeterminate. + +In the first main Ronny spoke about "public" branches. To me it means that +if a branch is public, he should like to have a status of that branch. + +We all agree (probably ;-) ) that tha main branch is the "right" branch, but +as I see it, Ronny's question has some logic. +I'd like to know that a certain bug is fixed in a certain branch, also if it +is still not merged in the main branch, for various reason (ie I am interested +in the solution since the bug stop my work) + +Imagine it like a rss feed aggregator: in one place there are all the bugs of +all the branches that the developers make avaible to the public with +a repository. This can make easier the life to who want to try a something +since he know what branch he must check out, instead of checking all the +branch he can find to test if he get what is looking for. + +Unluckyly I have no idea how to solve it. :-( + +bye +Gianluca + + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/c8283e08-967c-4a7b-b953-3ec62c83fb9f/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/c8283e08-967c-4a7b-b953-3ec62c83fb9f/values new file mode 100644 index 0000000..28fe7dc --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/c8283e08-967c-4a7b-b953-3ec62c83fb9f/values @@ -0,0 +1,14 @@ +Alt-id: <20090713085859.GA21800@grys.it> + + +Author: Gianluca Montecchi <gian@grys.it> + + +Content-type: text/plain + + +Date: Mon, 13 Jul 2009 10:58:59 +0200 + + +In-reply-to: e520239c-8d69-4ff6-b1bd-0c2f74366200 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/d86e497d-667d-4c2b-9249-76026df56633/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/d86e497d-667d-4c2b-9249-76026df56633/body new file mode 100644 index 0000000..93f082b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/d86e497d-667d-4c2b-9249-76026df56633/body @@ -0,0 +1,33 @@ +On Sat, 2009-07-11 at 23:34 +1000, Ben Finney wrote: +> Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes: +> +> > the basic idea is to take a look at all public branches (for exaple +> > all on lp/bitbucket/github) in order to tell the user of a +> > webinterface that bug foo is fixed in branch xyz, and if its merged to +> > the main branch +> +> I don't understand. The state of the bug in the main branch is right +> there in the main branch; if it's not fixed there, it's not fixed there. +> If it's merged in from a different branch, the bug state follows all the +> other changes when they come in. +> +> Can you give an example of what would be done differently? +> +i want to see the combination of the bug data of all branches + +for example + +i got bug +its fixed in the branch "something" +its not fixed/merged to "main" + +now something like a website should tell me, this bug has been fixed in +branch xyz and the fix is not yet merged into main + + + + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/d86e497d-667d-4c2b-9249-76026df56633/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/d86e497d-667d-4c2b-9249-76026df56633/values new file mode 100644 index 0000000..a79837f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/d86e497d-667d-4c2b-9249-76026df56633/values @@ -0,0 +1,14 @@ +Alt-id: <1247320857.7701.67.camel@localhost> + + +Author: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> + + +Content-type: text/plain + + +Date: Sat, 11 Jul 2009 16:00:57 +0200 + + +In-reply-to: 0f60a148-7024-44bd-bbed-377cbece9d1b + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/dc32aa62-cf56-4171-84a1-8f7d02b23b6d/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/dc32aa62-cf56-4171-84a1-8f7d02b23b6d/body new file mode 100644 index 0000000..3b417be --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/dc32aa62-cf56-4171-84a1-8f7d02b23b6d/body @@ -0,0 +1,27 @@ +On Sat, 2009-07-11 at 08:50 -0400, W. Trevor King wrote: +> On Sat, Jul 11, 2009 at 01:54:54PM +0200, Ronny Pfannschmidt wrote: +> > 1. is there any way to aggregate over multiple public branches in order +> > to get the complete bug state +> +> Keeping the bug data with the source helps synchronize bug state and +> source code. Bug state in branch A may not apply to branch B. Some +> people like to weaken this source-bug linkage by keeping their bugs in +> a branch all by themselves (ditz [http://ditz.rubyforge.org/] +> currently supports this workflow). It sounds like you want to move +> from "bugs with code" to "bugs and code in separate branches". We +> don't have an easy way to do that in BE at the moment, since +> version-control systems like Git have a single working branch at a +> time (I think :p). What VCS are you using as a backend? +the basic idea is to take a look at all public branches (for exaple all +on lp/bitbucket/github) in order to tell the user of a webinterface that +bug foo is fixed in branch xyz, and if its merged to the main branch +> +> > 2. is there any model for storing bigger files at a central place (for +> > some of my bugs i have multi-megabyte tarballs attached) +> +> be comment ID "See the tarball at http://yourpage/something.tar.gz" +> Then to grab the tarball, you'd use: +> wget `be show COMMENT-ID | sed -n 's/ *See the tarball at //p'` +> to grab it. +so the basic idea is to do it completely self-managed and have have +heterogenous sources of extended data? diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/dc32aa62-cf56-4171-84a1-8f7d02b23b6d/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/dc32aa62-cf56-4171-84a1-8f7d02b23b6d/values new file mode 100644 index 0000000..00fe043 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/dc32aa62-cf56-4171-84a1-8f7d02b23b6d/values @@ -0,0 +1,14 @@ +Alt-id: <1247317985.7701.63.camel@localhost> + + +Author: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> + + +Content-type: text/plain + + +Date: Sat, 11 Jul 2009 15:13:05 +0200 + + +In-reply-to: 13012b22-2d02-444c-87c0-8cf0f17137ae + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/e520239c-8d69-4ff6-b1bd-0c2f74366200/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/e520239c-8d69-4ff6-b1bd-0c2f74366200/body new file mode 100644 index 0000000..0263fbb --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/e520239c-8d69-4ff6-b1bd-0c2f74366200/body @@ -0,0 +1,22 @@ +Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes: + +> i want to see the combination of the bug data of all branches + +What is your definition of “all branches”? When I'm working with +distributed VCS, I create branches wherever I feel like, and the VCS +tool doesn't have some central registry of branches to keep up to date. + +How is a tool to determine the set of “all branches”? The distributed +VCS model means that set is indeterminate. + +-- + \ “Pinky, are you pondering what I'm pondering?” “I think so, | + `\ Brain, but I find scratching just makes it worse.” —_Pinky and | +_o__) The Brain_ | +Ben Finney + + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/e520239c-8d69-4ff6-b1bd-0c2f74366200/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/e520239c-8d69-4ff6-b1bd-0c2f74366200/values new file mode 100644 index 0000000..2adef07 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/e520239c-8d69-4ff6-b1bd-0c2f74366200/values @@ -0,0 +1,14 @@ +Alt-id: <87zlbbl128.fsf@benfinney.id.au> + + +Author: Ben Finney <bignose+hates-spam@benfinney.id.au> + + +Content-type: text/plain + + +Date: Sun, 12 Jul 2009 00:57:35 +1000 + + +In-reply-to: 88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/fd6162f3-7fc1-41d1-a073-a07465802b72/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/fd6162f3-7fc1-41d1-a073-a07465802b72/body new file mode 100644 index 0000000..9fb10bc --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/fd6162f3-7fc1-41d1-a073-a07465802b72/body @@ -0,0 +1,26 @@ +On Sat, Jul 11, 2009 at 11:31:34PM +1000, Ben Finney wrote: +> Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes: +> +> > 1. is there any way to aggregate over multiple public branches in +> > order to get the complete bug state +> +> The bug state is as complete as the source code state. It's exactly as +> aggregated as the rest of the source code; the ???complete bug state??? +> would be the integration branch where you merge all the feature branches +> and bug-fix branches together. +> +> If instead you want bugs to *not* be tightly linked with the rest of the +> source code state, it seems you don't want a distributed bug tracker +> like Bugs Everywhere. + +"the complete bug state" probably means that he want to know (and in some way +to publish it) that the bug "xyz" is fixed and merged in main while bug "abc" +is fixed but only in branch "123" and bug "def" is still open in branch "456" + +bye +Gianluca + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/fd6162f3-7fc1-41d1-a073-a07465802b72/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/fd6162f3-7fc1-41d1-a073-a07465802b72/values new file mode 100644 index 0000000..fc2560e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/fd6162f3-7fc1-41d1-a073-a07465802b72/values @@ -0,0 +1,14 @@ +Alt-id: <20090713090341.GB21800@grys.it> + + +Author: Gianluca Montecchi <gian@grys.it> + + +Content-type: text/plain + + +Date: Mon, 13 Jul 2009 11:03:41 +0200 + + +In-reply-to: 1f9f60de-ba37-42bc-a1c0-dc062ef255e1 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/values new file mode 100644 index 0000000..2a3c4f3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/values @@ -0,0 +1,17 @@ +creator: W. Trevor King <wking@drexel.edu> + + +reporter: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> + + +severity: wishlist + + +status: unconfirmed + + +summary: Bug aggregation. Multi-repo meta-BE? + + +time: Tue, 21 Jul 2009 18:32:12 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16989098-aa1d-4a08-bff9-80446b4a82c5/comments/85770405-0ead-4044-a3cf-082615ff1b6f/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16989098-aa1d-4a08-bff9-80446b4a82c5/comments/85770405-0ead-4044-a3cf-082615ff1b6f/body new file mode 100644 index 0000000..c49c15c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16989098-aa1d-4a08-bff9-80446b4a82c5/comments/85770405-0ead-4044-a3cf-082615ff1b6f/body @@ -0,0 +1,22 @@ +This is an outgrowth of #bea86499-824e-4e77-b085-2d581fa9ccab/1100c966-9671-4bc6-8b68-6d408a910da1/bd1207ef-f97e-4078-8c5d-046072012082#: +> All of this makes me wonder how much we care about being able to +> see bug diffs for any repository format older than the current one. +> I think that we don't really care ;). After all, the on-disk +> format should settle down as BE matures :p. When you _do_ want +> to see the long-term history of a particular bug, there's always +> bzr log .be/123/bugs/456/values +> or the equivalent for your VCS. If access to the raw log ends +> up being important, it should be very easy to add +> libbe.storage.base.VersionedStorage.log(id) +> libbe.command.log + +Access to the (parsed) logs will be important for pretty-printing +bugdir/bug/comment change logs. Since we do version the bug +repository, users will expect us to be able to list the history for +any particular item (e.g. for "last activity" timestamps, automatic +reminder emails, whatever). While it does not necessarily need to be +able to delve into old storage formats, it does need to get +implemented. It's probably worth encapsulating changes in something +like a list of Diff() objects, although it might be worth linking +along bug lines, etc., like VCS annotation. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16989098-aa1d-4a08-bff9-80446b4a82c5/comments/85770405-0ead-4044-a3cf-082615ff1b6f/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16989098-aa1d-4a08-bff9-80446b4a82c5/comments/85770405-0ead-4044-a3cf-082615ff1b6f/values new file mode 100644 index 0000000..d29604d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16989098-aa1d-4a08-bff9-80446b4a82c5/comments/85770405-0ead-4044-a3cf-082615ff1b6f/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Fri, 29 Jan 2010 01:12:54 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16989098-aa1d-4a08-bff9-80446b4a82c5/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16989098-aa1d-4a08-bff9-80446b4a82c5/values new file mode 100644 index 0000000..6d46f8f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16989098-aa1d-4a08-bff9-80446b4a82c5/values @@ -0,0 +1,17 @@ +creator: W. Trevor King <wking@drexel.edu> + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: wishlist + + +status: open + + +summary: Generating per-bugdir/bug/comment change logs + + +time: Thu, 28 Jan 2010 23:10:48 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16fc9496-cdc2-4c6e-9b9f-b8f483b6dedb/comments/489397bd-b987-4a08-9589-c5b71661ebb7/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16fc9496-cdc2-4c6e-9b9f-b8f483b6dedb/comments/489397bd-b987-4a08-9589-c5b71661ebb7/body new file mode 100644 index 0000000..6f00ded --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16fc9496-cdc2-4c6e-9b9f-b8f483b6dedb/comments/489397bd-b987-4a08-9589-c5b71661ebb7/body @@ -0,0 +1,5 @@ +Aaron said this was closeable in Nov. 24th email to the BE list. + +I think "priorities" == "bug severities", in which case this +functionality is now available with the per-tree severity +configuration. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16fc9496-cdc2-4c6e-9b9f-b8f483b6dedb/comments/489397bd-b987-4a08-9589-c5b71661ebb7/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16fc9496-cdc2-4c6e-9b9f-b8f483b6dedb/comments/489397bd-b987-4a08-9589-c5b71661ebb7/values new file mode 100644 index 0000000..a346a7c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16fc9496-cdc2-4c6e-9b9f-b8f483b6dedb/comments/489397bd-b987-4a08-9589-c5b71661ebb7/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 17:16:11 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16fc9496-cdc2-4c6e-9b9f-b8f483b6dedb/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16fc9496-cdc2-4c6e-9b9f-b8f483b6dedb/values new file mode 100644 index 0000000..400c6de --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/16fc9496-cdc2-4c6e-9b9f-b8f483b6dedb/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: Arbitrary numerical priorities? + + +time: Wed, 04 Jan 2006 21:09:30 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/6010e186-0260-44e5-8442-8df2269910ce/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/6010e186-0260-44e5-8442-8df2269910ce/body new file mode 100644 index 0000000..33638ab --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/6010e186-0260-44e5-8442-8df2269910ce/body @@ -0,0 +1,5 @@ +It's tricky to say whether we should have dependencies or reverse dependencies +or both. + +In the case where a bug is removed, normal dependencies mean that its +dependencies are erased from this system. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/6010e186-0260-44e5-8442-8df2269910ce/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/6010e186-0260-44e5-8442-8df2269910ce/values new file mode 100644 index 0000000..b82fbcb --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/6010e186-0260-44e5-8442-8df2269910ce/values @@ -0,0 +1,8 @@ +Author: abentley + + +Content-type: text/plain + + +Date: Mon, 17 Apr 2006 20:59:15 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/body new file mode 100644 index 0000000..4ac7a33 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/body @@ -0,0 +1,3 @@ +This could be implemented with an external frontend storing the +dependency data in arbitrary tags. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values new file mode 100644 index 0000000..42836a5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 21:29:13 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c531727a-9d0f-486f-aa0e-d4d2f2236640/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c531727a-9d0f-486f-aa0e-d4d2f2236640/body new file mode 100644 index 0000000..b68090a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c531727a-9d0f-486f-aa0e-d4d2f2236640/body @@ -0,0 +1 @@ +Merged into bug 7ec2c071-9630-42b0-b08a-9854616f9144
\ No newline at end of file diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c531727a-9d0f-486f-aa0e-d4d2f2236640/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c531727a-9d0f-486f-aa0e-d4d2f2236640/values new file mode 100644 index 0000000..ac20cae --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c531727a-9d0f-486f-aa0e-d4d2f2236640/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 21:29:33 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/values new file mode 100644 index 0000000..2afafb8 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: Indicate bug dependencies + + +time: Wed, 04 Jan 2006 21:05:37 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/body new file mode 100644 index 0000000..8fd0355 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/body @@ -0,0 +1,2 @@ +Target takes no options, so it does no parsing. Bad. We should probably +use a framework more like bzr's. In any case, EVERY command should accept -h. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values new file mode 100644 index 0000000..01296d8 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values @@ -0,0 +1,8 @@ +Author: abentley + + +Content-type: text/plain + + +Date: Sat, 01 Apr 2006 18:32:47 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/e5db7c9b-de48-4302-905b-9570bb6e7ade/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/e5db7c9b-de48-4302-905b-9570bb6e7ade/body new file mode 100644 index 0000000..d09a4be --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/e5db7c9b-de48-4302-905b-9570bb6e7ade/body @@ -0,0 +1 @@ +This seems to be taken care of. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/e5db7c9b-de48-4302-905b-9570bb6e7ade/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/e5db7c9b-de48-4302-905b-9570bb6e7ade/values new file mode 100644 index 0000000..7beb827 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/e5db7c9b-de48-4302-905b-9570bb6e7ade/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Fri, 14 Nov 2008 05:00:43 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/values new file mode 100644 index 0000000..25dbaca --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: target (and others?) aren't parsed properly + + +time: Sat, 01 Apr 2006 18:29:33 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4012c6cc-1300-4f6b-af0e-9176eedf8de7/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4012c6cc-1300-4f6b-af0e-9176eedf8de7/body new file mode 100644 index 0000000..552b2ea --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4012c6cc-1300-4f6b-af0e-9176eedf8de7/body @@ -0,0 +1,56 @@ +On Mon, Jul 20, 2009 at 05:03:18PM -0400, Chris Ball wrote: +> Hi Gianluca, +> +> > In any case, having the possibility to set a due date does not +> > means that it is obligatory to do it and should be a good idea to +> > offer as many possibilities as we can to the users of BE +> +> Okay, sounds reasonable. Would you like to write a patch for +> associating due dates and open/closed with a target? + +I've been mulling this over, and I think that targets are a lot like +bugs. Here's a list of issue/implementation pairs: + + * Targeting normal bugs + + With "be depend". I think we should remove the "target" field from + bugs, and move target dependencies over into the "be depend" + framework. Of course, we could add "blocks" (in addition to the + current "blocked-by") tags to make target lookup more efficient. + + * "due_by" + + We could add "due-by" to Bug.extra_strings as well, so that anyone + could set due dates for any issue they wanted. + + * Bugdir-wide target + + Just a pointer to the current target bug. + + * Target dependency tree / time-series. + + Use BLOCKS/BLOCKED-BY tags between targets, so you'd know which ones + came first. + + * be target list + + Would become "be list --severity target". A target "severity" would + keep target bugs distinct from other bug/issue types. + + * Commenting on targets + + They'd be Bug()s, so commenting already build in, e.g. to add + release notes, layout roadmaps, etc. + +If you want, we could maintain the current "be target" interface, +and just use all this stuff behind the scenes. + +Thoughts? +Trevor + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4012c6cc-1300-4f6b-af0e-9176eedf8de7/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4012c6cc-1300-4f6b-af0e-9176eedf8de7/values new file mode 100644 index 0000000..c93321b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4012c6cc-1300-4f6b-af0e-9176eedf8de7/values @@ -0,0 +1,11 @@ +Alt-id: <20090801102742.GA29000@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 1 Aug 2009 06:27:42 -0400 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4952e1c7-e035-42f1-882b-6b5264481d0a/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4952e1c7-e035-42f1-882b-6b5264481d0a/body new file mode 100644 index 0000000..c799630 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4952e1c7-e035-42f1-882b-6b5264481d0a/body @@ -0,0 +1,47 @@ +On Sunday 19 July 2009 00:00:46 Chris Ball wrote: +> Hi, +> +> > For example, let's assume we have target a, b, c There is a way +> > to know that "a" is a past target, "b" is the current target and +> > "c" is a future target ? +> +> We could add a "date due" field for each target. + +Good idea + +> > More: there is a way to know if a target is closed or open ? +> +> We could add a "target close" operation that moves all open bugs +> assigned to one target to the next date-due target. + +Nice. But instead of moving all bugs to the next date-due target, I'd prefer +to leave the choice to the user + + +> I see problems with these ideas in general, because we're assuming +> agreement by all parties/branches on when a target's date due is. +> Maybe it's okay to demand that social conventions be used to handle +> such a disagreement, or maybe not. + +I don't see these as problems per se. We can have two cases: + +1) a personal branch (like my html output or Trevor's email interface). In +this case there is not any problem to decide the due date + +2) a branch with a group of delopers (let it be the canonical branch o an +experimental branch): in this case I suppose that working together means to be +able to agree on some things + +In any case, having the possibility to set a due date does not means that it +is obligatory to do it and should be a good idea to offer as many possibilities +as we can to the users of BE + +bye +Gianluca + + + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4952e1c7-e035-42f1-882b-6b5264481d0a/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4952e1c7-e035-42f1-882b-6b5264481d0a/values new file mode 100644 index 0000000..5b9011f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4952e1c7-e035-42f1-882b-6b5264481d0a/values @@ -0,0 +1,14 @@ +Alt-id: <200907202259.11774.gian@grys.it> + + +Author: Gianluca Montecchi <gian@grys.it> + + +Content-type: text/plain + + +Date: Mon, 20 Jul 2009 22:59:11 +0200 + + +In-reply-to: 6555a651-5a7f-4a8a-9793-47ad1315e9e8 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/64424f05-b42b-4835-8afd-8495ae61345d/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/64424f05-b42b-4835-8afd-8495ae61345d/body new file mode 100644 index 0000000..08595d1 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/64424f05-b42b-4835-8afd-8495ae61345d/body @@ -0,0 +1,8 @@ +Implemented. + +You can now list targets by dependency (not by date, but better for +most cases) with + be depend -t-1 --severity target ID +where ID is the uuid of any target bug, or with + be depend -t-1 --severity target $(be target --resolve TARGET) +where TARGET is the summary of any target bug. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/64424f05-b42b-4835-8afd-8495ae61345d/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/64424f05-b42b-4835-8afd-8495ae61345d/values new file mode 100644 index 0000000..3925aa2 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/64424f05-b42b-4835-8afd-8495ae61345d/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 06 Dec 2009 05:42:52 +0000 + + +In-reply-to: 4012c6cc-1300-4f6b-af0e-9176eedf8de7 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/6555a651-5a7f-4a8a-9793-47ad1315e9e8/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/6555a651-5a7f-4a8a-9793-47ad1315e9e8/body new file mode 100644 index 0000000..ef09dc0 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/6555a651-5a7f-4a8a-9793-47ad1315e9e8/body @@ -0,0 +1,26 @@ +Hi, + + > For example, let's assume we have target a, b, c There is a way + > to know that "a" is a past target, "b" is the current target and + > "c" is a future target ? + +We could add a "date due" field for each target. + + > More: there is a way to know if a target is closed or open ? + +We could add a "target close" operation that moves all open bugs +assigned to one target to the next date-due target. + +I see problems with these ideas in general, because we're assuming +agreement by all parties/branches on when a target's date due is. +Maybe it's okay to demand that social conventions be used to handle +such a disagreement, or maybe not. + +- Chris. +-- +Chris Ball <cjb@laptop.org> + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/6555a651-5a7f-4a8a-9793-47ad1315e9e8/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/6555a651-5a7f-4a8a-9793-47ad1315e9e8/values new file mode 100644 index 0000000..5de3e0c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/6555a651-5a7f-4a8a-9793-47ad1315e9e8/values @@ -0,0 +1,14 @@ +Alt-id: <m3skgt648h.fsf@pullcord.laptop.org> + + +Author: Chris Ball <cjb@laptop.org> + + +Content-type: text/plain + + +Date: Sat, 18 Jul 2009 18:00:46 -0400 + + +In-reply-to: b9865d8b-46ae-4169-bc83-d75a98164729 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/7750d77c-85d2-4810-9d41-cec62b0da885/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/7750d77c-85d2-4810-9d41-cec62b0da885/body new file mode 100644 index 0000000..874d906 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/7750d77c-85d2-4810-9d41-cec62b0da885/body @@ -0,0 +1,20 @@ +On Monday 20 July 2009 23:03:18 Chris Ball wrote: +> Hi Gianluca, +> +> > In any case, having the possibility to set a due date does not +> > means that it is obligatory to do it and should be a good idea to +> > offer as many possibilities as we can to the users of BE +> +> Okay, sounds reasonable. Would you like to write a patch for +> associating due dates and open/closed with a target? + +Ok. As soon as I finish a basic implementation of the html export, I will be +glad to try to write a patch. + +bye +Gianluca + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/7750d77c-85d2-4810-9d41-cec62b0da885/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/7750d77c-85d2-4810-9d41-cec62b0da885/values new file mode 100644 index 0000000..0d96f8e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/7750d77c-85d2-4810-9d41-cec62b0da885/values @@ -0,0 +1,14 @@ +Alt-id: <200907202340.39963.gian@grys.it> + + +Author: Gianluca Montecchi <gian@grys.it> + + +Content-type: text/plain + + +Date: Mon, 20 Jul 2009 23:40:39 +0200 + + +In-reply-to: 777182da-a216-45c7-bf4d-42c84e511c66 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/777182da-a216-45c7-bf4d-42c84e511c66/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/777182da-a216-45c7-bf4d-42c84e511c66/body new file mode 100644 index 0000000..13505c1 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/777182da-a216-45c7-bf4d-42c84e511c66/body @@ -0,0 +1,19 @@ +Hi Gianluca, + + > In any case, having the possibility to set a due date does not + > means that it is obligatory to do it and should be a good idea to + > offer as many possibilities as we can to the users of BE + +Okay, sounds reasonable. Would you like to write a patch for +associating due dates and open/closed with a target? + +Thanks, + +- Chris. +-- +Chris Ball <cjb@laptop.org> + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/777182da-a216-45c7-bf4d-42c84e511c66/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/777182da-a216-45c7-bf4d-42c84e511c66/values new file mode 100644 index 0000000..bf069fc --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/777182da-a216-45c7-bf4d-42c84e511c66/values @@ -0,0 +1,14 @@ +Alt-id: <m3hbx72hk9.fsf@pullcord.laptop.org> + + +Author: Chris Ball <cjb@laptop.org> + + +Content-type: text/plain + + +Date: Mon, 20 Jul 2009 17:03:18 -0400 + + +In-reply-to: 4952e1c7-e035-42f1-882b-6b5264481d0a + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/9bbe9370-99c7-4d7c-80ee-9ade6b6feb9f/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/9bbe9370-99c7-4d7c-80ee-9ade6b6feb9f/body new file mode 100644 index 0000000..a916904 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/9bbe9370-99c7-4d7c-80ee-9ade6b6feb9f/body @@ -0,0 +1,37 @@ +On Sat, Jul 18, 2009 at 06:00:46PM -0400, Chris Ball wrote: +> > For example, let's assume we have target a, b, c There is a way +> > to know that "a" is a past target, "b" is the current target and +> > "c" is a future target ? +> +> We could add a "date due" field for each target. + +Another option would be a "blocked by" field, since you might miss +deadlines, or have parallel targeted branches. Or just pick target +names following some scheme so the alphanumeric-sort is also a +dependency-order sort ;). + +> > More: there is a way to know if a target is closed or open ? + +There's also + $ be list --target 0.1 +If there are active bugs, the target is open. Otherwise, you must have +made it ;). + +> We could add a "target close" operation that moves all open bugs +> assigned to one target to the next date-due target. + +for bug in `be list --target 0.1 --uuids`; do + be target $bug $NEXT_TARGET +done + +To avoid the loop, we could change status, severity, target, etc from + be COMMAND BUG ARG +to + be COMMAND ARG BUG [MORE BUGS ...] + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/9bbe9370-99c7-4d7c-80ee-9ade6b6feb9f/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/9bbe9370-99c7-4d7c-80ee-9ade6b6feb9f/values new file mode 100644 index 0000000..0f4ff0a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/9bbe9370-99c7-4d7c-80ee-9ade6b6feb9f/values @@ -0,0 +1,14 @@ +Alt-id: <20090718222701.GA304@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 18 Jul 2009 18:27:01 -0400 + + +In-reply-to: 6555a651-5a7f-4a8a-9793-47ad1315e9e8 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/b9865d8b-46ae-4169-bc83-d75a98164729/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/b9865d8b-46ae-4169-bc83-d75a98164729/body new file mode 100644 index 0000000..7382bae --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/b9865d8b-46ae-4169-bc83-d75a98164729/body @@ -0,0 +1,20 @@ +Hello + +Just a question and only for curiosity: there is an easy way to determine the +target succession ? + +For example, let's assume we have target a, b, c +There is a way to know that "a" is a past target, "b" is the current target +and "c" is a future target ? More: there is a way to know if a target is +closed or open ? + +thanks + +bye +Gianluca + + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/b9865d8b-46ae-4169-bc83-d75a98164729/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/b9865d8b-46ae-4169-bc83-d75a98164729/values new file mode 100644 index 0000000..6f56640 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/b9865d8b-46ae-4169-bc83-d75a98164729/values @@ -0,0 +1,11 @@ +Alt-id: <200907182351.03217.gian@grys.it> + + +Author: Gianluca Montecchi <gian@grys.it> + + +Content-type: text/plain + + +Date: Sat, 18 Jul 2009 23:51:03 +0200 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values new file mode 100644 index 0000000..64928e8 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values @@ -0,0 +1,24 @@ +assigned: W. Trevor King <wking@drexel.edu> + + +creator: W. Trevor King <wking@drexel.edu> + + +extra_strings: +- BLOCKED-BY:51930348-9ccc-4165-af41-6c7450de050e + + +reporter: Gianluca Montecchi <gian@grys.it> + + +severity: wishlist + + +status: fixed + + +summary: Sorting targets chronologically + + +time: Tue, 21 Jul 2009 18:34:25 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/27bb8bc2-05c2-417a-9d09-928471380d7a/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/27bb8bc2-05c2-417a-9d09-928471380d7a/values new file mode 100644 index 0000000..7082a38 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/27bb8bc2-05c2-417a-9d09-928471380d7a/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: Are dates still a problem? + + +time: Tue, 20 Dec 2005 19:37:03 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/comments/6d7072de-89b6-4c53-a435-6879c644a0e8/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/comments/6d7072de-89b6-4c53-a435-6879c644a0e8/body new file mode 100644 index 0000000..e936bd4 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/comments/6d7072de-89b6-4c53-a435-6879c644a0e8/body @@ -0,0 +1,3 @@ +We should support:
+WONTFIX
+EMPTY
\ No newline at end of file diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/comments/6d7072de-89b6-4c53-a435-6879c644a0e8/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/comments/6d7072de-89b6-4c53-a435-6879c644a0e8/values new file mode 100644 index 0000000..1402892 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/comments/6d7072de-89b6-4c53-a435-6879c644a0e8/values @@ -0,0 +1,8 @@ +Author: abentley + + +Content-type: text/plain + + +Date: Wed, 04 Jan 2006 21:03:54 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/values new file mode 100644 index 0000000..df80422 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: More types of closed bugs + + +time: Wed, 04 Jan 2006 21:03:27 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2aa60b34-2c8d-4f41-bb97-a57309523262/comments/f21bec0d-cad0-44d2-a301-bfb11adce313/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2aa60b34-2c8d-4f41-bb97-a57309523262/comments/f21bec0d-cad0-44d2-a301-bfb11adce313/body new file mode 100644 index 0000000..dd40bfa --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2aa60b34-2c8d-4f41-bb97-a57309523262/comments/f21bec0d-cad0-44d2-a301-bfb11adce313/body @@ -0,0 +1 @@ +Aaron said this was closeable in Nov. 24th email to the BE list. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2aa60b34-2c8d-4f41-bb97-a57309523262/comments/f21bec0d-cad0-44d2-a301-bfb11adce313/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2aa60b34-2c8d-4f41-bb97-a57309523262/comments/f21bec0d-cad0-44d2-a301-bfb11adce313/values new file mode 100644 index 0000000..4c495f7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2aa60b34-2c8d-4f41-bb97-a57309523262/comments/f21bec0d-cad0-44d2-a301-bfb11adce313/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 17:21:08 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2aa60b34-2c8d-4f41-bb97-a57309523262/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2aa60b34-2c8d-4f41-bb97-a57309523262/values new file mode 100644 index 0000000..1d358cd --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2aa60b34-2c8d-4f41-bb97-a57309523262/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: implement message-change log + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2b81b428-fc43-4970-9469-b442385b9c0d/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2b81b428-fc43-4970-9469-b442385b9c0d/values new file mode 100644 index 0000000..c2861d0 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2b81b428-fc43-4970-9469-b442385b9c0d/values @@ -0,0 +1,17 @@ +creator: gianluca <gian@galactica> + + +reporter: gianluca <gian@galactica> + + +severity: minor + + +status: fixed + + +summary: Use the get_parser in becommands/html.py + + +time: Wed, 08 Jul 2009 21:27:37 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/074ef29a-3f1d-46dc-8561-7a56af7e6d67/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/074ef29a-3f1d-46dc-8561-7a56af7e6d67/body new file mode 100644 index 0000000..5ce4f1c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/074ef29a-3f1d-46dc-8561-7a56af7e6d67/body @@ -0,0 +1,23 @@ +"W. Trevor King" <wking@drexel.edu> writes: + +> On Sat, Jul 04, 2009 at 10:19:35AM +1000, Ben Finney wrote: +> > Instead of a separate command for each output format, could we have +> > a single "produce a static report of the bug database" command, and +> > specify output format as an option? +> > […] +> +> Do people like this architecture better than my be-xml-to-mbox +> approach? + +I think this question is illuminated by the related question: Is mbox +output a static report, or another read-write data store? + +It can technically be both, of course, which is why the question may be +helpful: it may help show what is the *conceptual* purpose of the mbox +output format for Bugs Everywhere. + +-- + \ “Time is the great legalizer, even in the field of morals.” | + `\ —Henry L. Mencken | +_o__) | +Ben Finney diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/074ef29a-3f1d-46dc-8561-7a56af7e6d67/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/074ef29a-3f1d-46dc-8561-7a56af7e6d67/values new file mode 100644 index 0000000..eda49f5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/074ef29a-3f1d-46dc-8561-7a56af7e6d67/values @@ -0,0 +1,14 @@ +Alt-id: <87hbxqrckv.fsf@benfinney.id.au> + + +Author: Ben Finney <bignose+hates-spam@benfinney.id.au> + + +Content-type: text/plain + + +Date: Mon, 06 Jul 2009 08:26:24 +1000 + + +In-reply-to: cb5689f4-7c36-4c44-b380-ca9e06e80bae + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/1dba8196-654b-4ca0-9a95-fb334af81863/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/1dba8196-654b-4ca0-9a95-fb334af81863/body new file mode 100644 index 0000000..dbf3b1b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/1dba8196-654b-4ca0-9a95-fb334af81863/body @@ -0,0 +1,47 @@ +Gianluca Montecchi <gian@grys.it> writes: + +> 1) is it ok to develop this command ? I know that this is not a fully +> featured web interface, but I am sure that it can be usefull. + +Yes, definitely. I can see it being a very easy way to put one's bug +database online for browsing. + +> I am open to suggestion about it of course. + +Instead of a separate command for each output format, could we have a +single “produce a static report of the bug database” command, and +specify output format as an option? + +How about: + + be report + be report --format ascii + be report --format rst + be report --format html + +Where the ‘--format’ option has a default of, e.g., “ascii”. + +This would mean that you are implementing the ‘html’ format of this +putative command. + +> 2) I see that every command is implemented with a python file in the +> becommand dir. For a better code, I'd like to split the command +> implementation into two files: a file that contain the actual code and +> a second file that have the html related part, any problem with this ? + +This sounds quite sensible to me. The existence of a command implies a +module of the same name in ‘becommand’, but there's no necessary +implication that that module can't import modules from elsewhere to do +its work. + +-- + \ “It ain't so much the things we don't know that get us in | + `\ trouble. It's the things we know that ain't so.” —Artemus Ward | +_o__) (1834–1867), U.S. journalist | +Ben Finney + + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/1dba8196-654b-4ca0-9a95-fb334af81863/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/1dba8196-654b-4ca0-9a95-fb334af81863/values new file mode 100644 index 0000000..642697d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/1dba8196-654b-4ca0-9a95-fb334af81863/values @@ -0,0 +1,14 @@ +Alt-id: <87y6r5qoyw.fsf@benfinney.id.au> + + +Author: Ben Finney <bignose+hates-spam@benfinney.id.au> + + +Content-type: text/plain + + +Date: Sat, 04 Jul 2009 10:19:35 +1000 + + +In-reply-to: cb5689f4-7c36-4c44-b380-ca9e06e80bae + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/3bf57ee7-710f-4a01-a8af-8bb9eb9dc937/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/3bf57ee7-710f-4a01-a8af-8bb9eb9dc937/body new file mode 100644 index 0000000..4276b9c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/3bf57ee7-710f-4a01-a8af-8bb9eb9dc937/body @@ -0,0 +1,25 @@ +Gianluca Montecchi <gian@grys.it> writes: + +> On Monday 06 July 2009 12:48:39 W. Trevor King wrote: +> > Gianluca is clearly thinking about a static report [for a collection +> > of HTML files as output]: +> +> You are right, static, but not exactly a report as I think Ben is +> thinking + +I think it exactly is a report: multiple, static, browseable pages +reporting the state of the database at a point in time. + +What makes you think that term doesn't apply? + +-- + \ “The problem with television is that the people must sit and | + `\ keep their eyes glued on a screen: the average American family | +_o__) hasn't time for it.” —_The New York Times_, 1939 | +Ben Finney + + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/3bf57ee7-710f-4a01-a8af-8bb9eb9dc937/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/3bf57ee7-710f-4a01-a8af-8bb9eb9dc937/values new file mode 100644 index 0000000..d8ccad9 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/3bf57ee7-710f-4a01-a8af-8bb9eb9dc937/values @@ -0,0 +1,14 @@ +Alt-id: <87skh9p8ax.fsf@benfinney.id.au> + + +Author: Ben Finney <bignose+hates-spam@benfinney.id.au> + + +Content-type: text/plain + + +Date: Tue, 07 Jul 2009 11:53:58 +1000 + + +In-reply-to: cb5689f4-7c36-4c44-b380-ca9e06e80bae + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/55263144-9775-4b18-ab83-29d66ed91a53/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/55263144-9775-4b18-ab83-29d66ed91a53/body new file mode 100644 index 0000000..8451bd7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/55263144-9775-4b18-ab83-29d66ed91a53/body @@ -0,0 +1,50 @@ +On Mon, Jul 06, 2009 at 08:26:24AM +1000, Ben Finney wrote: +> "W. Trevor King" <wking@drexel.edu> writes: +> +> > On Sat, Jul 04, 2009 at 10:19:35AM +1000, Ben Finney wrote: +> > > Instead of a separate command for each output format, could we have +> > > a single "produce a static report of the bug database" command, and +> > > specify output format as an option? +> > +> > Do people like this architecture better than my be-xml-to-mbox +> > approach? +> +> I think this question is illuminated by the related question: Is mbox +> output a static report, or another read-write data store? + +Gianluca is clearly thinking about a static report: + +On Fri, Jul 03, 2009 at 10:50:17PM +0200, Gianluca Montecchi wrote: +> The goal is to be able to do something like "be html /web/page" to have in the +> /web/page directory some static html pages that basically are the dump of the +> be repository, much like ditz have + +I think truly interactive frontends like Steve's working on need to be +build on top of libbe directly, since they'll need to make lots of +small changes to the database, and it's to slow to be reloading the +database for every change. Static dumps like my mbox or Gianluca's +html could just parse the xml output of `be list' and other be +commands. + +There should also be an xml import for `be new' and `be comment' so +you could import new bugs/comments from whatever format after writing +a whatever->xml converter. This would allow you to email new bugs and +comments to the database (e.g. via some procmail-spawned +be-parse-email script) which would give you some level of +interactivity, but you'd have to regenerate your mbox to see your new +comments in your mail reader. + +I think interactive use that gives you live-updates in your mail +reader isn't worth the trouble, since you'd need to teach BE imap or +smtp+mbox-locking. Hmm, maybe it smtp+mbox-locking wouldn't be so bad, +but that would be a distinct frontend project like Steve's, not part +of the becommands. + +Trevor + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/55263144-9775-4b18-ab83-29d66ed91a53/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/55263144-9775-4b18-ab83-29d66ed91a53/values new file mode 100644 index 0000000..8b10a06 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/55263144-9775-4b18-ab83-29d66ed91a53/values @@ -0,0 +1,14 @@ +Alt-id: <20090706104839.GA19537@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 6 Jul 2009 06:48:39 -0400 + + +In-reply-to: 074ef29a-3f1d-46dc-8561-7a56af7e6d67 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/68927fef-6ce1-4a1f-a414-28695d913a50/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/68927fef-6ce1-4a1f-a414-28695d913a50/body new file mode 100644 index 0000000..3b53533 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/68927fef-6ce1-4a1f-a414-28695d913a50/body @@ -0,0 +1,23 @@ +On Sat, Jul 04, 2009 at 10:19:35AM +1000, Ben Finney wrote: +> Instead of a separate command for each output format, could we have a +> single "produce a static report of the bug database" command, and +> specify output format as an option? +> +> How about: +> +> be report +> be report --format ascii +> be report --format rst +> be report --format html + +Do people like this architecture better than my be-xml-to-mbox +approach? I think the tradeoff is easy output format implementation +vs cluttered core codebase. Should we use both, depending on how +useful people think the output format will be? + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/68927fef-6ce1-4a1f-a414-28695d913a50/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/68927fef-6ce1-4a1f-a414-28695d913a50/values new file mode 100644 index 0000000..a01e2cd --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/68927fef-6ce1-4a1f-a414-28695d913a50/values @@ -0,0 +1,14 @@ +Alt-id: <20090705143108.GB10709@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 5 Jul 2009 10:31:08 -0400 + + +In-reply-to: 1dba8196-654b-4ca0-9a95-fb334af81863 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/83202b83-eea8-452f-8239-d468940bddba/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/83202b83-eea8-452f-8239-d468940bddba/body new file mode 100644 index 0000000..9bf3851 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/83202b83-eea8-452f-8239-d468940bddba/body @@ -0,0 +1,123 @@ +On Fri, Jul 03, 2009 at 10:50:17PM +0200, Gianluca Montecchi wrote: +> +> Hello to everyone +> +> As i said in a previous mail, I am working on a "html" command for be. +> The goal is to be able to do something like "be html /web/page" to have in the +> /web/page directory some static html pages that basically are the dump of the +> be repository, much like ditz have +> This will enable a simple and fast publish of the bus list and details on the +> web, at least in read only mode. +> +> So I'd like to ask some question: +> 1) is it ok to develop this command ? I know that this is not a fully featured +> web interface, but I am sure that it can be usefull. +> +> I am open to suggestion about it of course. +> +> 2) I see that every command is implemented with a python file in the becommand +> dir. For a better code, I'd like to split the command implementation into two +> files: a file that contain the actual code and a second file that have the html +> related part, any problem with this ? I don't like to have the html part and +> the code part in one big and unreadable file. +> +> I'd like to hear other opinion about this. +> +> Thanks for now +> bye +> Gianluca +> +> +> _______________________________________________ +> Be-devel mailing list +> Be-devel@bugseverywhere.org +> http://void.printf.net/cgi-bin/mailman/listinfo/be-devel + +On Mon, Jul 06, 2009 at 10:18:33PM +0200, Gianluca Montecchi wrote: +> This sound like an interesting idea, but what i'd like to do is not, strictly +> speaking, a report. It is a full tree of html pages that are browseable, both +> on line and offline + +I'm not sure what distinction you're making about "report". You're +just producing a static snapshot of the current database status, +right? The number of pages and completeness of coverage are nice, but +it's still a static entity generated from a particular snapshot, which +is what I mean by "report" ;). + +> > > 2) I see that every command is implemented with a python file in the +> > > becommand dir. For a better code, I'd like to split the command +> > > implementation into two files: a file that contain the actual code and +> > > a second file that have the html related part, any problem with this ? +> > +> > This sounds quite sensible to me. The existence of a command implies a +> > module of the same name in ‘becommand’, but there's no necessary +> > implication that that module can't import modules from elsewhere to do +> > its work. +> +> The "elsewhere" for now is the same directory, just another module +> + +On Mon, Jul 06, 2009 at 10:38:56PM +0200, Gianluca Montecchi wrote: +> > On Fri, Jul 03, 2009 at 10:50:17PM +0200, Gianluca Montecchi wrote: +> > > The goal is to be able to do something like "be html /web/page" to have +> > > in the /web/page directory some static html pages that basically are the +> > > dump of the be repository, much like ditz have +> > +> > I think truly interactive frontends like Steve's working on need to be +> > build on top of libbe directly, since they'll need to make lots of +> > small changes to the database, and it's to slow to be reloading the +> > database for every change. Static dumps like my mbox or Gianluca's +> > html could just parse the xml output of `be list' and other be +> > commands. +> +> Ok, but if I want to have an html dump that is browseable, I need to parse the +> xml. Am I correct ? +> If yes, should not be easiear to use directly the libbe ? + +Using libbe directly is easier, but also more tightly tied to the be +internals which could weigh down future refactoring. Partly I'm +afraid of our 2.5 different html-output mechanisms. Either their +should be a single Right Way that tries to satisfy everyone, or a +smorgasbord of loosely coupled translators, so it's not so painful to +kill them if/when they go out of style :p. + +On Mon, Jul 06, 2009 at 10:46:54PM +0200, Gianluca Montecchi wrote: +> On Saturday 04 July 2009 02:31:26 Chris Ball wrote: +> > It might be a good idea for "be html" to use the CherryPy web interface +> > that Steve is working on. The command could start up the CherryPy app +> > and scrape all of the available pages to get a stand-alone dump; this +> > would avoid having to keep two (okay, more than two at this point) +> > separate sets of HTML templates in the source tree. What do you think? +> +> It can be do, but this implies that CherryPy must be installed and configured, +> a thing that I don't want to impose. My idea is to offer a simpler way to have +> some html pages, where you just need to have BE installed. + +I agree that not needing CherryPy for a static html dump is good. +Also, read-only templates will look different from the CherryPy +interactive templates. +1 for another quasi-redundant template set +;). + +> > > 2) I see that every command is implemented with a python file in +> > > the becommand dir. For a better code, I'd like to split the +> > > command implementation into two files: a file that contain the +> > > actual code and a second file that have the html related part, +> > > any problem with this ? I don't like to have the html part and +> > > the code part in one big and unreadable file. +> > +> > I agree that becommands/*.py commands should not contain any HTML +> > layout code. Putting it somewhere else instead sounds fine. +> +> I am in doubt with the "somewhere else", since for now I put the html template +> into a separate file in the same directory. Suggestion ? + +I think that only code intended only for command line use only should +go into becommands, but really, just dump it anywhere and we can shift +it around later :p. + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/83202b83-eea8-452f-8239-d468940bddba/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/83202b83-eea8-452f-8239-d468940bddba/values new file mode 100644 index 0000000..07da71c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/83202b83-eea8-452f-8239-d468940bddba/values @@ -0,0 +1,14 @@ +Alt-id: <20090707013454.GA3721@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 6 Jul 2009 21:34:54 -0400 + + +In-reply-to: da97e18f-33d6-469e-9d93-6457b9a6bfca + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/8c1c4f38-a8d4-4cf9-a9f0-e9846ebbcad8/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/8c1c4f38-a8d4-4cf9-a9f0-e9846ebbcad8/body new file mode 100644 index 0000000..2301eba --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/8c1c4f38-a8d4-4cf9-a9f0-e9846ebbcad8/body @@ -0,0 +1,47 @@ +On Saturday 04 July 2009 02:19:35 Ben Finney wrote: +> Gianluca Montecchi <gian@grys.it> writes: + +> +> > I am open to suggestion about it of course. +> +> Instead of a separate command for each output format, could we have a +> single “produce a static report of the bug database” command, and +> specify output format as an option? +> +> How about: +> +> be report +> be report --format ascii +> be report --format rst +> be report --format html +> +> Where the ‘--format’ option has a default of, e.g., “ascii”. +> +> This would mean that you are implementing the ‘html’ format of this +> putative command. +> + +This sound like an interesting idea, but what i'd like to do is not, strictly +speaking, a report. It is a full tree of html pages that are browseable, both +on line and offline + +> > 2) I see that every command is implemented with a python file in the +> > becommand dir. For a better code, I'd like to split the command +> > implementation into two files: a file that contain the actual code and +> > a second file that have the html related part, any problem with this ? +> +> This sounds quite sensible to me. The existence of a command implies a +> module of the same name in ‘becommand’, but there's no necessary +> implication that that module can't import modules from elsewhere to do +> its work. + +The "elsewhere" for now is the same directory, just another module + +bye +Gianluca + + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/8c1c4f38-a8d4-4cf9-a9f0-e9846ebbcad8/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/8c1c4f38-a8d4-4cf9-a9f0-e9846ebbcad8/values new file mode 100644 index 0000000..17513d6 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/8c1c4f38-a8d4-4cf9-a9f0-e9846ebbcad8/values @@ -0,0 +1,14 @@ +Alt-id: <200907062218.33895.gian@grys.it> + + +Author: Gianluca Montecchi <gian@grys.it> + + +Content-type: text/plain + + +Date: Mon, 06 Jul 2009 22:18:33 +0200 + + +In-reply-to: 1dba8196-654b-4ca0-9a95-fb334af81863 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/b900f7fd-bab6-48c4-922c-a051f933da58/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/b900f7fd-bab6-48c4-922c-a051f933da58/body new file mode 100644 index 0000000..50a30e8 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/b900f7fd-bab6-48c4-922c-a051f933da58/body @@ -0,0 +1,35 @@ +Hi Gianluca, + + > As i said in a previous mail, I am working on a "html" command + > for be. The goal is to be able to do something like "be html + > /web/page" to have in the /web/page directory some static html + > pages that basically are the dump of the be repository, much like + > ditz have. This will enable a simple and fast publish of the bus + > list and details on the web, at least in read only mode. + +It might be a good idea for "be html" to use the CherryPy web interface +that Steve is working on. The command could start up the CherryPy app +and scrape all of the available pages to get a stand-alone dump; this +would avoid having to keep two (okay, more than two at this point) +separate sets of HTML templates in the source tree. What do you think? + + > 2) I see that every command is implemented with a python file in + > the becommand dir. For a better code, I'd like to split the + > command implementation into two files: a file that contain the + > actual code and a second file that have the html related part, + > any problem with this ? I don't like to have the html part and + > the code part in one big and unreadable file. + +I agree that becommands/*.py commands should not contain any HTML +layout code. Putting it somewhere else instead sounds fine. + +Thanks! + +- Chris. +-- +Chris Ball <cjb@laptop.org> + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/b900f7fd-bab6-48c4-922c-a051f933da58/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/b900f7fd-bab6-48c4-922c-a051f933da58/values new file mode 100644 index 0000000..ee8e589 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/b900f7fd-bab6-48c4-922c-a051f933da58/values @@ -0,0 +1,14 @@ +Alt-id: <m3iqi9thk1.fsf@pullcord.laptop.org> + + +Author: Chris Ball <cjb@laptop.org> + + +Content-type: text/plain + + +Date: Fri, 03 Jul 2009 20:31:26 -0400 + + +In-reply-to: cb5689f4-7c36-4c44-b380-ca9e06e80bae + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/c7ace551-2982-4683-bca3-b5e66056cce5/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/c7ace551-2982-4683-bca3-b5e66056cce5/body new file mode 100644 index 0000000..8991cfb --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/c7ace551-2982-4683-bca3-b5e66056cce5/body @@ -0,0 +1,93 @@ +> On Mon, Jul 06, 2009 at 10:18:33PM +0200, Gianluca Montecchi wrote: +>> This sound like an interesting idea, but what i'd like to do is not, +>> strictly +>> speaking, a report. It is a full tree of html pages that are browseable, +>> both +>> on line and offline +> +> I'm not sure what distinction you're making about "report". You're +> just producing a static snapshot of the current database status, +> right? The number of pages and completeness of coverage are nice, but +> it's still a static entity generated from a particular snapshot, which +> is what I mean by "report" ;). + +Mmm, my bad here. +I normally speak about "report" as something that is not browseable, like +the output of a report generator (reportlab or whatever), but I admit that +basically also the html output I am working on is a report. + + +> On Mon, Jul 06, 2009 at 10:38:56PM +0200, Gianluca Montecchi wrote: +>> +>> Ok, but if I want to have an html dump that is browseable, I need to +>> parse the +>> xml. Am I correct ? +>> If yes, should not be easiear to use directly the libbe ? +> +> Using libbe directly is easier, but also more tightly tied to the be +> internals which could weigh down future refactoring. Partly I'm +> afraid of our 2.5 different html-output mechanisms. Either their +> should be a single Right Way that tries to satisfy everyone, or a +> smorgasbord of loosely coupled translators, so it's not so painful to +> kill them if/when they go out of style :p. + +I know that using libbe I am more tightly tied to the internals, but +I am trying to keep the command code and the presentation code crearly +separated to minimize this problem. I am not sure this is a real problem +anyway. + + +> On Mon, Jul 06, 2009 at 10:46:54PM +0200, Gianluca Montecchi wrote: +>> On Saturday 04 July 2009 02:31:26 Chris Ball wrote: +>> > It might be a good idea for "be html" to use the CherryPy web +>> interface +>> > that Steve is working on. The command could start up the CherryPy app +>> > and scrape all of the available pages to get a stand-alone dump; this +>> > would avoid having to keep two (okay, more than two at this point) +>> > separate sets of HTML templates in the source tree. What do you +>> think? +>> +>> It can be do, but this implies that CherryPy must be installed and +>> configured, +>> a thing that I don't want to impose. My idea is to offer a simpler way +>> to have +>> some html pages, where you just need to have BE installed. +> +> I agree that not needing CherryPy for a static html dump is good. +> Also, read-only templates will look different from the CherryPy +> interactive templates. +1 for another quasi-redundant template set +> ;). + +The look is not a problem. I can always use the same html Steve is using. +I am also playing with the idea to have the template themeable some time +after I have a fully working version. + +> +>> > > 2) I see that every command is implemented with a python file in +>> > > the becommand dir. For a better code, I'd like to split the +>> > > command implementation into two files: a file that contain the +>> > > actual code and a second file that have the html related part, +>> > > any problem with this ? I don't like to have the html part and +>> > > the code part in one big and unreadable file. +>> > +>> > I agree that becommands/*.py commands should not contain any HTML +>> > layout code. Putting it somewhere else instead sounds fine. +>> +>> I am in doubt with the "somewhere else", since for now I put the html +>> template +>> into a separate file in the same directory. Suggestion ? +> +> I think that only code intended only for command line use only should +> go into becommands, but really, just dump it anywhere and we can shift +> it around later :p. + +Of course. + +bye +Gianluca + + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/c7ace551-2982-4683-bca3-b5e66056cce5/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/c7ace551-2982-4683-bca3-b5e66056cce5/values new file mode 100644 index 0000000..fc79745 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/c7ace551-2982-4683-bca3-b5e66056cce5/values @@ -0,0 +1,14 @@ +Alt-id: <6f719a1c43fdcba8bdbfee1130072595.squirrel@webmail.grys.it> + + +Author: gian@grys.it + + +Content-type: text/plain + + +Date: Tue, 07 Jul 2009 14:15:08 +0200 + + +In-reply-to: 83202b83-eea8-452f-8239-d468940bddba + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/cb5689f4-7c36-4c44-b380-ca9e06e80bae/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/cb5689f4-7c36-4c44-b380-ca9e06e80bae/body new file mode 100644 index 0000000..d8f14fc --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/cb5689f4-7c36-4c44-b380-ca9e06e80bae/body @@ -0,0 +1,32 @@ +Hello to everyone + +As i said in a previous mail, I am working on a "html" command for be. +The goal is to be able to do something like "be html /web/page" to have in the +/web/page directory some static html pages that basically are the dump of the +be repository, much like ditz have +This will enable a simple and fast publish of the bus list and details on the +web, at least in read only mode. + +So I'd like to ask some question: +1) is it ok to develop this command ? I know that this is not a fully featured +web interface, but I am sure that it can be usefull. + +I am open to suggestion about it of course. + +2) I see that every command is implemented with a python file in the becommand +dir. For a better code, I'd like to split the command implementation into two +files: a file that contain the actual code and a second file that have the html +related part, any problem with this ? I don't like to have the html part and +the code part in one big and unreadable file. + +I'd like to hear other opinion about this. + +Thanks for now +bye +Gianluca + + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/cb5689f4-7c36-4c44-b380-ca9e06e80bae/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/cb5689f4-7c36-4c44-b380-ca9e06e80bae/values new file mode 100644 index 0000000..9ce9085 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/cb5689f4-7c36-4c44-b380-ca9e06e80bae/values @@ -0,0 +1,11 @@ +Alt-id: <200907032250.17327.gian@grys.it> + + +Author: Gianluca Montecchi <gian@grys.it> + + +Content-type: text/plain + + +Date: Fri, 03 Jul 2009 22:50:17 +0200 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/da97e18f-33d6-469e-9d93-6457b9a6bfca/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/da97e18f-33d6-469e-9d93-6457b9a6bfca/body new file mode 100644 index 0000000..27dca1e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/da97e18f-33d6-469e-9d93-6457b9a6bfca/body @@ -0,0 +1,45 @@ +On Saturday 04 July 2009 02:31:26 Chris Ball wrote: +> Hi Gianluca, +> +> > As i said in a previous mail, I am working on a "html" command +> > for be. The goal is to be able to do something like "be html +> > /web/page" to have in the /web/page directory some static html +> > pages that basically are the dump of the be repository, much like +> > ditz have. This will enable a simple and fast publish of the bus +> > list and details on the web, at least in read only mode. +> +> It might be a good idea for "be html" to use the CherryPy web interface +> that Steve is working on. The command could start up the CherryPy app +> and scrape all of the available pages to get a stand-alone dump; this +> would avoid having to keep two (okay, more than two at this point) +> separate sets of HTML templates in the source tree. What do you think? + +It can be do, but this implies that CherryPy must be installed and configured, +a thing that I don't want to impose. My idea is to offer a simpler way to have +some html pages, where you just need to have BE installed. + +My very first implementation was a script that parse directly the .be directory +to build the pages, without BE itself installed. + + +> > 2) I see that every command is implemented with a python file in +> > the becommand dir. For a better code, I'd like to split the +> > command implementation into two files: a file that contain the +> > actual code and a second file that have the html related part, +> > any problem with this ? I don't like to have the html part and +> > the code part in one big and unreadable file. +> +> I agree that becommands/*.py commands should not contain any HTML +> layout code. Putting it somewhere else instead sounds fine. + +I am in doubt with the "somewhere else", since for now I put the html template +into a separate file in the same directory. Suggestion ? + +thanks +bye +Gianluca + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/da97e18f-33d6-469e-9d93-6457b9a6bfca/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/da97e18f-33d6-469e-9d93-6457b9a6bfca/values new file mode 100644 index 0000000..f989b78 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/da97e18f-33d6-469e-9d93-6457b9a6bfca/values @@ -0,0 +1,14 @@ +Alt-id: <200907062246.54804.gian@grys.it> + + +Author: Gianluca Montecchi <gian@grys.it> + + +Content-type: text/plain + + +Date: Mon, 06 Jul 2009 22:46:54 +0200 + + +In-reply-to: b900f7fd-bab6-48c4-922c-a051f933da58 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/e5248100-ea02-4205-a4c1-ac7a577c6362/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/e5248100-ea02-4205-a4c1-ac7a577c6362/body new file mode 100644 index 0000000..1d2b619 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/e5248100-ea02-4205-a4c1-ac7a577c6362/body @@ -0,0 +1,43 @@ +On Thursday 25 June 2009 16:23:04 Steve Losh wrote: +> On Jun 25, 2009, at 10:02 AM, Chris Ball <cjb@laptop.org> wrote: +> >> Oh, and obviously there must still be bugs in BE. Please submit +> >> more ;). +> > +> > Perhaps it's a good time to merge Steve Losh's CherryPy web interface? +> > +> > http://void.printf.net/pipermail/be-devel/2009-February/000095.html +> > http://bitbucket.org/sjl/cherryflavoredbugseverywhere/ +> +> Hey, I haven't touched the web interface in a while, but I should have +> some time to fix some stuff up tonight and tomorrow. Hold off on +> merging it in until then. +> +> I'm still curious as to what people think the role of a web interface +> like this should be. When I wrote it I meant it as a single-user +> interface like the command line one. It could definitely work as a +> public, read-only interface too. + +I'd really like to have some sort of web interface for BE, also in read-only +mode. + +I am thinking to write (actually I wrote some test code) a tool to parse a BE +repository to output a set of static html pages to put online, like the "ditz +html" command, but this was before I start to play with BE sourcecode, so now +I ma thinking to implement it as a BE command. + +> If the goal is to allow more than one person to add issues, how should +> commits go? One commit per change? Commit every X minutes if necessary? + +I think that a simple web interface should be read-only. + +Eventually, to allow to add issues also from the web interface, it should be +done to a specific branch, one commit per change. + +just my 2 cents... +bye +Gianluca + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/e5248100-ea02-4205-a4c1-ac7a577c6362/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/e5248100-ea02-4205-a4c1-ac7a577c6362/values new file mode 100644 index 0000000..931a187 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/e5248100-ea02-4205-a4c1-ac7a577c6362/values @@ -0,0 +1,11 @@ +Alt-id: <200906252203.08535.gian@grys.it> + + +Author: Gianluca Montecchi <gian@grys.it> + + +Content-type: text/plain + + +Date: Thu, 25 Jun 2009 22:03:08 +0200 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/fd7ab206-5937-4ede-9e78-97aff098b677/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/fd7ab206-5937-4ede-9e78-97aff098b677/body new file mode 100644 index 0000000..2e4f851 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/fd7ab206-5937-4ede-9e78-97aff098b677/body @@ -0,0 +1,43 @@ +On Monday 06 July 2009 12:48:39 W. Trevor King wrote: +> On Mon, Jul 06, 2009 at 08:26:24AM +1000, Ben Finney wrote: +> > "W. Trevor King" <wking@drexel.edu> writes: +> > > On Sat, Jul 04, 2009 at 10:19:35AM +1000, Ben Finney wrote: +> > > > Instead of a separate command for each output format, could we have +> > > > a single "produce a static report of the bug database" command, and +> > > > specify output format as an option? +> > > +> > > Do people like this architecture better than my be-xml-to-mbox +> > > approach? +> > +> > I think this question is illuminated by the related question: Is mbox +> > output a static report, or another read-write data store? +> +> Gianluca is clearly thinking about a static report: + +You are right, static, but not exactly a report as I think Ben is thinking + +> +> On Fri, Jul 03, 2009 at 10:50:17PM +0200, Gianluca Montecchi wrote: +> > The goal is to be able to do something like "be html /web/page" to have +> > in the /web/page directory some static html pages that basically are the +> > dump of the be repository, much like ditz have +> +> I think truly interactive frontends like Steve's working on need to be +> build on top of libbe directly, since they'll need to make lots of +> small changes to the database, and it's to slow to be reloading the +> database for every change. Static dumps like my mbox or Gianluca's +> html could just parse the xml output of `be list' and other be +> commands. + +Ok, but if I want to have an html dump that is browseable, I need to parse the +xml. Am I correct ? +If yes, should not be easiear to use directly the libbe ? + + +bye +Gianluca + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/fd7ab206-5937-4ede-9e78-97aff098b677/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/fd7ab206-5937-4ede-9e78-97aff098b677/values new file mode 100644 index 0000000..d4458fd --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/fd7ab206-5937-4ede-9e78-97aff098b677/values @@ -0,0 +1,14 @@ +Alt-id: <200907062238.56930.gian@grys.it> + + +Author: Gianluca Montecchi <gian@grys.it> + + +Content-type: text/plain + + +Date: Mon, 06 Jul 2009 22:38:56 +0200 + + +In-reply-to: 55263144-9775-4b18-ab83-29d66ed91a53 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/values new file mode 100644 index 0000000..5f2d264 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/values @@ -0,0 +1,20 @@ +assigned: Gianluca Montecchi <gian@grys.it> + + +creator: W. Trevor King <wking@drexel.edu> + + +reporter: Gianluca Montecchi <gian@grys.it> + + +severity: wishlist + + +status: fixed + + +summary: Static html report generation + + +time: Tue, 21 Jul 2009 18:43:06 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/301724b1-3853-4aff-8f23-44373df7cf1c/comments/0d8af004-8352-4254-b747-d96a40a5d457/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/301724b1-3853-4aff-8f23-44373df7cf1c/comments/0d8af004-8352-4254-b747-d96a40a5d457/body new file mode 100644 index 0000000..708159c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/301724b1-3853-4aff-8f23-44373df7cf1c/comments/0d8af004-8352-4254-b747-d96a40a5d457/body @@ -0,0 +1 @@ +Implemented diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/301724b1-3853-4aff-8f23-44373df7cf1c/comments/0d8af004-8352-4254-b747-d96a40a5d457/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/301724b1-3853-4aff-8f23-44373df7cf1c/comments/0d8af004-8352-4254-b747-d96a40a5d457/values new file mode 100644 index 0000000..0eaf9c9 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/301724b1-3853-4aff-8f23-44373df7cf1c/comments/0d8af004-8352-4254-b747-d96a40a5d457/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 17:40:08 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/301724b1-3853-4aff-8f23-44373df7cf1c/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/301724b1-3853-4aff-8f23-44373df7cf1c/values new file mode 100644 index 0000000..8704a7e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/301724b1-3853-4aff-8f23-44373df7cf1c/values @@ -0,0 +1,14 @@ +assigned: abentley + + +creator: abentley + + +severity: minor + + +status: fixed + + +summary: 'Per-tree configuration: default-assigneed?' + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/31cd490d-a1c2-4ab3-8284-d80395e34dd2/comments/b2a333f7-eda6-42b9-8940-177f61ca7f48/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/31cd490d-a1c2-4ab3-8284-d80395e34dd2/comments/b2a333f7-eda6-42b9-8940-177f61ca7f48/body new file mode 100644 index 0000000..396c06a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/31cd490d-a1c2-4ab3-8284-d80395e34dd2/comments/b2a333f7-eda6-42b9-8940-177f61ca7f48/body @@ -0,0 +1,51 @@ +$ python test.py +********************************************************************** +File "/home/wking/src/fun/be/libbe/plugin.py", line 31, in libbe.plugin.iter_plugins +Failed example: + "plugin" in [n for n,m in iter_plugins("libbe")] +Exception raised: + Traceback (most recent call last): + File "/usr/lib/python2.5/doctest.py", line 1228, in __run + compileflags, 1) in test.globs + File "<doctest libbe.plugin.iter_plugins[1]>", line 1, in <module> + "plugin" in [n for n,m in iter_plugins("libbe")] + File "/home/wking/src/fun/be/libbe/plugin.py", line 38, in iter_plugins + yield modfile[:-3], my_import(prefix+"."+modfile[:-3]) + File "/home/wking/src/fun/be/libbe/plugin.py", line 21, in my_import + module = __import__(mod_name) + File "/home/wking/src/fun/be/libbe/restconvert.py", line 27, in <module> + from elementtree import ElementTree + ImportError: No module named elementtree +********************************************************************** +1 items had failures: + 1 of 2 in libbe.plugin.iter_plugins +***Test Failed*** 1 failures. +Traceback (most recent call last): + File "test.py", line 32, in <module> + for module in plugin.iter_plugins("libbe"): + File "/home/wking/src/fun/be/libbe/plugin.py", line 38, in iter_plugins + yield modfile[:-3], my_import(prefix+"."+modfile[:-3]) + File "/home/wking/src/fun/be/libbe/plugin.py", line 21, in my_import + module = __import__(mod_name) + File "/home/wking/src/fun/be/libbe/restconvert.py", line 27, in <module> + from elementtree import ElementTree +ImportError: No module named elementtree + + +Looking into ElementTree, I found their webpage: +http://effbot.org/zone/element-index.htm + + It’s common practice to import ElementTree under an alias, both to + minimize typing, and to make it easier to switch between different + implementations: + + $ python + >>> import elementtree.ElementTree as ET + >>> import cElementTree as ET + >>> import lxml.etree as ET + >>> import xml.etree.ElementTree as ET # Python 2.5 + +Using new import style, fall back to old if that fails. +Affected files: + libbe/restconvert.py + Bugs-Everywhere-Web/beweb/formatting.py diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/31cd490d-a1c2-4ab3-8284-d80395e34dd2/comments/b2a333f7-eda6-42b9-8940-177f61ca7f48/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/31cd490d-a1c2-4ab3-8284-d80395e34dd2/comments/b2a333f7-eda6-42b9-8940-177f61ca7f48/values new file mode 100644 index 0000000..2a52700 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/31cd490d-a1c2-4ab3-8284-d80395e34dd2/comments/b2a333f7-eda6-42b9-8940-177f61ca7f48/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 13 Nov 2008 17:27:17 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/31cd490d-a1c2-4ab3-8284-d80395e34dd2/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/31cd490d-a1c2-4ab3-8284-d80395e34dd2/values new file mode 100644 index 0000000..5c9594e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/31cd490d-a1c2-4ab3-8284-d80395e34dd2/values @@ -0,0 +1,14 @@ +creator: wking + + +severity: minor + + +status: fixed + + +summary: elementtree module moved in Python 2.5 + + +time: Thu, 13 Nov 2008 16:45:24 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3438b72c-6244-4f1d-8722-8c8d41484e35/comments/ba96f1c0-ba48-4df8-aaf0-4e3a3144fc46/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3438b72c-6244-4f1d-8722-8c8d41484e35/comments/ba96f1c0-ba48-4df8-aaf0-4e3a3144fc46/body new file mode 100644 index 0000000..288fc29 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3438b72c-6244-4f1d-8722-8c8d41484e35/comments/ba96f1c0-ba48-4df8-aaf0-4e3a3144fc46/body @@ -0,0 +1,11 @@ +It would be nice if we could store tests. + .be/BUGDIR/tests/... +and link them from bugs. + +Then running + test.py BUGDIR/BUG +would run the tests for that particular bug. + +This would provide regression testing via + test.py $(be list --ids --status fixed) + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3438b72c-6244-4f1d-8722-8c8d41484e35/comments/ba96f1c0-ba48-4df8-aaf0-4e3a3144fc46/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3438b72c-6244-4f1d-8722-8c8d41484e35/comments/ba96f1c0-ba48-4df8-aaf0-4e3a3144fc46/values new file mode 100644 index 0000000..691163d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3438b72c-6244-4f1d-8722-8c8d41484e35/comments/ba96f1c0-ba48-4df8-aaf0-4e3a3144fc46/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 03 Jan 2010 16:32:13 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3438b72c-6244-4f1d-8722-8c8d41484e35/comments/e7d8343a-bd85-4359-bcda-bf0dc1e8177a/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3438b72c-6244-4f1d-8722-8c8d41484e35/comments/e7d8343a-bd85-4359-bcda-bf0dc1e8177a/body new file mode 100644 index 0000000..b6a0435 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3438b72c-6244-4f1d-8722-8c8d41484e35/comments/e7d8343a-bd85-4359-bcda-bf0dc1e8177a/body @@ -0,0 +1,42 @@ +> It would be nice if we could store tests. +> .be/BUGDIR/tests/... +> and link them from bugs. + +Better: have them be comments with a TEST tag. + +The mime type could hint at the execution mechanism: + text/x-python + application/x-sh + ... + +> Then running +> test.py BUGDIR/BUG +> would run the tests for that particular bug. +> +> This would provide regression testing via +> test.py $(be list --ids --status fixed) + +This should be a 'test' command (libbe.command.test.Test), since +people will want to test bugs for their own projects, and out current +test.py is for testing BE specifically. It should be + be test BUGDIR/BUG + be test $(be list --ids --status fixed) + +We _should_ add be + test $(be list --ids --status fixed) +to test.py for regression testing. + +This whole thing would make the fixed/closed distinction more clear, +since fixed bugs would get tests run and expect success, while closed +bugs' tests would be skipped. + +Finally, if users are submitting tests on their own, it would be a +good idea to sandbox them, but a portable way for sandboxing scripts +sounds very complicated. It would probably be easier to sandbox +python scripts, but I don't know what that would look like... + +A work around would be to allow users to post tests, but not allow +them to set the TEST flag. Then the bugdir maintainer could set the +flag themselves once they'd vetted the test. Much uglier than +sandboxing, but also much more easily implemented. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3438b72c-6244-4f1d-8722-8c8d41484e35/comments/e7d8343a-bd85-4359-bcda-bf0dc1e8177a/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3438b72c-6244-4f1d-8722-8c8d41484e35/comments/e7d8343a-bd85-4359-bcda-bf0dc1e8177a/values new file mode 100644 index 0000000..3ddceba --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3438b72c-6244-4f1d-8722-8c8d41484e35/comments/e7d8343a-bd85-4359-bcda-bf0dc1e8177a/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 31 Jan 2010 17:36:52 +0000 + + +In-reply-to: ba96f1c0-ba48-4df8-aaf0-4e3a3144fc46 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3438b72c-6244-4f1d-8722-8c8d41484e35/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3438b72c-6244-4f1d-8722-8c8d41484e35/values new file mode 100644 index 0000000..5c72e5f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3438b72c-6244-4f1d-8722-8c8d41484e35/values @@ -0,0 +1,14 @@ +creator: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: open + + +summary: Attach tests to bugs + + +time: Sun, 03 Jan 2010 16:23:42 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3613e6e9-db9e-4775-8914-f31f0b4b81ac/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3613e6e9-db9e-4775-8914-f31f0b4b81ac/values new file mode 100644 index 0000000..70ec5f5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3613e6e9-db9e-4775-8914-f31f0b4b81ac/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: auto-add files to revision control + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/372f8a5c-a1ce-4b07-a7b1-f409033a7eec/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/372f8a5c-a1ce-4b07-a7b1-f409033a7eec/values new file mode 100644 index 0000000..ec315a3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/372f8a5c-a1ce-4b07-a7b1-f409033a7eec/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: implement severity on bug creation + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9aa88bbd-71d0-44fa-804d-3562171f9539/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9aa88bbd-71d0-44fa-804d-3562171f9539/body new file mode 100644 index 0000000..5dde31f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9aa88bbd-71d0-44fa-804d-3562171f9539/body @@ -0,0 +1 @@ +Merged from bug 4f7a4c3b-31e3-4023-8c9d-e67f627a34f0
\ No newline at end of file diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9aa88bbd-71d0-44fa-804d-3562171f9539/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9aa88bbd-71d0-44fa-804d-3562171f9539/values new file mode 100644 index 0000000..ab2a567 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9aa88bbd-71d0-44fa-804d-3562171f9539/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 13:44:33 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9e33512e-e3cb-42ec-bc99-8e77587d0d3f/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9e33512e-e3cb-42ec-bc99-8e77587d0d3f/body new file mode 100644 index 0000000..00d6eb7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9e33512e-e3cb-42ec-bc99-8e77587d0d3f/body @@ -0,0 +1,4 @@ +Trees created with tla make dotfiles precious by default, so add +"source ^\.be$" to the root .arch-inventory. + +Optionally, only do this if inventory --source doesn't list .be diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9e33512e-e3cb-42ec-bc99-8e77587d0d3f/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9e33512e-e3cb-42ec-bc99-8e77587d0d3f/values new file mode 100644 index 0000000..f692e19 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9e33512e-e3cb-42ec-bc99-8e77587d0d3f/values @@ -0,0 +1,8 @@ +Author: abentley + + +Content-type: text/plain + + +Date: Tue, 17 May 2005 13:42:52 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/b76434a3-5cf9-4d2c-820b-64444289c09f/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/b76434a3-5cf9-4d2c-820b-64444289c09f/body new file mode 100644 index 0000000..f03ef32 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/b76434a3-5cf9-4d2c-820b-64444289c09f/body @@ -0,0 +1 @@ +Fixed with Arch._adjust_naming_conventions on a per-tree basis instead. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/b76434a3-5cf9-4d2c-820b-64444289c09f/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/b76434a3-5cf9-4d2c-820b-64444289c09f/values new file mode 100644 index 0000000..a0c9a34 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/b76434a3-5cf9-4d2c-820b-64444289c09f/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 13:46:32 +0000 + + +In-reply-to: 9e33512e-e3cb-42ec-bc99-8e77587d0d3f + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/values new file mode 100644 index 0000000..f59cf90 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: Ensure .be is source in Arch + + +time: Tue, 17 May 2005 13:39:02 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/287d3cc1-1cd0-449a-b280-87c529e33951/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/287d3cc1-1cd0-449a-b280-87c529e33951/body new file mode 100644 index 0000000..53456f6 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/287d3cc1-1cd0-449a-b280-87c529e33951/body @@ -0,0 +1,10 @@ +Hmm, perhaps my thinking has been too revision-centric. I'm not +really sure what other level of granularity is appropriate though. +Both notifications and commits should be generated on a "per-session" +level, so maybe I'll just ignore Arch and Mercurial (for whom revising +history is difficult, so per-session commits can be more work) for the +time being ;). + +In that case, _every_ commit will be a + notify-since <revision-id> +sort of change, so I'll just use libbe.diff :). diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/287d3cc1-1cd0-449a-b280-87c529e33951/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/287d3cc1-1cd0-449a-b280-87c529e33951/values new file mode 100644 index 0000000..797a274 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/287d3cc1-1cd0-449a-b280-87c529e33951/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Wed, 22 Jul 2009 19:07:28 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/303986f2-0b17-4589-bf76-ed1461699c3e/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/303986f2-0b17-4589-bf76-ed1461699c3e/body new file mode 100644 index 0000000..df90918 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/303986f2-0b17-4589-bf76-ed1461699c3e/body @@ -0,0 +1,16 @@ +Perhaps something like + be-handle-mail --notify-since <revision-id> +to tell subscribers about changes since the specified revision. + +This would duplicate mail to P in our first example above, but that's +not too annoying, and P might _want_ to know what R had merged from Q. + +On the other hand it would be annoying if 10 other repos merged Q and +ran the notification. + +We could make the subscription something like + subscribe BUG-ID HOST-LIST +e.g. + subscribe 1234 bugseverywhere.org,fancy_branch.com + subscribe abcd * +To allow users to whitelist hosts they want updates from. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/303986f2-0b17-4589-bf76-ed1461699c3e/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/303986f2-0b17-4589-bf76-ed1461699c3e/values new file mode 100644 index 0000000..e19bf0b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/303986f2-0b17-4589-bf76-ed1461699c3e/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 21 Jul 2009 19:52:25 +0000 + + +In-reply-to: 950ac308-f3e1-4956-885a-e79ce3025fd5 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/478443b3-dd69-4719-b79a-b1279f75b8e4/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/478443b3-dd69-4719-b79a-b1279f75b8e4/body new file mode 100644 index 0000000..8842587 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/478443b3-dd69-4719-b79a-b1279f75b8e4/body @@ -0,0 +1,5 @@ +"all" and "new" might be valid shortnames? + +Nope, UUID string representations are restricted to hex (0-9a-f) and +"-" as per RFC 4122 section 3. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/478443b3-dd69-4719-b79a-b1279f75b8e4/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/478443b3-dd69-4719-b79a-b1279f75b8e4/values new file mode 100644 index 0000000..74d7d97 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/478443b3-dd69-4719-b79a-b1279f75b8e4/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 21 Jul 2009 19:53:02 +0000 + + +In-reply-to: 85a2d1ac-200a-4ae7-841f-9f4e87795dbf + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/85a2d1ac-200a-4ae7-841f-9f4e87795dbf/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/85a2d1ac-200a-4ae7-841f-9f4e87795dbf/body new file mode 100644 index 0000000..99d9cc3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/85a2d1ac-200a-4ae7-841f-9f4e87795dbf/body @@ -0,0 +1,8 @@ +Obviously via the control interface: + subscribe #BUG-ID + subscribe new + subscribe all + unsubscribe #BUG-ID + ... +Implemented via .extra_strings, although we'll need +BugDir.extra_strings for the repo-wide new/all. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/85a2d1ac-200a-4ae7-841f-9f4e87795dbf/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/85a2d1ac-200a-4ae7-841f-9f4e87795dbf/values new file mode 100644 index 0000000..ae4672b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/85a2d1ac-200a-4ae7-841f-9f4e87795dbf/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 21 Jul 2009 19:34:20 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/950ac308-f3e1-4956-885a-e79ce3025fd5/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/950ac308-f3e1-4956-885a-e79ce3025fd5/body new file mode 100644 index 0000000..890a4b6 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/950ac308-f3e1-4956-885a-e79ce3025fd5/body @@ -0,0 +1,10 @@ +This creates an interesting situation: + Person P subscribes to bug B in repo R. + Repo S merges repo R. + Person Q comments on B in S. + S notifies P :). +which is nice. However + Person P subscribes to bug B in repo R. + Person Q comments on B in repo S. + R merges S. + P never notified about Q's comment. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/950ac308-f3e1-4956-885a-e79ce3025fd5/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/950ac308-f3e1-4956-885a-e79ce3025fd5/values new file mode 100644 index 0000000..d9fcf73 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/950ac308-f3e1-4956-885a-e79ce3025fd5/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 21 Jul 2009 19:34:32 +0000 + + +In-reply-to: 85a2d1ac-200a-4ae7-841f-9f4e87795dbf + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/f72f8640-2e50-471e-aebe-0ddb8cdd5a2a/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/f72f8640-2e50-471e-aebe-0ddb8cdd5a2a/body new file mode 100644 index 0000000..3c95f19 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/f72f8640-2e50-471e-aebe-0ddb8cdd5a2a/body @@ -0,0 +1,2 @@ +The intereface changed a bit as I implemented it. See "be help +subscribe" for details. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/f72f8640-2e50-471e-aebe-0ddb8cdd5a2a/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/f72f8640-2e50-471e-aebe-0ddb8cdd5a2a/values new file mode 100644 index 0000000..f42f8ad --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/f72f8640-2e50-471e-aebe-0ddb8cdd5a2a/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Wed, 22 Jul 2009 18:54:06 +0000 + + +In-reply-to: 85a2d1ac-200a-4ae7-841f-9f4e87795dbf + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/values new file mode 100644 index 0000000..aa22fab --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/values @@ -0,0 +1,20 @@ +assigned: W. Trevor King <wking@drexel.edu> + + +creator: W. Trevor King <wking@drexel.edu> + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: fixed + + +summary: 'subscribe/unsubscribe (bug #..., "new bugs", "all", etc.)' + + +time: Tue, 21 Jul 2009 19:27:04 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/40dac9af-951e-4b98-8779-9ba02c37f8a1/comments/e1ff6c81-37d8-43ee-9dcf-17a89e07556a/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/40dac9af-951e-4b98-8779-9ba02c37f8a1/comments/e1ff6c81-37d8-43ee-9dcf-17a89e07556a/body new file mode 100644 index 0000000..20b3da3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/40dac9af-951e-4b98-8779-9ba02c37f8a1/comments/e1ff6c81-37d8-43ee-9dcf-17a89e07556a/body @@ -0,0 +1,3 @@ +Calls to Popen() while running `test.py` raised OSError because of +missing binaries (tla was not installed). Added catches to produce +more useful error messages in the backtrace. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/40dac9af-951e-4b98-8779-9ba02c37f8a1/comments/e1ff6c81-37d8-43ee-9dcf-17a89e07556a/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/40dac9af-951e-4b98-8779-9ba02c37f8a1/comments/e1ff6c81-37d8-43ee-9dcf-17a89e07556a/values new file mode 100644 index 0000000..5e1f3de --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/40dac9af-951e-4b98-8779-9ba02c37f8a1/comments/e1ff6c81-37d8-43ee-9dcf-17a89e07556a/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 13 Nov 2008 15:58:18 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/40dac9af-951e-4b98-8779-9ba02c37f8a1/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/40dac9af-951e-4b98-8779-9ba02c37f8a1/values new file mode 100644 index 0000000..59d0695 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/40dac9af-951e-4b98-8779-9ba02c37f8a1/values @@ -0,0 +1,14 @@ +creator: wking + + +severity: minor + + +status: fixed + + +summary: Popen OSErrors not caught + + +time: Thu, 13 Nov 2008 15:54:45 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/29ad0d9e-c05b-4793-bb8b-e8bf237f51b3/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/29ad0d9e-c05b-4793-bb8b-e8bf237f51b3/body new file mode 100644 index 0000000..e39beb0 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/29ad0d9e-c05b-4793-bb8b-e8bf237f51b3/body @@ -0,0 +1,24 @@ +> Currently, the code and interface of Bugs Everywhere speaks loosely +> about the term “RCS”. Sometimes it means “revision control system” +> referring in general to these types of system, and sometimes it talks +> about GNU RCS, a specific system. + +I don't think we ever rever to GNU RCS. Our current libbe.rcs.RCS +default implementation is a "don't version" backend for BE, but +perhaps this is what you're refereing to. + +> I propose that “Version Control System” (“VCS”) has emerged as a +> consensus term to refer to such systems in general, with no specific +> reference to any particular system. + +Fair enough. + +> This will change some interface (e.g. the ‘rcs_name’ configuration +> setting, and some of the methods on objects), but making this change +> while Bugs Everywhere is small will be much less painful than making it +> later. + +Hmm, we really need a method for upgrading the on-disk BugDir version. +It's hard when you need to maintain backwards compatibilty with +earlier versions in the VCS history.... + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/29ad0d9e-c05b-4793-bb8b-e8bf237f51b3/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/29ad0d9e-c05b-4793-bb8b-e8bf237f51b3/values new file mode 100644 index 0000000..7eb5b45 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/29ad0d9e-c05b-4793-bb8b-e8bf237f51b3/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 03 Aug 2009 23:26:22 +0000 + + +In-reply-to: a92f97a4-e9fe-43f7-bf56-5862b03a2641 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/a92f97a4-e9fe-43f7-bf56-5862b03a2641/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/a92f97a4-e9fe-43f7-bf56-5862b03a2641/body new file mode 100644 index 0000000..f9c166b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/a92f97a4-e9fe-43f7-bf56-5862b03a2641/body @@ -0,0 +1,33 @@ +Howdy all, + +Currently, the code and interface of Bugs Everywhere speaks loosely +about the term “RCS”. Sometimes it means “revision control system” +referring in general to these types of system, and sometimes it talks +about GNU RCS, a specific system. + +I propose that “Version Control System” (“VCS”) has emerged as a +consensus term to refer to such systems in general, with no specific +reference to any particular system. + +So I'd like to modify the Bugs Everywhere code to disambiguate: the term +“VCS” will be used consistently to refer to version control systems in +general, and “RCS” will only ever refer to GNU RCS. + +This will change some interface (e.g. the ‘rcs_name’ configuration +setting, and some of the methods on objects), but making this change +while Bugs Everywhere is small will be much less painful than making it +later. + +Any objections? Any alternative suggestions? + +-- + \ “I watched the Indy 500, and I was thinking that if they left | + `\ earlier they wouldn't have to go so fast.” —Steven Wright | +_o__) | +Ben Finney + + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/a92f97a4-e9fe-43f7-bf56-5862b03a2641/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/a92f97a4-e9fe-43f7-bf56-5862b03a2641/values new file mode 100644 index 0000000..5f3cf73 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/a92f97a4-e9fe-43f7-bf56-5862b03a2641/values @@ -0,0 +1,11 @@ +Alt-id: <87d49879v7.fsf@benfinney.id.au> + + +Author: Ben Finney <ben@benfinney.id.au> + + +Content-type: text/plain + + +Date: Sat, 13 Jun 2009 19:37:16 +1000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/values new file mode 100644 index 0000000..d88c668 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/values @@ -0,0 +1,21 @@ +creator: W. Trevor King <wking@drexel.edu> + + +extra_strings: +- BLOCKED-BY:51930348-9ccc-4165-af41-6c7450de050e + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: fixed + + +summary: 'Terminology: Version control system vs. RCS' + + +time: Mon, 03 Aug 2009 23:10:02 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/47c8fd5f-1f5a-4048-bef7-bb4c9a37c411/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/47c8fd5f-1f5a-4048-bef7-bb4c9a37c411/values new file mode 100644 index 0000000..f008963 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/47c8fd5f-1f5a-4048-bef7-bb4c9a37c411/values @@ -0,0 +1,20 @@ +creator: W. Trevor King <wking@drexel.edu> + + +extra_strings: +- BLOCKED-BY:f51dc5a7-37b7-4ce1-859a-b7cb58be6494 +- BLOCKS:4fc71206-4285-417f-8a3c-ed6fb31bbbda +- BLOCKS:bd0ebb56-fb46-45bc-af08-1e4a94e8ef3c + + +severity: target + + +status: fixed + + +summary: '0.1' + + +time: Sun, 06 Dec 2009 00:37:15 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/body new file mode 100644 index 0000000..dfcf82c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/body @@ -0,0 +1,29 @@ +I was having problems with `python test.py bugdir` with the Arch +backend. Commits were failing with `archive not registered'. + +Adding some trace information to arch.Arch._rcs_init() and +._rcs_cleanup() (the traceback module is great :p), I found +that the problem was coming from bugdir.BugDir.guess_rcs(). + +The Arch backend deletes any auto-created archives when it is cleaned +up (RCS.__del__ -> RCS.cleanup -> Arch._rcs_cleanup). This means that +whatever instance is used to init the archive in guess_rcs() must be +kept around. I had been doing: + * installed_rcs() -> Arch-instance-A + * Arch-instance-A.init() + * store Arch-instnance-A.name as bugdir.rcs_name + * future calls to bugdir.rcs get new instance Arch-instance-B + * eventually Arch-instance-A cleaned up + * archive dissapears & tests crash + +I switched things around so .rcs is the `master attribute' and +.rcs_name follows it. Now just save whichever rcs you used to init +your archive as .rcs. + +In order to implement the fix, I had to tweak the memory/file-system +interaction a bit. Instead of saving the settings *every*time* a +setting_property changed, we now save only if the .be file exists. +This file serves as a 'file-system-bugdir-active' flag. Before it is +created (e.g., by a .save()), the BugDir lives purely in memory, and +can freely go about configuring .rcs, .rcs_name, etc until it get's +to the point where it's ready to go to disk. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/values new file mode 100644 index 0000000..e9bbdac --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 22 Nov 2008 18:53:20 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/496edad5-1484-413a-bc68-4b01274a65eb/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/496edad5-1484-413a-bc68-4b01274a65eb/values new file mode 100644 index 0000000..2ef6ea3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/496edad5-1484-413a-bc68-4b01274a65eb/values @@ -0,0 +1,14 @@ +creator: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: fixed + + +summary: Early del-cleanup with Arch backend + + +time: Sat, 22 Nov 2008 18:38:32 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/0ac3c4cb-90e3-4b67-b6cb-1186d5d66240/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/0ac3c4cb-90e3-4b67-b6cb-1186d5d66240/body new file mode 100644 index 0000000..ab2dc28 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/0ac3c4cb-90e3-4b67-b6cb-1186d5d66240/body @@ -0,0 +1 @@ +Merged into bug ae998b27-a11b-4243-abf6-11841e5b8242
\ No newline at end of file diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/0ac3c4cb-90e3-4b67-b6cb-1186d5d66240/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/0ac3c4cb-90e3-4b67-b6cb-1186d5d66240/values new file mode 100644 index 0000000..63842d1 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/0ac3c4cb-90e3-4b67-b6cb-1186d5d66240/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 17:05:50 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/942cd941-583d-4020-99e4-80de7e836129/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/942cd941-583d-4020-99e4-80de7e836129/body new file mode 100644 index 0000000..d0b8404 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/942cd941-583d-4020-99e4-80de7e836129/body @@ -0,0 +1 @@ +Implemented. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/942cd941-583d-4020-99e4-80de7e836129/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/942cd941-583d-4020-99e4-80de7e836129/values new file mode 100644 index 0000000..6a4005c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/942cd941-583d-4020-99e4-80de7e836129/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 15:42:07 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/values new file mode 100644 index 0000000..37197a7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: Do we need a severity between serious and minor? EG "Moderate"? + + +time: Wed, 25 Jan 2006 23:14:07 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4f7a4c3b-31e3-4023-8c9d-e67f627a34f0/comments/a8f35fca-8a15-4833-b568-326f0cc89bfa/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4f7a4c3b-31e3-4023-8c9d-e67f627a34f0/comments/a8f35fca-8a15-4833-b568-326f0cc89bfa/body new file mode 100644 index 0000000..fb08206 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4f7a4c3b-31e3-4023-8c9d-e67f627a34f0/comments/a8f35fca-8a15-4833-b568-326f0cc89bfa/body @@ -0,0 +1 @@ +Merged into bug 381555eb-f2e3-4ef0-8303-d759c00b390a
\ No newline at end of file diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4f7a4c3b-31e3-4023-8c9d-e67f627a34f0/comments/a8f35fca-8a15-4833-b568-326f0cc89bfa/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4f7a4c3b-31e3-4023-8c9d-e67f627a34f0/comments/a8f35fca-8a15-4833-b568-326f0cc89bfa/values new file mode 100644 index 0000000..ab2a567 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4f7a4c3b-31e3-4023-8c9d-e67f627a34f0/comments/a8f35fca-8a15-4833-b568-326f0cc89bfa/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 13:44:33 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4f7a4c3b-31e3-4023-8c9d-e67f627a34f0/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4f7a4c3b-31e3-4023-8c9d-e67f627a34f0/values new file mode 100644 index 0000000..b97cb07 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4f7a4c3b-31e3-4023-8c9d-e67f627a34f0/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: Automatically set .be as source for arch + + +time: Thu, 07 Apr 2005 16:07:51 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4fc71206-4285-417f-8a3c-ed6fb31bbbda/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4fc71206-4285-417f-8a3c-ed6fb31bbbda/values new file mode 100644 index 0000000..4eebcc4 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4fc71206-4285-417f-8a3c-ed6fb31bbbda/values @@ -0,0 +1,20 @@ +creator: W. Trevor King <wking@drexel.edu> + + +extra_strings: +- BLOCKED-BY:47c8fd5f-1f5a-4048-bef7-bb4c9a37c411 +- BLOCKED-BY:ee681951-f254-43d3-a53a-1b36ae415d5c +- BLOCKS:bd0ebb56-fb46-45bc-af08-1e4a94e8ef3c + + +severity: target + + +status: closed + + +summary: patch-52 + + +time: Sun, 06 Dec 2009 00:37:16 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/body new file mode 100644 index 0000000..16e1919 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/body @@ -0,0 +1,2 @@ +Note that VISUAL means interactive editors like vi, emacs. It compares with +EDITOR, which originally meant line editors like ex (or edlin?). diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/values new file mode 100644 index 0000000..3ea62d2 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/values @@ -0,0 +1,11 @@ +Author: abentley + + +Content-type: text/plain + + +Date: Mon, 16 Jul 2007 15:23:47 +0000 + + +In-reply-to: e173c09a-1b3e-4d8a-a86a-6b8c94a76247 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/body new file mode 100644 index 0000000..9987f21 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/body @@ -0,0 +1,2 @@ +the $VISUAL environment variable is common for setting a users preferred +editor. It would be nice if this would be supported by be. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/values new file mode 100644 index 0000000..3292da3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/values @@ -0,0 +1,8 @@ +Author: jelmer + + +Content-type: text/plain + + +Date: Sun, 15 Jul 2007 13:34:52 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/values new file mode 100644 index 0000000..c471b0f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/values @@ -0,0 +1,14 @@ +creator: jelmer + + +severity: minor + + +status: fixed + + +summary: should check not just EDITOR but also VISUAL. + + +time: Sun, 15 Jul 2007 13:33:03 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/d304f93b-faf2-477e-9ff8-c77e301fd9f9/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/d304f93b-faf2-477e-9ff8-c77e301fd9f9/body new file mode 100644 index 0000000..34d37e5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/d304f93b-faf2-477e-9ff8-c77e301fd9f9/body @@ -0,0 +1,30 @@ +Added libbe/upgrade.py to handle upgrading on-disk bugdirs. + +When upgrade.BUGDIR_DISK_VERSION changes, a series of Updater +classes handle the upgrade. For example, if + BUGDIR_DISK_VERSIONS = ["v1", "v2", "v3"] +and the on-disk version is "v1", you should have defined classes + class Upgrade_1_to_2 (Upgrader): + initial_version = "v1" + final_version = "v2" + def _upgrade(): + .... + class Upgrade_2_to_3 (Upgrader): + initial_version = "v2" + final_version = "v3" + def _upgrade(): + .... +and added them to upgraders: + upgraders = [Upgrade_1_to_2, Upgrade_2_to_3] +If the on-disk version is v2, then only Upgrade_2_to_3.upgrade() is +run. If the on-disk version is v1, then Upgrade_1_to_2.upgrade() is +run, followed by Upgrade_2_to_3.upgrade(). + +You can optionally define shortcut upgrades (e.g. Upgrade_1_to_3) for +efficiency or to avoid data loss. + +This upgrade occurs during BugDir.load(), which is called by +BugDir.__init__(from_disk=True), before any processing of the on-disk +data except for the access of .be/version to determine if an upgrade +was necessary. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/d304f93b-faf2-477e-9ff8-c77e301fd9f9/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/d304f93b-faf2-477e-9ff8-c77e301fd9f9/values new file mode 100644 index 0000000..b296bff --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/d304f93b-faf2-477e-9ff8-c77e301fd9f9/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 31 Aug 2009 16:29:50 +0000 + + +In-reply-to: f1479ecf-4154-4cd4-bbd6-0ed6275b9f98 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/f1479ecf-4154-4cd4-bbd6-0ed6275b9f98/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/f1479ecf-4154-4cd4-bbd6-0ed6275b9f98/body new file mode 100644 index 0000000..372a655 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/f1479ecf-4154-4cd4-bbd6-0ed6275b9f98/body @@ -0,0 +1,16 @@ +There is no obvious means of using +".be/version"/"libbe.bugdir.TREE_VERSION_STRING". In the past I've +worked around this by keeping all the disk-reading backwards +compatible (e.g. homemade mapfile -> YAML, the "From" hack in +libbe.comment.Comment.load_settings, possibly others). However, this +is not the road to easily maintainable code. + +Most projects only need to maintain backwards compatibility with the +last few versions of their disk cache, to allow users an easy upgrade +path. The difficulties come with "be diff", which must be able to +read _every_ disk-image of the bugdir ever committed into something +comparible with the current cutting edge. This makes sweeping changes +very difficult. VCSs themselves avoid this by never showing their +disk-cache to another program, but we've shown ours to the VCS, and +it's difficult (or impossible, depending on the VCS) to change history +to match the current format. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/f1479ecf-4154-4cd4-bbd6-0ed6275b9f98/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/f1479ecf-4154-4cd4-bbd6-0ed6275b9f98/values new file mode 100644 index 0000000..3d4d9df --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/f1479ecf-4154-4cd4-bbd6-0ed6275b9f98/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 16 Aug 2009 19:07:06 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/51930348-9ccc-4165-af41-6c7450de050e/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/51930348-9ccc-4165-af41-6c7450de050e/values new file mode 100644 index 0000000..75a191c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/51930348-9ccc-4165-af41-6c7450de050e/values @@ -0,0 +1,22 @@ +creator: W. Trevor King <wking@drexel.edu> + + +extra_strings: +- BLOCKS:22b6f620-d2f7-42a5-a02e-145733a4e366 +- BLOCKS:427e0ca7-17f5-4a5a-8c68-98cc111a2495 + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: fixed + + +summary: Upgrade path for on-disk representation + + +time: Sun, 16 Aug 2009 19:05:59 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/4c50ca0b-a08f-4723-b00d-4bf342cf86b6/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/4c50ca0b-a08f-4723-b00d-4bf342cf86b6/body new file mode 100644 index 0000000..90b386a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/4c50ca0b-a08f-4723-b00d-4bf342cf86b6/body @@ -0,0 +1,20 @@ +I'm all for flexibility, so long as it doesn't require too much +hackery to implement it. You'll have two problems: + + * Determining what to commit. + + You'd have to have RCS keep a log of all versioned files it + touched, and extend .commit() to accept the keyword list "files" + and commit only those files. This is doable, but maybe not worth + the trouble. + + * Generating meaningful commit messages. + + You'd have to add this functionality to each command (and future + commands). + +This would probably not be a good idea for the Arch and Mercurial +backends, since they have a limited ability to rewrite history when +you screw up your commit message (as far as I can tell). Mercurial +does have "hg rollback", but it only works once, and lots of +typo-correction commits would just make the logs awkward. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/4c50ca0b-a08f-4723-b00d-4bf342cf86b6/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/4c50ca0b-a08f-4723-b00d-4bf342cf86b6/values new file mode 100644 index 0000000..eb90c47 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/4c50ca0b-a08f-4723-b00d-4bf342cf86b6/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Fri, 24 Jul 2009 12:33:58 +0000 + + +In-reply-to: b17a561a-6100-490e-84eb-d1ae4b617940 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/79fb6ef2-176c-45c0-b898-59c3c3e0aafe/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/79fb6ef2-176c-45c0-b898-59c3c3e0aafe/body new file mode 100644 index 0000000..3ed77e7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/79fb6ef2-176c-45c0-b898-59c3c3e0aafe/body @@ -0,0 +1,13 @@ +> * Determining what to commit. +> +> You'd have to have RCS keep a log of all versioned files it +> touched, and extend .commit() to accept the keyword list "files" +> and commit only those files. This is doable, but maybe not worth +> the trouble. + +On the other hand, just attemting to commit everything after each +command would make it nice and easy to commit bug fixes: + be --auto-commit status XYZ fixed +which would commit whatever changes you had outstanding with an +appropriate commit message. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/79fb6ef2-176c-45c0-b898-59c3c3e0aafe/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/79fb6ef2-176c-45c0-b898-59c3c3e0aafe/values new file mode 100644 index 0000000..b3dba3f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/79fb6ef2-176c-45c0-b898-59c3c3e0aafe/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 06 Dec 2009 21:45:15 +0000 + + +In-reply-to: 4c50ca0b-a08f-4723-b00d-4bf342cf86b6 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/b17a561a-6100-490e-84eb-d1ae4b617940/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/b17a561a-6100-490e-84eb-d1ae4b617940/body new file mode 100644 index 0000000..c88a838 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/b17a561a-6100-490e-84eb-d1ae4b617940/body @@ -0,0 +1,9 @@ +... +Also, why doesn't be commit after it takes an action? I think it's +kinda weird that I have to commit after creating a new bug. +... + +as posted in + http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=477125 + on + Fri, 12 Jun 2009 17:03:02 +0200 diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/b17a561a-6100-490e-84eb-d1ae4b617940/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/b17a561a-6100-490e-84eb-d1ae4b617940/values new file mode 100644 index 0000000..d9d45f7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/b17a561a-6100-490e-84eb-d1ae4b617940/values @@ -0,0 +1,8 @@ +Author: Martin F Krafft <madduck@debian.org> + + +Content-type: text/plain + + +Date: Fri, 24 Jul 2009 12:09:02 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/values new file mode 100644 index 0000000..5b332ed --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/values @@ -0,0 +1,21 @@ +creator: W. Trevor King <wking@drexel.edu> + + +extra_strings: +- BLOCKED-BY:5fb11e65-68a0-4015-b404-737238299cdc + + +reporter: Martin F Krafft <madduck@debian.org> + + +severity: wishlist + + +status: open + + +summary: Allow autocommit option for command line interface? + + +time: Fri, 24 Jul 2009 12:04:08 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/0c40c13a-3515-4b45-a8c3-142cceab9254/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/0c40c13a-3515-4b45-a8c3-142cceab9254/body new file mode 100644 index 0000000..fa9e963 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/0c40c13a-3515-4b45-a8c3-142cceab9254/body @@ -0,0 +1,36 @@ +* W. Trevor King (wking@drexel.edu) wrote: +> One problem is that we don't actually have "releases". People just +> clone a branch, install, and go. + + This is actually the main reason I've manually mirrored the tree in +the past, so that users of our projects can get BE. If tarballs were +available I probably wouldn't even bother, but bzr really isn't a nice +dependency for just submitting/commenting on bugs. + + Isn't there a bzr web interface that at least supports creating +tarballs/zips? It is pretty standard functionality for most other VCS' +web interfaces so I'm guessing there must be, but loggerhead seems not +to support it. + + If it is a case of not having the hardware to host a more featureful +web UI I may be able to offer some assistance. + +> If you're worried about stability, just clone from a more stable branch +> (i.e., Chris' trunk). I think > this is good for distributed development, +> but maybe makes it hard to package into a conventional release-based system. +> With the bzr patch number in setup.py as the patch release number, I would be +> releasing my 0.1.363 while Chris releases his 0.1.314, even though they're at +> about the same point. I would rather be releasing my +> 0.1.20090714121347 +> while Chris releases his +> 0.1.20090713154540 +> Since then the similarity is clearer. + + Both approaches seem pretty odd to me, as a user you would have no +idea if 0.1.200910302359 has the fixes you required in a release you +were using that was numbered 0.1.200907141554. Surely you'd at least be +{pre,suf}fixing a branch name to the version. + +Thanks, + +James diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/0c40c13a-3515-4b45-a8c3-142cceab9254/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/0c40c13a-3515-4b45-a8c3-142cceab9254/values new file mode 100644 index 0000000..e7077e7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/0c40c13a-3515-4b45-a8c3-142cceab9254/values @@ -0,0 +1,14 @@ +Alt-id: <20090714142942.GA5717@ukfsn.org> + + +Author: James Rowe <jnrowe@gmail.com> + + +Content-type: text/plain + + +Date: Tue, 14 Jul 2009 15:29:42 +0100 + + +In-reply-to: ea01c122-e629-4d5c-afa7-b180f4a8748b + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1847f1f8-525a-42c4-ae2b-e9377459d2a6/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1847f1f8-525a-42c4-ae2b-e9377459d2a6/body new file mode 100644 index 0000000..8c890f3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1847f1f8-525a-42c4-ae2b-e9377459d2a6/body @@ -0,0 +1,27 @@ +"W. Trevor King" <wking@drexel.edu> writes: + +> On Tue, Nov 17, 2009 at 01:41:26PM -0300, Nicolas Alvarez wrote: +> > I'm using the latest version available on Debian +> > (0.0.193+bzr.r217-2). I should ask for an updated package... +> +[…] + +> There is also an outstanding Debian bug for updating the Debian package +> http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=544515 +> so there may be a more current package on the way, but I don't know +> about timeframes for that sort of thing. + +It would make it much easier on the Debian package maintainer if the +Bugs Everywhere project would make conventional tarball releases, with +conventional version numbers, with a changelog describing what has +changed between versions. + +Trying to maintain a package of a project that is only made available by +undifferentiated VCS revision numbers is a lot more effort, and so +doesn't happen very often. + +-- + \ “Roll dice!” “Why?” “Shut up! I don't need your fucking | + `\ *input*, I need you to roll dice!” —Luke Crane, demonstrating | +_o__) his refined approach to play testing, 2009 | +Ben Finney diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1847f1f8-525a-42c4-ae2b-e9377459d2a6/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1847f1f8-525a-42c4-ae2b-e9377459d2a6/values new file mode 100644 index 0000000..3b45fbf --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1847f1f8-525a-42c4-ae2b-e9377459d2a6/values @@ -0,0 +1,11 @@ +Alt-id: <87d43gn8ju.fsf_-_@benfinney.id.au> + + +Author: Ben Finney <bignose+hates-spam@benfinney.id.au> + + +Content-type: text/plain + + +Date: Wed, 18 Nov 2009 13:30:29 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1f40efc1-6efc-4dd8-bdd2-97907e5aa624/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1f40efc1-6efc-4dd8-bdd2-97907e5aa624/body new file mode 100644 index 0000000..7e1434b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1f40efc1-6efc-4dd8-bdd2-97907e5aa624/body @@ -0,0 +1,115 @@ +On Tue, Jul 14, 2009 at 03:29:42PM +0100, James Rowe wrote: +> * W. Trevor King (wking@drexel.edu) wrote: +> > One problem is that we don't actually have "releases". People just +> > clone a branch, install, and go. +> +> This is actually the main reason I've manually mirrored the tree in +> the past, so that users of our projects can get BE. If tarballs were +> available I probably wouldn't even bother, but bzr really isn't a nice +> dependency for just submitting/commenting on bugs. + +Fair enough. It will be good when we get a commit-able web interface +and/or email interface going. + +> Isn't there a bzr web interface that at least supports creating +> tarballs/zips? It is pretty standard functionality for most other VCS' +> web interfaces so I'm guessing there must be, but loggerhead seems not +> to support it. + +Unfortunately, people would still need bzr to install the versioned source: + + libbe/_version.py: + bzr version-info --format python > $@ + +So you'll need a "release" target in the makefile to build a bzr-less +install. While you're at it, you should probably compile the manpage +too to remove the docbook-to-man dependency. + +Do people want a HEAD tarball too? There must be a bzr equivalent of + .git/hooks/post-update +but I don't know what it is. + +> > If you're worried about stability, just clone from a more stable branch +> > (i.e., Chris' trunk). I think > this is good for distributed development, +> > but maybe makes it hard to package into a conventional release-based system. +> > With the bzr patch number in setup.py as the patch release number, I would be +> > releasing my 0.1.363 while Chris releases his 0.1.314, even though they're at +> > about the same point. I would rather be releasing my +> > 0.1.20090714121347 +> > while Chris releases his +> > 0.1.20090713154540 +> > Since then the similarity is clearer. +> +> Both approaches seem pretty odd to me, as a user you would have no +> idea if 0.1.200910302359 has the fixes you required in a release you +> were using that was numbered 0.1.200907141554. Surely you'd at least be +> {pre,suf}fixing a branch name to the version. + +"be --version" currently gives you the revision id: + wking@drexel.edu-20090714121347-c6rloikst1t3m5yl +which tells you exactly which commit your installed version is based on. +If we want stick with revision numbers, how about: + major.minor.revno-branch_nick +But then we'd have to pick "unique" branch nicknames... + +I'd sliced out the timestamp portion of the revision id so that the +"patch-number" would be an integer and the branch name wasn't +references, so that "upgrading" from one branch to another could be +transparent to the users (who just see an increading timestamp), but +still available to the developers (who know when commits were made to +the branches they track, and the likelyhood of to-the-second commit +collisions in official packages is small). + +On Wed, Jul 15, 2009 at 12:54:05AM +1000, Ben Finney wrote: +> "W. Trevor King" <wking@drexel.edu> writes: +> +> > On Tue, Jul 14, 2009 at 10:36:26PM +1000, Ben Finney wrote: +> > > Please, no. Timestamps aren't version strings, that's conflating two +> > > pieces of information with very different meanings. Correlating the +> > > two is the job of a changelog. +> > +> > Which we don't bother keeping (also NEWS), since "bzr log" works so +> > nicely. +> +> That's not a changelog, that's a commit log of every source-level commit +> made. Far too much detail for a changelog of *user-visible* changes +> associated with a release. + +I need a user around to help me determine "user-visable" changes ;). +My labmates loose interest after be init/new/comment :p. None of +which has ever changed, other than set-root -> init ;). + +> > The timestamp should at least replace the patch release number, which +> > you agree is-desirable-to increase motonically ;). +> +> I still disagree that a timestamp is the right thing to use there. If +> you want a monotonically-increasing indicator of which revision we're up +> to, that's immediately available with the revision number from VCS on +> the main branch. That also has the advantage of producing consecutive +> numbers for each revision, by definition. + +But not during branch-switches, while my method skips large regions, +but probably increases during any reasonable branch-switch. For +example, when I upgraded to rich root to pull Ben's patch, I'm not +sure if Chris upgraded the trunk and merged my branch, or just ditched +the trunk and cloned my branch. Using actual bzr revision numbers +would make switching branches that either wrong (in the case of +rev-id decreases) or confusing (in the case of a single +non-consecutive increase). + +On Tue, Jul 14, 2009 at 11:11:31AM -0400, Chris Ball wrote: +> > I agree that's a problem. I think the solution is to start making +> > releases, with specific version strings, as source tarballs. +> +> I'm happy to do this if people think it would be useful, and I don't +> yet have a strong opinion on whether the releases should come with +> version numbers or timestamps. + +I imagine the news from 2006 to now will be somewhat abridged ;). + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1f40efc1-6efc-4dd8-bdd2-97907e5aa624/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1f40efc1-6efc-4dd8-bdd2-97907e5aa624/values new file mode 100644 index 0000000..9e84a24 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1f40efc1-6efc-4dd8-bdd2-97907e5aa624/values @@ -0,0 +1,14 @@ +Alt-id: <20090714171725.GB10445@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 14 Jul 2009 13:17:25 -0400 + + +In-reply-to: 0c40c13a-3515-4b45-a8c3-142cceab9254 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2bb7b4d0-6290-4771-9fff-4aa2e8086b1a/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2bb7b4d0-6290-4771-9fff-4aa2e8086b1a/body new file mode 100644 index 0000000..a0b6a14 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2bb7b4d0-6290-4771-9fff-4aa2e8086b1a/body @@ -0,0 +1,58 @@ +Chris Ball <cjb@laptop.org> writes: + +> Hi, +> +> > That's not a changelog, that's a commit log of every source-level +> > commit made. Far too much detail for a changelog of +> > *user-visible* changes associated with a release. +> +> I think I agree with both of you. :) It seems like it's both true that +> there's no point in keeping a GNU-style ChangeLog these days + +I think I have a better understanding of why this apparent disagreement +occurred, and it was due to my sloppy use of terms. + +Looking into it further, it seems there is a certain expectation (set, +in part, by the long-standing GNU coding conventions) that a “GNU-style +ChangeLog” contains not only a particular *format*, but information at +a particular level of *detail*. + +That is, a GNU ChangeLog is intended for the style of work where one +logs all the changes made to every file in the tree each working day, +and then makes a new day's entry above that, and so on. This is, of +course, entirely redundant with a VCS revision history, which makes all +the commit messages available on request. + +So to disambiguate, that's not what I meant. I'm more familiar with a +less-frequently-updated and less-fine-detail change log; perhaps more +akin to the GNU-style “NEWS” file. + +> and that if we make a release we should write an announce mail that +> directly mentions new user-visible changes as well as attaching the +> commit log. That smaller list of highly user-visible changes could +> live in NEWS, or in the announce mail, or both. + +Yes, that's mostly what I meant. + +I actually don't think the commit log needs to be part of the release at +all. It's of interest only to those who want fine-level detail about +changes to every file, and for that purpose I think read access to the +VCS is much better. Packaging a static copy of the commit log as plain +text seems pointless. + +Rather, we should treat a user-changes level “NEWS” file (or whatever +name we choose for it) as part of the documentation, and set the +expectation among the team that it will be updated for each user-visible +change being worked on, like any other documentation. + +-- + \ “… Nature … is seen to do all things Herself and through | + `\ herself of own accord, rid of all gods.” —Titus Lucretius | +_o__) Carus, c. 40 BCE | +Ben Finney + + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2bb7b4d0-6290-4771-9fff-4aa2e8086b1a/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2bb7b4d0-6290-4771-9fff-4aa2e8086b1a/values new file mode 100644 index 0000000..320c484 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2bb7b4d0-6290-4771-9fff-4aa2e8086b1a/values @@ -0,0 +1,14 @@ +Alt-id: <87hbxdhtkp.fsf@benfinney.id.au> + + +Author: Ben Finney <bignose+hates-spam@benfinney.id.au> + + +Content-type: text/plain + + +Date: Thu, 16 Jul 2009 19:21:10 +1000 + + +In-reply-to: cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2c95ee07-462d-42cf-8dc3-8f5389a392cb/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2c95ee07-462d-42cf-8dc3-8f5389a392cb/body new file mode 100644 index 0000000..5f478b5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2c95ee07-462d-42cf-8dc3-8f5389a392cb/body @@ -0,0 +1,96 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA1 + +W. Trevor King wrote: +> Thinking about this some more, I think that the role of the +> main-branch is to officially sanction the current state of the code as +> "released". If a series of commits will leave a branch in a +> known-unusable form, they should be carried out in some appropriately +> named development branch. Then the log of commits to the main branch +> ("bzr log -n 1" for bzr > ) should produce a fairly respectable +> changelog. + +This is how we develop bzr itself. The mainline is controlled by PQM, +which is a tool that merges feature branches, runs the tests, and +commits only if the tests pass. + +$ bzr log --short --limit 10 + 4534 Canonical.com Patch Queue Manager 2009-07-14 [merge] + (abentley) Implement merge --interactive + + 4533 Canonical.com Patch Queue Manager 2009-07-14 [merge] + (jml) Merge in changes from 1.17 branch. + + 4532 Canonical.com Patch Queue Manager 2009-07-14 [merge] + (igc) zc.buildout Windows build support (Sidnei da Silva) + + 4531 Canonical.com Patch Queue Manager 2009-07-13 [merge] + (vila) Delete forgotten debug print + + 4530 Canonical.com Patch Queue Manager 2009-07-13 [merge] + (vila) Isolate some tests from TZ + + 4529 Canonical.com Patch Queue Manager 2009-07-13 [merge] + (igc) Bazaar 2.0 Upgrade Guide + + 4528 Canonical.com Patch Queue Manager 2009-07-13 [merge] + (mbp) correction to news + + 4527 Canonical.com Patch Queue Manager 2009-07-13 [merge] + (jml) Merge in 1.17 branch, updating version numbers and NEWS file. + + 4526 Canonical.com Patch Queue Manager 2009-07-10 [merge] + (mbp, vila) Finish the *_implementation to per_* test renaming + + 4525 Canonical.com Patch Queue Manager 2009-07-10 [merge] + (vila) Quicker check for changes in mutable trees + +You can also see all the merges as they come into the mainline: + +$ bzr log --short --limit 10 --include-merges + 4534 Canonical.com Patch Queue Manager 2009-07-14 [merge] + (abentley) Implement merge --interactive + + 4526.6.15 Aaron Bentley 2009-07-14 + Update command help + + 4526.6.14 Aaron Bentley 2009-07-14 + Use default DiffWriter. + + 4526.6.13 Aaron Bentley 2009-07-14 + Add docstring to do_interactive. + + 4526.6.12 Aaron Bentley 2009-07-14 + Updates from review. + + 4526.6.11 Aaron Bentley 2009-07-13 + Update NEWS. + + 4526.6.10 Aaron Bentley 2009-07-13 [merge] + Merged apply-vocab into merge-interactive. + + 4526.7.4 Aaron Bentley 2009-07-13 [merge] + Merged bzr.dev into apply-vocab. + + 4526.6.9 Aaron Bentley 2009-07-13 [merge] + Merged apply-vocab into merge-interactive. + + 4526.7.3 Aaron Bentley 2009-07-13 + Test shelve_change. + +> This also means that _every_commit_ to a main branch would +> be an official release. + +We don't do that. We have official releases every 4 weeks, but we do +believe that running bzr.dev is pretty safe, because it's always tested +and our test suite is quite thorough. + +Aaron +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.9 (GNU/Linux) +Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org + +iEYEARECAAYFAkpcznIACgkQ0F+nu1YWqI0yhACePTFUUp6u+Dw+8IRwWOWBQRtb +TgsAniJq4lqnDfjNACMr7IEt7xYJhx7m +=BbGG +-----END PGP SIGNATURE----- diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2c95ee07-462d-42cf-8dc3-8f5389a392cb/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2c95ee07-462d-42cf-8dc3-8f5389a392cb/values new file mode 100644 index 0000000..8cfe1b0 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2c95ee07-462d-42cf-8dc3-8f5389a392cb/values @@ -0,0 +1,14 @@ +Alt-id: <4A5CCE76.9040106@aaronbentley.com> + + +Author: Aaron Bentley <aaron@aaronbentley.com> + + +Content-type: text/plain + + +Date: Tue, 14 Jul 2009 14:29:10 -0400 + + +In-reply-to: ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/31beb504-c72b-4304-95ba-a66d2bcbc46a/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/31beb504-c72b-4304-95ba-a66d2bcbc46a/body new file mode 100644 index 0000000..b34e037 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/31beb504-c72b-4304-95ba-a66d2bcbc46a/body @@ -0,0 +1,52 @@ +On Tue, Jul 14, 2009 at 07:34:04PM +0100, jnrowe@gmail.com wrote: +> [This time to the list] +> +> * W. Trevor King (wking@drexel.edu) wrote: +> > On Tue, Jul 14, 2009 at 03:29:42PM +0100, James Rowe wrote: +> > > Isn't there a bzr web interface that at least supports creating +> > > tarballs/zips? It is pretty standard functionality for most other VCS' +> > > web interfaces so I'm guessing there must be, but loggerhead seems not +> > > to support it. +> > +> > Unfortunately, people would still need bzr to install the versioned source: +> > +> > libbe/_version.py: +> > bzr version-info --format python > $@ +> +> I hadn't even seen that change go in. The last upstream change in the +> version I have installed locally was by you on 2008-11-24. + +It's only been in Chris' http://bzr.bugseverywhere.org/be/ branch +since revno: 321, 2009-06-25. Obviously we may have to adjust the +--verison output once we settle on a versioning scheme, but whatever +we pick, I think having the auto-generated libbe/_version.py around +for pinpointing bugs is worth the trouble of requiring bzr when +building distribution tarballs. + +> > So you'll need a "release" target in the makefile to build a bzr-less +> > install. While you're at it, you should probably compile the manpage +> > too to remove the docbook-to-man dependency. +> +> Maybe for others. Our packages just don't have the manpage as it is only +> the "be help" text reformatted, the easy option is sometimes the right +> one :) Also, I've just noticed that it has even less documentation in +> the bzr tree[1] making its installation much less compelling unless your +> packaging rules require a man page like Debians. +> +> Out of curiosity why is the Makefile being used for this stuff anyway? +> It is going to make it difficult to build locally when we finally get +> around to merging. Examples: If distutils was being used exclusively it +> would fix the #! lines in xml/*. We'd be able to point Python +> $version_of_the_day at setup.py instead of having to sed the Makefile or +> run setup and manually install other files. + +I speak Makefile better than I speak distutils ;). I'm not sure how +to translate the be.1 generation/installation or the libbe/_version.py +generation into distutils. Anyone else? + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/31beb504-c72b-4304-95ba-a66d2bcbc46a/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/31beb504-c72b-4304-95ba-a66d2bcbc46a/values new file mode 100644 index 0000000..e0c0955 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/31beb504-c72b-4304-95ba-a66d2bcbc46a/values @@ -0,0 +1,14 @@ +Alt-id: <20090714191145.GB10606@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 14 Jul 2009 15:11:45 -0400 + + +In-reply-to: 6e315abe-a080-4369-8729-4aea2dee8494 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/49e0425b-3332-4d0e-b371-300eccd55370/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/49e0425b-3332-4d0e-b371-300eccd55370/body new file mode 100644 index 0000000..4ebb4f2 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/49e0425b-3332-4d0e-b371-300eccd55370/body @@ -0,0 +1,51 @@ +"W. Trevor King" <wking@drexel.edu> writes: + +> ** NEWS file + +Speaking as the package maintainer, I would like a ‘ChangeLog’ file +separate from a ‘NEWS’ file. + +The ‘NEWS’ file would continue to be hand-edited, and would be a +high-level view of user-visible changes in the project each version. +Users could reasonably expect to be interested in this file when +installing a new version. It would also make sense to retire old news +From this file once it becomes sufficiently old, to keep it relevant to +users to read. + + +The ‘ChangeLog’ would be an automatically-generated changelog of +low-level changes, not for general human consumption but for letting +recipients have a fighting chance at knowing the historical context of a +particular change without access to the VCS. It would probably be best +done as Trevor says: + +> Depending on our level of masochism, either something starting out +> along the lines of [2] +> bzr log --gnu-changelog -n1 -r 200.. + +That makes it necessary to add the changelog file to the tarball, since +it won't be a file tracked by VCS and therefore won't be exported. Not a +problem:: + + $ release_version="1.0.0" + $ release_name="be-$release_version" + $ tarball_file=../$release_name.tar.gz + $ work_dir=$(mktemp -t -d) + $ export_dir=$work_dir/$release_name + $ changelog_file=$export_dir/ChangeLog + + $ bzr export $export_dir + $ bzr log --gnu-changelog -n1 -r ..tag:"$release_version" > $changelog_file + $ tar -czf $tarball_file $export_dir + $ rm -r $work_dir/ + + $ ls $tarball_file + ../be-1.0.0.tar.gz + $ tar -tzf $tarball_file | grep ChangeLog + be-1.0.0/ChangeLog + +-- + \ “I bought a dog the other day. I named him Stay. It's fun to | + `\ call him. ‘Come here, Stay! Come here, Stay!’ He went insane. | +_o__) Now he just ignores me and keeps typing.” —Steven Wright | +Ben Finney diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/49e0425b-3332-4d0e-b371-300eccd55370/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/49e0425b-3332-4d0e-b371-300eccd55370/values new file mode 100644 index 0000000..b45a747 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/49e0425b-3332-4d0e-b371-300eccd55370/values @@ -0,0 +1,14 @@ +Alt-id: <873a4cmjw5.fsf@benfinney.id.au> + + +Author: Ben Finney <bignose+hates-spam@benfinney.id.au> + + +Content-type: text/plain + + +Date: Wed, 18 Nov 2009 22:23:06 +0000 + + +In-reply-to: a4720227-43cf-49aa-8f9f-f49f46e3e809 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/6e315abe-a080-4369-8729-4aea2dee8494/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/6e315abe-a080-4369-8729-4aea2dee8494/body new file mode 100644 index 0000000..7ffe231 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/6e315abe-a080-4369-8729-4aea2dee8494/body @@ -0,0 +1,38 @@ +[This time to the list] + +* W. Trevor King (wking@drexel.edu) wrote: +> On Tue, Jul 14, 2009 at 03:29:42PM +0100, James Rowe wrote: +> > Isn't there a bzr web interface that at least supports creating +> > tarballs/zips? It is pretty standard functionality for most other VCS' +> > web interfaces so I'm guessing there must be, but loggerhead seems not +> > to support it. +> +> Unfortunately, people would still need bzr to install the versioned source: +> +> libbe/_version.py: +> bzr version-info --format python > $@ + + I hadn't even seen that change go in. The last upstream change in the +version I have installed locally was by you on 2008-11-24. + +> So you'll need a "release" target in the makefile to build a bzr-less +> install. While you're at it, you should probably compile the manpage +> too to remove the docbook-to-man dependency. + + Maybe for others. Our packages just don't have the manpage as it is only +the "be help" text reformatted, the easy option is sometimes the right +one :) Also, I've just noticed that it has even less documentation in +the bzr tree[1] making its installation much less compelling unless your +packaging rules require a man page like Debians. + + Out of curiosity why is the Makefile being used for this stuff anyway? +It is going to make it difficult to build locally when we finally get +around to merging. Examples: If distutils was being used exclusively it +would fix the #! lines in xml/*. We'd be able to point Python +$version_of_the_day at setup.py instead of having to sed the Makefile or +run setup and manually install other files. + +Thanks, + +James + 1. http://pullcord.laptop.org:4000/revision/314.1.15/doc/be.1.sgml diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/6e315abe-a080-4369-8729-4aea2dee8494/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/6e315abe-a080-4369-8729-4aea2dee8494/values new file mode 100644 index 0000000..4f1d60d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/6e315abe-a080-4369-8729-4aea2dee8494/values @@ -0,0 +1,14 @@ +Alt-id: <20090714183404.GB26032@ukfsn.org> + + +Author: jnrowe@gmail.com + + +Content-type: text/plain + + +Date: Tue, 14 Jul 2009 19:34:04 +0100 + + +In-reply-to: 1f40efc1-6efc-4dd8-bdd2-97907e5aa624 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/72a519e3-3d6b-4f0f-b412-1310efd255eb/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/72a519e3-3d6b-4f0f-b412-1310efd255eb/body new file mode 100644 index 0000000..d00eb64 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/72a519e3-3d6b-4f0f-b412-1310efd255eb/body @@ -0,0 +1,22 @@ +Hi, + + > It would make it much easier on the Debian package maintainer if + > the Bugs Everywhere project would make conventional tarball + > releases, with conventional version numbers, with a changelog + > describing what has changed between versions. + +Fair point. + +How do people feel about pushing for a 1.0 release, with Trevor's tree +plus a finished cfbe merge? Or would we rather wait until afterwards +to try for cfbe? + +- Chris. +-- +Chris Ball <cjb@laptop.org> +One Laptop Per Child + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/72a519e3-3d6b-4f0f-b412-1310efd255eb/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/72a519e3-3d6b-4f0f-b412-1310efd255eb/values new file mode 100644 index 0000000..4fb068d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/72a519e3-3d6b-4f0f-b412-1310efd255eb/values @@ -0,0 +1,14 @@ +Alt-id: <m3ocn09310.fsf@pullcord.laptop.org> + + +Author: Chris Ball <cjb@laptop.org> + + +Content-type: text/plain + + +Date: Tue, 17 Nov 2009 22:53:31 +0000 + + +In-reply-to: 1847f1f8-525a-42c4-ae2b-e9377459d2a6 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/744435b7-1521-4059-a55d-f0c403d7b4d8/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/744435b7-1521-4059-a55d-f0c403d7b4d8/body new file mode 100644 index 0000000..24ff7b0 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/744435b7-1521-4059-a55d-f0c403d7b4d8/body @@ -0,0 +1,58 @@ +"W. Trevor King" <wking@drexel.edu> writes: + +> Currently setup.py sets the version number for BE to 0.0.193 and the +> url to http://panoramicfeedback.com/opensource/. These are both a bit +> outdated ;). + +Right, that should change. + +> I've switched my branch over to the current url, and moved to +> last-commit-timestamp version numbers. + +Please, no. Timestamps aren't version strings, that's conflating two +pieces of information with very different meanings. Correlating the two +is the job of a changelog. + +> This removes the "prefered branch" issues with the old scheme, and +> version numbers should increase monotonically + +The English word “should” is ambiguous in this context. Are you saying +this is desirable, or are you predicting that it will likely be the +case? + +I don't see how it's either, so am doubly confused :-) + +> but it looses any stability information suggested by the preceding +> 0.0. + +The convention for three-part version strings is often: + + * Major release number (big changes in how the program works, + increasing monotonically per major release, with “0”indicating no + major release yet) + + * Minor release number (smaller impact on how the program works, + increasing monotonically per minor release, with “0” indicating no + minor release yet since the previous major) + + * Patch release number (bug-fix and other changes that don't affect + the documented interface, increasing monotonically per patch + release, with “0” indicating no patch release since the previous + major or minor) + +Obviously there's no standard or enforcement for this, but that's the +interpretation I usually give to dotted version strings in the absence +of more formal declaration specific to the project. + +> We can add those back in if people want. Does the first 0 mean +> "interfaces in flux" and the second 0 mean "lots of bugs"? If so, I +> think we're up to 0.1, since the major features are pretty calm. + +I disagree with your interpretation and prefer mine, above; on that +basis, I agree that we're at least up to version 0.1 by now :-) + +-- + \ “A lot of water has been passed under the bridge since this | + `\ variation has been played.” chess book, Russia | +_o__) | +Ben Finney diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/744435b7-1521-4059-a55d-f0c403d7b4d8/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/744435b7-1521-4059-a55d-f0c403d7b4d8/values new file mode 100644 index 0000000..c5d9cbb --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/744435b7-1521-4059-a55d-f0c403d7b4d8/values @@ -0,0 +1,14 @@ +Alt-id: <87ocrnjvat.fsf@benfinney.id.au> + + +Author: Ben Finney <bignose+hates-spam@benfinney.id.au> + + +Content-type: text/plain + + +Date: Tue, 14 Jul 2009 22:36:26 +1000 + + +In-reply-to: cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/96abea83-9867-4c21-8eb8-9e1b1093cba4/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/96abea83-9867-4c21-8eb8-9e1b1093cba4/body new file mode 100644 index 0000000..a3fc57f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/96abea83-9867-4c21-8eb8-9e1b1093cba4/body @@ -0,0 +1,36 @@ +I've written up a little release script that bundles all the steps +we've mentioned so far into a single command. Of course, we'll still +have to keep NEWS up to date on our own. + +The output prints a trace of what's going on: + + $ ./release.py 1.0.0 + set libbe.version._VERSION = '1.0.0' + updating AUTHORS + updating ./becommands/assign.py + updating ./becommands/html.py + ... + commit current status: Bumped to version 1.0.0 + tag current revision 1.0.0 + export current revision to be-1.0.0 + generate libbe/_version.py + copy libbe/_version.py to be-1.0.0/libbe/_version.py + generate ChangeLog file be-1.0.0/ChangeLog up to tag 1.0.0 + set vcs_name in be-1.0.0/.be/settings to None + create tarball be-1.0.0.tar.gz + remove be-1.0.0 + +Since we'll be distributing a non-bzr-repo version, it would be nice +to adapt our 'submit bug' procedure (outlined on the main page) to one +that works with this setup. Without guaranteed versioning, that would +probably be something along the lines of + be email-bugs [--to be-devel@bugseverywhere.org] BUG-ID ... +With interfaces/email/interactive listening on the recieving end to +grab new-bug emails and import them into an incoming bug repository. + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/96abea83-9867-4c21-8eb8-9e1b1093cba4/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/96abea83-9867-4c21-8eb8-9e1b1093cba4/values new file mode 100644 index 0000000..b6d25cb --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/96abea83-9867-4c21-8eb8-9e1b1093cba4/values @@ -0,0 +1,14 @@ +Alt-id: <20091120132219.GA17577@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Fri, 20 Nov 2009 13:22:19 +0000 + + +In-reply-to: 49e0425b-3332-4d0e-b371-300eccd55370 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a4720227-43cf-49aa-8f9f-f49f46e3e809/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a4720227-43cf-49aa-8f9f-f49f46e3e809/body new file mode 100644 index 0000000..5d29f85 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a4720227-43cf-49aa-8f9f-f49f46e3e809/body @@ -0,0 +1,102 @@ +On Tue, Nov 17, 2009 at 05:53:31PM -0500, Chris Ball wrote: +> > It would make it much easier on the Debian package maintainer if +> > the Bugs Everywhere project would make conventional tarball +> > releases, with conventional version numbers, with a changelog +> > describing what has changed between versions. +> How do people feel about pushing for a 1.0 release, with Trevor's tree +> plus a finished cfbe merge? Or would we rather wait until afterwards +> to try for cfbe? + +Sounds good to me. Not that my tree is much ahead of the trunk at the +moment. We've talked over most of these issues a few times, so I'll +just summarize where I think we stand on the steps needed to make a +release. + +** cfbe integration + +Postpone until we work out bzr/hg versioning [1]? + +** Conventional version number + +Set to "1.0.0" using libbe.version._VERSION. + +** NEWS file + +Depending on our level of masochism, either something starting out +along the lines of [2] + bzr log --gnu-changelog -n1 -r 200.. +(commit 200, or + aaron.bentley@utoronto.ca-20060411035623-9b8d222282a26ce1 + was the last time anyone touched the NEWS file), +or a much abbreviated entry [3,4], along the lines of my current NEWS +file (changed just a few minutes ago). + +** Tag bzr commit + + bzr tag 1.0.0 + +** Create tarball + +From Ben[5]: + bzr export /tmp/be-1.0.0.tar.gz + + +References: + +[1] +On Thu, Jul 23, 2009 at 05:38:03PM -0400, Steve Losh wrote: +> On Jul 21, 2009, at 9:59 AM, W. Trevor King wrote: +> > Steve's also versioning it with Mercurial. Will he mind changing to +> > Bazaar? +> +> Yeah, I've tried bazaar but really don't like the interface at all. If +> everyone else really wants me to move it over I guess I can though. + +[2] +On Tue, Jul 14, 2009 at 11:05:38AM -0400, Chris Ball wrote: +> Actually, there's a `bzr log --gnu-changelog` now, and `bzr help +> log-formats` offers some more styles. (None of them seem to match +> my preferred style for release announcements exactly, which would +> be `git shortlog`-style.) + +[3] +On Thu, Jul 16, 2009 at 07:21:10PM +1000, Ben Finney wrote: +> I actually don't think the commit log needs to be part of the release at +> all. It's of interest only to those who want fine-level detail about +> changes to every file, and for that purpose I think read access to the +> VCS is much better. Packaging a static copy of the commit log as plain +> text seems pointless. +> +> Rather, we should treat a user-changes level “NEWS” file (or whatever +> name we choose for it) as part of the documentation, and set the +> expectation among the team that it will be updated for each user-visible +> change being worked on, like any other documentation. + +[4] +On Tue, Jul 14, 2009 at 11:11:31AM -0400, Chris Ball wrote: +> Hi, +> +> > That's not a changelog, that's a commit log of every source-level +> > commit made. Far too much detail for a changelog of +> > *user-visible* changes associated with a release. +> +> I think I agree with both of you. :) It seems like it's both true that +> there's no point in keeping a GNU-style ChangeLog these days, and that +> if we make a release we should write an announce mail that directly +> mentions new user-visible changes as well as attaching the commit log. +> That smaller list of highly user-visible changes could live in NEWS, +> or in the announce mail, or both. + +[5] +On Wed, Jul 15, 2009 at 12:54:05AM +1000, Ben Finney wrote: +> Even better: ‘bzr export /tmp/foo.tar.gz’ will create a source tarball +> of all the files in the branch's VCS inventory. All we need to do is +> start the practice of tagging a release in the VCS, and export the +> tarball at that time. + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a4720227-43cf-49aa-8f9f-f49f46e3e809/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a4720227-43cf-49aa-8f9f-f49f46e3e809/values new file mode 100644 index 0000000..7f205d6 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a4720227-43cf-49aa-8f9f-f49f46e3e809/values @@ -0,0 +1,14 @@ +Alt-id: <20091118011403.GB9503@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Wed, 18 Nov 2009 01:14:03 +0000 + + +In-reply-to: 72a519e3-3d6b-4f0f-b412-1310efd255eb + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a536cee5-cc8d-4b18-b491-657e0c7998b4/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a536cee5-cc8d-4b18-b491-657e0c7998b4/body new file mode 100644 index 0000000..8b32751 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a536cee5-cc8d-4b18-b491-657e0c7998b4/body @@ -0,0 +1,14 @@ +Hi, + + > Which we don't bother keeping (also NEWS), since "bzr log" works + > so nicely. If you really want an standard changelog, see + > http://mail.gnome.org/archives/desktop-devel-list/2007-September/msg00186.html + +Actually, there's a `bzr log --gnu-changelog` now, and `bzr help +log-formats` offers some more styles. (None of them seem to match +my preferred style for release announcements exactly, which would +be `git shortlog`-style.) + +- Chris. +-- +Chris Ball <cjb@laptop.org> diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a536cee5-cc8d-4b18-b491-657e0c7998b4/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a536cee5-cc8d-4b18-b491-657e0c7998b4/values new file mode 100644 index 0000000..239feb5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a536cee5-cc8d-4b18-b491-657e0c7998b4/values @@ -0,0 +1,14 @@ +Alt-id: <m3ljmrfgot.fsf@pullcord.laptop.org> + + +Author: Chris Ball <cjb@laptop.org> + + +Content-type: text/plain + + +Date: Tue, 14 Jul 2009 11:05:38 -0400 + + +In-reply-to: ea01c122-e629-4d5c-afa7-b180f4a8748b + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a845096e-3cdf-41ed-a0e3-283439665b92/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a845096e-3cdf-41ed-a0e3-283439665b92/body new file mode 100644 index 0000000..33a8d66 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a845096e-3cdf-41ed-a0e3-283439665b92/body @@ -0,0 +1,51 @@ +I don't think anyone's changing their mind ;), so tallying the +comments so far: + +On Wed, Jul 15, 2009 at 12:54:05AM +1000, Ben Finney wrote: +> I still disagree that a timestamp is the right thing to use there. If +> you want a monotonically-increasing indicator of which revision we're up +> to, that's immediately available with the revision number from VCS on +> the main branch. That also has the advantage of producing consecutive +> numbers for each revision, by definition. + ++1 for trunk version number. + +On Tue, Jul 14, 2009 at 05:27:52PM +0200, Elena of Valhalla wrote: +> I also have a weak preference for version numbers, as long as they +> give useful informations on the state the release. + ++1 for trunk version number. + +On Tue, Jul 14, 2009 at 02:29:10PM -0400, Aaron Bentley wrote: +> We don't do that. We have official releases every 4 weeks, but we do +> believe that running bzr.dev is pretty safe, because it's always tested +> and our test suite is quite thorough. + ++1 for by hand version bumps. + +On Fri, Jul 17, 2009 at 11:37:49PM +0200, Gianluca Montecchi wrote: +> The version number of trunk _is_ should be the official version number of the +> Bugs Everywhere releases. +> The version number in branch does not means nothing outside the branch. +> At least we can have a mechanism to build a version number scheme that is +> consistent for us to be able to merge branch easily. + ++1 for trunk version number. + +And me with my timestamps ;). + +Sounds like we should go with trunk version number, but that it should +be set by hand whenever Chris decides to release something, since the +rest of us don't know what version the trunk is on. Unless we do +something like: + bzr log -n 0 | grep -B2 'nick: be$' | head -n1 | sed 's/ *revno: \([0-9]*\).*/\1/' +to extract the last trunk commit referenced from our branch. + +Implementation preferences? (i.e. Chris vs. regexp matching :p) + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a845096e-3cdf-41ed-a0e3-283439665b92/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a845096e-3cdf-41ed-a0e3-283439665b92/values new file mode 100644 index 0000000..b757933 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a845096e-3cdf-41ed-a0e3-283439665b92/values @@ -0,0 +1,14 @@ +Alt-id: <20090718105008.GA31639@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 18 Jul 2009 06:50:08 -0400 + + +In-reply-to: c35835c0-8f9f-4090-ba92-1f616867e486 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/aad59898-8949-44fb-ad0b-2acea6eb2ef8/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/aad59898-8949-44fb-ad0b-2acea6eb2ef8/body new file mode 100644 index 0000000..063afcb --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/aad59898-8949-44fb-ad0b-2acea6eb2ef8/body @@ -0,0 +1,30 @@ +Hi, + + > That's not a changelog, that's a commit log of every source-level + > commit made. Far too much detail for a changelog of + > *user-visible* changes associated with a release. + +I think I agree with both of you. :) It seems like it's both true that +there's no point in keeping a GNU-style ChangeLog these days, and that +if we make a release we should write an announce mail that directly +mentions new user-visible changes as well as attaching the commit log. +That smaller list of highly user-visible changes could live in NEWS, +or in the announce mail, or both. + + > I agree that's a problem. I think the solution is to start making + > releases, with specific version strings, as source tarballs. + +I'm happy to do this if people think it would be useful, and I don't +yet have a strong opinion on whether the releases should come with +version numbers or timestamps. + +Thanks, + +- Chris. +-- +Chris Ball <cjb@laptop.org> + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/aad59898-8949-44fb-ad0b-2acea6eb2ef8/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/aad59898-8949-44fb-ad0b-2acea6eb2ef8/values new file mode 100644 index 0000000..466be33 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/aad59898-8949-44fb-ad0b-2acea6eb2ef8/values @@ -0,0 +1,14 @@ +Alt-id: <m3k52bfgf0.fsf@pullcord.laptop.org> + + +Author: Chris Ball <cjb@laptop.org> + + +Content-type: text/plain + + +Date: Tue, 14 Jul 2009 11:11:31 -0400 + + +In-reply-to: ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68/body new file mode 100644 index 0000000..1e2a870 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68/body @@ -0,0 +1,37 @@ +On Tue, Jul 14, 2009 at 01:17:25PM -0400, W. Trevor King wrote: +> On Wed, Jul 15, 2009 at 12:54:05AM +1000, Ben Finney wrote: +> > "W. Trevor King" <wking@drexel.edu> writes: +> > +> > > On Tue, Jul 14, 2009 at 10:36:26PM +1000, Ben Finney wrote: +> > > > Please, no. Timestamps aren't version strings, that's conflating two +> > > > pieces of information with very different meanings. Correlating the +> > > > two is the job of a changelog. +> > > +> > > Which we don't bother keeping (also NEWS), since "bzr log" works so +> > > nicely. +> > +> > That's not a changelog, that's a commit log of every source-level commit +> > made. Far too much detail for a changelog of *user-visible* changes +> > associated with a release. +> +> I need a user around to help me determine "user-visable" changes ;). +> My labmates loose interest after be init/new/comment :p. None of +> which has ever changed, other than set-root -> init ;). + +Thinking about this some more, I think that the role of the +main-branch is to officially sanction the current state of the code as +"released". If a series of commits will leave a branch in a +known-unusable form, they should be carried out in some appropriately +named development branch. Then the log of commits to the main branch +("bzr log -n 1" for bzr > ) should produce a fairly respectable +changelog. Obviously we are all quite guilty of doing most of our +development in single branches, but it may be a useful model going +forward. This also means that _every_commit_ to a main branch would +be an official release. + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68/values new file mode 100644 index 0000000..b5c41c9 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68/values @@ -0,0 +1,14 @@ +Alt-id: <20090714182034.GA10606@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 14 Jul 2009 14:20:34 -0400 + + +In-reply-to: 1f40efc1-6efc-4dd8-bdd2-97907e5aa624 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/b19a8f6a-1d7b-4887-a9df-123d59b0cd9b/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/b19a8f6a-1d7b-4887-a9df-123d59b0cd9b/body new file mode 100644 index 0000000..e02bd38 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/b19a8f6a-1d7b-4887-a9df-123d59b0cd9b/body @@ -0,0 +1,25 @@ +On Tue, Jul 14, 2009 at 5:11 PM, Chris Ball<cjb@laptop.org> wrote: +> > I agree that's a problem. I think the solution is to start making +> > releases, with specific version strings, as source tarballs. +> +> I'm happy to do this if people think it would be useful, and I don't +> yet have a strong opinion on whether the releases should come with +> version numbers or timestamps. + +as an user of be that plans to try and "package" it for openembedded, +a release would be very useful: writing a recipe that downloads a +specific commit from bzr and builds it is probably feasible, but +definitely not ideal. + +I also have a weak preference for version numbers, as long as they +give useful informations on the state the release. + +-- +Elena ``of Valhalla'' + +email: elena.valhalla@gmail.com + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/b19a8f6a-1d7b-4887-a9df-123d59b0cd9b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/b19a8f6a-1d7b-4887-a9df-123d59b0cd9b/values new file mode 100644 index 0000000..57b408f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/b19a8f6a-1d7b-4887-a9df-123d59b0cd9b/values @@ -0,0 +1,14 @@ +Alt-id: <5c5e5c350907140827u218553e8rc5773325d43c2bf2@mail.gmail.com> + + +Author: Elena of Valhalla <elena.valhalla@gmail.com> + + +Content-type: text/plain + + +Date: Tue, 14 Jul 2009 17:27:52 +0200 + + +In-reply-to: aad59898-8949-44fb-ad0b-2acea6eb2ef8 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/c35835c0-8f9f-4090-ba92-1f616867e486/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/c35835c0-8f9f-4090-ba92-1f616867e486/body new file mode 100644 index 0000000..d8014d2 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/c35835c0-8f9f-4090-ba92-1f616867e486/body @@ -0,0 +1,102 @@ +On Thursday 16 July 2009 12:38:55 W. Trevor King wrote: +> On Thu, Jul 16, 2009 at 07:32:31PM +1000, Ben Finney wrote: +> > "W. Trevor King" <wking@drexel.edu> writes: +> > > On Wed, Jul 15, 2009 at 12:54:05AM +1000, Ben Finney wrote: +> > > > "W. Trevor King" <wking@drexel.edu> writes: +> > > > > On Tue, Jul 14, 2009 at 10:36:26PM +1000, Ben Finney wrote: +> > > > > > Please, no. Timestamps aren't version strings, that's conflating +> > > > > > two pieces of information with very different meanings. +> > > > > > Correlating the two is the job of a [NEWS file]. +> > > > +> > > > If you want a monotonically-increasing indicator of which revision +> > > > we're up to, that's immediately available with the revision number +> > > > from VCS on the main branch. That also has the advantage of +> > > > producing consecutive numbers for each revision, by definition. +> > > +> > > But not during branch-switches, while my method skips large regions, +> > > but probably increases during any reasonable branch-switch. +> > +> > I've read this several times now, and I don't see what it's saying. +> > +> > The assumption I'm making is that there is a single canonical “main +> > branch”, from which releases will be made. +> +> I don't think you need to assume this. See my "virtual branch" +> argument below. + +But if we have a canonical "main branch" that we release, and the packager +get, we can refer to it as the stable branch, that it is not a bad idea. + + + +> > The version number set in that branch is the one which determines +> > the version of Bugs Everywhere as a whole. +> +> If you are suggesting that the dev branches adjust their release +> number _by_hand_ to match the current trunk release number, that +> allows switching, but sounds like a lot of work and isn't correct +> anyway, since they are not in the same state as the trunk. + +The version number of trunk _is_ should be the official version number of the +Bugs Everywhere releases. +The version number in branch does not means nothing outside the branch. +At least we can have a mechanism to build a version number scheme that is +consistent for us to be able to merge branch easily. + +> > The revision number is only useful in the context of the branch, so it +> > only matters when comparing versions within a branch. When you switch +> > between branches, if you're interested in the revision number you'll +> > still need to know which branch you're talking about. +> +> I think this is our main disagreement. I see all the branches as part +> of the same codebase, with monotonically increasing timestamp patch +> numbers. If you were to collapse all the commit snapshots down into a +> single chronological "virtual branch", it would still make sense, it +> would just be a bit unorganized. We do all try to move in the same +> general direction ;). + +I don't think that, outside the developers, a version number like + +cjb@laptop.org-20090713154540-ve4pmydqzb1ghgvc + +is a good choice, not for the user of BE, not for the packager of BE + + +> > This, then, is an argument for not having the revision number in the +> > version string at all. The version then becomes a more traditional +> > “major.minor.patch” tuple, and is only ever updated when some release +> > manager of the canonical branch decides it's correct to do so. +> +> It is an argument for not using the revision number. You can avoid +> revision numbers by using hand-coded patch numbers, or by using +> timestamps, which is what we're trying to decide on :p. + +We can use both. +During the development we can use version number like + +x.y.z.timestamp + +As we decide to release a stable version, the release manager set the version +number to a more traditional x.y.z format, and create a branch (stable branch) + +This way we have these advantages: + +1) an user have a simple version number to use for bug report/feature +request/help request + +2) a packager have an easy life to choose to package a stable or a trunk +version, knowing what are they doing + +bonus) we can maintain a stable and a developmente source tree/branch, where +in the development tree we can make also backward incompatible modification to +the source without making any damage to the users/packagers, while in the +stable branch we can make only bugfix/security fix or port from the devel branch +some interesting features as long as they don't break compatibility. + +bye +Gianluca + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/c35835c0-8f9f-4090-ba92-1f616867e486/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/c35835c0-8f9f-4090-ba92-1f616867e486/values new file mode 100644 index 0000000..c7c0273 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/c35835c0-8f9f-4090-ba92-1f616867e486/values @@ -0,0 +1,14 @@ +Alt-id: <200907172337.49779.gian@grys.it> + + +Author: Gianluca Montecchi <gian@grys.it> + + +Content-type: text/plain + + +Date: Fri, 17 Jul 2009 23:37:49 +0200 + + +In-reply-to: f925e56f-26f9-4620-82fb-a0f160f27921 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c/body new file mode 100644 index 0000000..4e8445a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c/body @@ -0,0 +1,18 @@ +Currently setup.py sets the version number for BE to 0.0.193 and the +url to http://panoramicfeedback.com/opensource/. These are both a bit +outdated ;). I've switched my branch over to the current url, and +moved to last-commit-timestamp version numbers. This removes the +"prefered branch" issues with the old scheme, and version numbers +should increase monotonically, but it looses any stability information +suggested by the preceding 0.0. + +We can add those back in if people want. Does the first 0 mean +"interfaces in flux" and the second 0 mean "lots of bugs"? If so, I +think we're up to 0.1, since the major features are pretty calm. + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c/values new file mode 100644 index 0000000..00309a2 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c/values @@ -0,0 +1,11 @@ +Alt-id: <20090714110543.GB4855@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 14 Jul 2009 07:05:43 -0400 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ea01c122-e629-4d5c-afa7-b180f4a8748b/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ea01c122-e629-4d5c-afa7-b180f4a8748b/body new file mode 100644 index 0000000..fce4941 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ea01c122-e629-4d5c-afa7-b180f4a8748b/body @@ -0,0 +1,72 @@ +On Tue, Jul 14, 2009 at 10:36:26PM +1000, Ben Finney wrote: +> "W. Trevor King" <wking@drexel.edu> writes: +> > I've switched my branch over to the current url, and moved to +> > last-commit-timestamp version numbers. +> +> Please, no. Timestamps aren't version strings, that's conflating two +> pieces of information with very different meanings. Correlating the two +> is the job of a changelog. + +Which we don't bother keeping (also NEWS), since "bzr log" works so nicely. +If you really want an standard changelog, see + http://mail.gnome.org/archives/desktop-devel-list/2007-September/msg00186.html + +> > This removes the "prefered branch" issues with the old scheme, and +> > version numbers should increase monotonically +> +> The English word “should” is ambiguous in this context. Are you saying +> this is desirable, or are you predicting that it will likely be the +> case? + +Both. + +> I don't see how it's either, so am doubly confused :-) + +The timestamp should at least replace the patch release number, which +you agree is-desirable-to increase motonically ;). I also predict +that it will increase monotonically for any given branch, since the +branch HEAD will have both the most recent commit and the highest +version number. The only problem I can think of is having your clock +_way_ off, and that is unlikely enough to ignore. If you hop between +branches, the timestamp is much more likely to increase going to the +more modern branch than the bzr revision number, which desynchronize +between branches fairly quickly. + +> The convention for three-part version strings is often: +> +> * Major release number (big changes in how the program works, +> increasing monotonically per major release, with “0”indicating no +> major release yet) +> +> * Minor release number (smaller impact on how the program works, +> increasing monotonically per minor release, with “0” indicating no +> minor release yet since the previous major) +> +> * Patch release number (bug-fix and other changes that don't affect +> the documented interface, increasing monotonically per patch +> release, with “0” indicating no patch release since the previous +> major or minor) + +One problem is that we don't actually have "releases". People just +clone a branch, install, and go. If you're worried about stability, +just clone from a more stable branch (i.e., Chris' trunk). I think +this is good for distributed development, but maybe makes it hard to +package into a conventional release-based system. With the bzr patch +number in setup.py as the patch release number, I would be releasing +my 0.1.363 while Chris releases his 0.1.314, even though they're at +about the same point. I would rather be releasing my + 0.1.20090714121347 +while Chris releases his + 0.1.20090713154540 +Since then the similarity is clearer. + +At any rate, I think the two approaches are close enough that an +auto-updating timestamp beats a manually bumped patch number, since +no-one ever actually bumps the patch number ;). + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ea01c122-e629-4d5c-afa7-b180f4a8748b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ea01c122-e629-4d5c-afa7-b180f4a8748b/values new file mode 100644 index 0000000..a3f74c4 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ea01c122-e629-4d5c-afa7-b180f4a8748b/values @@ -0,0 +1,14 @@ +Alt-id: <20090714133732.GB6160@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 14 Jul 2009 09:37:32 -0400 + + +In-reply-to: 744435b7-1521-4059-a55d-f0c403d7b4d8 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f925e56f-26f9-4620-82fb-a0f160f27921/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f925e56f-26f9-4620-82fb-a0f160f27921/body new file mode 100644 index 0000000..5eeb353 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f925e56f-26f9-4620-82fb-a0f160f27921/body @@ -0,0 +1,88 @@ +On Thu, Jul 16, 2009 at 07:32:31PM +1000, Ben Finney wrote: +> "W. Trevor King" <wking@drexel.edu> writes: +> +> > On Wed, Jul 15, 2009 at 12:54:05AM +1000, Ben Finney wrote: +> > > "W. Trevor King" <wking@drexel.edu> writes: +> > > +> > > > On Tue, Jul 14, 2009 at 10:36:26PM +1000, Ben Finney wrote: +> > > > > Please, no. Timestamps aren't version strings, that's conflating +> > > > > two pieces of information with very different meanings. +> > > > > Correlating the two is the job of a [NEWS file]. +> > +> > > If you want a monotonically-increasing indicator of which revision +> > > we're up to, that's immediately available with the revision number +> > > from VCS on the main branch. That also has the advantage of +> > > producing consecutive numbers for each revision, by definition. +> > +> > But not during branch-switches, while my method skips large regions, +> > but probably increases during any reasonable branch-switch. +> +> I've read this several times now, and I don't see what it's saying. +> +> The assumption I'm making is that there is a single canonical “main +> branch”, from which releases will be made. + +I don't think you need to assume this. See my "virtual branch" +argument below. + +> The version number set in that branch is the one which determines +> the version of Bugs Everywhere as a whole. + +If you are suggesting that the dev branches adjust their release +number _by_hand_ to match the current trunk release number, that +allows switching, but sounds like a lot of work and isn't correct +anyway, since they are not in the same state as the trunk. + +> The revision number is only useful in the context of the branch, so it +> only matters when comparing versions within a branch. When you switch +> between branches, if you're interested in the revision number you'll +> still need to know which branch you're talking about. + +I think this is our main disagreement. I see all the branches as part +of the same codebase, with monotonically increasing timestamp patch +numbers. If you were to collapse all the commit snapshots down into a +single chronological "virtual branch", it would still make sense, it +would just be a bit unorganized. We do all try to move in the same +general direction ;). + +> Switching between branches doesn't change the canonical version string. + +Different released code should have different version numbers. + +> > For example, when I upgraded to rich root to pull Ben's patch, I'm not +> > sure if Chris upgraded the trunk and merged my branch, or just ditched +> > the trunk and cloned my branch. Using actual bzr revision numbers +> > would make switching branches that either wrong (in the case of rev-id +> > decreases) or confusing (in the case of a single non-consecutive +> > increase). +> +> This, then, is an argument for not having the revision number in the +> version string at all. The version then becomes a more traditional +> “major.minor.patch” tuple, and is only ever updated when some release +> manager of the canonical branch decides it's correct to do so. + +It is an argument for not using the revision number. You can avoid +revision numbers by using hand-coded patch numbers, or by using +timestamps, which is what we're trying to decide on :p. + +> If we use the ‘bzr version-info --format=python > foo_version.py’ +> command in some build routine, the branch's revision number will be +> available directly within Python by importing that module. That would +> allow it to be output in some UI, if that's what you're interested in +> seeing. + +True. Which means that whichever version string wins out, the other +side will still be able to access the info we both want included ;). +We can certainly suggest that bug reporters submit their + be --verbose-version +when they submit bugs. The only role of the official "version string" +is to make life easy for packagers. If they woln't be switching +branches, then either of our proposals are fine. If they will, then +I think timestamps work better. + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f925e56f-26f9-4620-82fb-a0f160f27921/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f925e56f-26f9-4620-82fb-a0f160f27921/values new file mode 100644 index 0000000..63a2cae --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f925e56f-26f9-4620-82fb-a0f160f27921/values @@ -0,0 +1,14 @@ +Alt-id: <20090716103855.GA8579@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 16 Jul 2009 06:38:55 -0400 + + +In-reply-to: fdb615a4-168a-467b-8090-875c998455e5 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f92c6180-0ed8-4acc-8ced-22995a0c016b/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f92c6180-0ed8-4acc-8ced-22995a0c016b/body new file mode 100644 index 0000000..dee72c7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f92c6180-0ed8-4acc-8ced-22995a0c016b/body @@ -0,0 +1,2 @@ +Verdict: run releases.py periodically, and post the tarballs on the +web. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f92c6180-0ed8-4acc-8ced-22995a0c016b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f92c6180-0ed8-4acc-8ced-22995a0c016b/values new file mode 100644 index 0000000..2e85e56 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f92c6180-0ed8-4acc-8ced-22995a0c016b/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Fri, 20 Nov 2009 21:45:50 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/fdb615a4-168a-467b-8090-875c998455e5/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/fdb615a4-168a-467b-8090-875c998455e5/body new file mode 100644 index 0000000..b36292a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/fdb615a4-168a-467b-8090-875c998455e5/body @@ -0,0 +1,55 @@ +"W. Trevor King" <wking@drexel.edu> writes: + +> On Wed, Jul 15, 2009 at 12:54:05AM +1000, Ben Finney wrote: +> > "W. Trevor King" <wking@drexel.edu> writes: +> > +> > > On Tue, Jul 14, 2009 at 10:36:26PM +1000, Ben Finney wrote: +> > > > Please, no. Timestamps aren't version strings, that's conflating +> > > > two pieces of information with very different meanings. +> > > > Correlating the two is the job of a [NEWS file]. +> +> > If you want a monotonically-increasing indicator of which revision +> > we're up to, that's immediately available with the revision number +> > from VCS on the main branch. That also has the advantage of +> > producing consecutive numbers for each revision, by definition. +> +> But not during branch-switches, while my method skips large regions, +> but probably increases during any reasonable branch-switch. + +I've read this several times now, and I don't see what it's saying. + +The assumption I'm making is that there is a single canonical “main +branch”, from which releases will be made. The version number set in +that branch is the one which determines the version of Bugs Everywhere +as a whole. + +The revision number is only useful in the context of the branch, so it +only matters when comparing versions within a branch. When you switch +between branches, if you're interested in the revision number you'll +still need to know which branch you're talking about. + +Switching between branches doesn't change the canonical version string. + +> For example, when I upgraded to rich root to pull Ben's patch, I'm not +> sure if Chris upgraded the trunk and merged my branch, or just ditched +> the trunk and cloned my branch. Using actual bzr revision numbers +> would make switching branches that either wrong (in the case of rev-id +> decreases) or confusing (in the case of a single non-consecutive +> increase). + +This, then, is an argument for not having the revision number in the +version string at all. The version then becomes a more traditional +“major.minor.patch” tuple, and is only ever updated when some release +manager of the canonical branch decides it's correct to do so. + +If we use the ‘bzr version-info --format=python > foo_version.py’ +command in some build routine, the branch's revision number will be +available directly within Python by importing that module. That would +allow it to be output in some UI, if that's what you're interested in +seeing. + +-- + \ “Never do anything against conscience even if the state demands | + `\ it.” —Albert Einstein | +_o__) | +Ben Finney diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/fdb615a4-168a-467b-8090-875c998455e5/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/fdb615a4-168a-467b-8090-875c998455e5/values new file mode 100644 index 0000000..3a42917 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/fdb615a4-168a-467b-8090-875c998455e5/values @@ -0,0 +1,14 @@ +Alt-id: <87d481ht1s.fsf@benfinney.id.au> + + +Author: Ben Finney <bignose+hates-spam@benfinney.id.au> + + +Content-type: text/plain + + +Date: Thu, 16 Jul 2009 19:32:31 +1000 + + +In-reply-to: cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42/body new file mode 100644 index 0000000..30e3cbd --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42/body @@ -0,0 +1,44 @@ +"W. Trevor King" <wking@drexel.edu> writes: + +> On Tue, Jul 14, 2009 at 10:36:26PM +1000, Ben Finney wrote: +> > Please, no. Timestamps aren't version strings, that's conflating two +> > pieces of information with very different meanings. Correlating the +> > two is the job of a changelog. +> +> Which we don't bother keeping (also NEWS), since "bzr log" works so +> nicely. + +That's not a changelog, that's a commit log of every source-level commit +made. Far too much detail for a changelog of *user-visible* changes +associated with a release. + +> The timestamp should at least replace the patch release number, which +> you agree is-desirable-to increase motonically ;). + +I still disagree that a timestamp is the right thing to use there. If +you want a monotonically-increasing indicator of which revision we're up +to, that's immediately available with the revision number from VCS on +the main branch. That also has the advantage of producing consecutive +numbers for each revision, by definition. + +> One problem is that we don't actually have "releases". People just +> clone a branch, install, and go. + +I agree that's a problem. I think the solution is to start making +releases, with specific version strings, as source tarballs. + +James Rowe <jnrowe@gmail.com> writes: + +> Isn't there a bzr web interface that at least supports creating +> tarballs/zips? + +Even better: ‘bzr export /tmp/foo.tar.gz’ will create a source tarball +of all the files in the branch's VCS inventory. All we need to do is +start the practice of tagging a release in the VCS, and export the +tarball at that time. + +-- + \ “Pinky, are you pondering what I'm pondering?” “Well, I think | + `\ so (hiccup), but Kevin Costner with an English accent?” —_Pinky | +_o__) and The Brain_ | +Ben Finney diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42/values new file mode 100644 index 0000000..56bef0b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42/values @@ -0,0 +1,14 @@ +Alt-id: <87k52bjoxe.fsf_-_@benfinney.id.au> + + +Author: Ben Finney <bignose+hates-spam@benfinney.id.au> + + +Content-type: text/plain + + +Date: Wed, 15 Jul 2009 00:54:05 +1000 + + +In-reply-to: cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/values new file mode 100644 index 0000000..89203d2 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/values @@ -0,0 +1,17 @@ +creator: W. Trevor King <wking@drexel.edu> + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: wishlist + + +status: fixed + + +summary: How should we version BE? + + +time: Tue, 21 Jul 2009 17:19:22 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/56506b73-36cc-4e32-a578-258a219edba8/comments/0a995544-20dc-42a6-8d3f-348ebbc8921e/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/56506b73-36cc-4e32-a578-258a219edba8/comments/0a995544-20dc-42a6-8d3f-348ebbc8921e/body new file mode 100644 index 0000000..8596c92 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/56506b73-36cc-4e32-a578-258a219edba8/comments/0a995544-20dc-42a6-8d3f-348ebbc8921e/body @@ -0,0 +1,18 @@ +Since we'll be distributing a non-bzr-repo version, it would be nice +to adapt our 'submit bug' procedure + $ be new "The demuxulizer is broken" + Created bug with ID 48f + $ be comment 48f + <Describe bug> + $ bzr commit --message "Reported bug in demuxulizer" + $ bzr send --mail-to "be-devel@bugseverywhere.org" +to one that works with this setup. Without guaranteed versioning, +that would probably be something along the lines of + $ be new "The demuxulizer is broken" + Created bug with ID 48f + $ be comment 48f + <Describe bug> + $ be email-bugs [--to be-devel@bugseverywhere.org] 48f +With interfaces/email/interactive listening on the recieving end to +grab new-bug emails and import them into an incoming bug repository. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/56506b73-36cc-4e32-a578-258a219edba8/comments/0a995544-20dc-42a6-8d3f-348ebbc8921e/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/56506b73-36cc-4e32-a578-258a219edba8/comments/0a995544-20dc-42a6-8d3f-348ebbc8921e/values new file mode 100644 index 0000000..4bd8f81 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/56506b73-36cc-4e32-a578-258a219edba8/comments/0a995544-20dc-42a6-8d3f-348ebbc8921e/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Fri, 20 Nov 2009 13:31:25 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/56506b73-36cc-4e32-a578-258a219edba8/comments/4068c833-0c06-475e-8b7e-6701bc416dee/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/56506b73-36cc-4e32-a578-258a219edba8/comments/4068c833-0c06-475e-8b7e-6701bc416dee/body new file mode 100644 index 0000000..d3d9d0c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/56506b73-36cc-4e32-a578-258a219edba8/comments/4068c833-0c06-475e-8b7e-6701bc416dee/body @@ -0,0 +1,28 @@ +> With interfaces/email/interactive listening on the recieving end to +> grab new-bug emails and import them into an incoming bug repository. + +The email-bugs -> be-handle-mail import is based on `be import-xml`. +The current import-xml implementation allows good control over what +gets overwritten during a merge by overriding only those fields +defined in the incoming XML. + +For clients without the versioned bugdir (e.g. they installed via a +release tarball or their distro's packaging system), `be email-bugs` +will not know what fields have been changed/added/etc., so it sets +_all_ the fields in the outgoing XML. Importing that XML file will +override any changes that may have been made to the listed +bugs/comments between the release and your current source version, so +you may have to do some manual tweaking of the post-merge bugdir. + +One possible workaround would be to change the merge algorithm in +import-xml to take advantage of version information given in the XML +file. import-xml could checkout the shared root version of any +modified bugs, and compute the changes made by the remote user and +those made in the local tree. It could then merge these changes more +intelligently, by prompting the user, keeping the local changes, +keeping the remote changes, etc. + +While the more automated approach might be better, it's also more +complicated, so for now we'll stick with the simple "override all +fields defined in the XML" approach. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/56506b73-36cc-4e32-a578-258a219edba8/comments/4068c833-0c06-475e-8b7e-6701bc416dee/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/56506b73-36cc-4e32-a578-258a219edba8/comments/4068c833-0c06-475e-8b7e-6701bc416dee/values new file mode 100644 index 0000000..e77ec55 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/56506b73-36cc-4e32-a578-258a219edba8/comments/4068c833-0c06-475e-8b7e-6701bc416dee/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 29 Nov 2009 01:19:05 +0000 + + +In-reply-to: 0a995544-20dc-42a6-8d3f-348ebbc8921e + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/56506b73-36cc-4e32-a578-258a219edba8/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/56506b73-36cc-4e32-a578-258a219edba8/values new file mode 100644 index 0000000..2d546cb --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/56506b73-36cc-4e32-a578-258a219edba8/values @@ -0,0 +1,17 @@ +creator: W. Trevor King <wking@drexel.edu> + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: fixed + + +summary: be email-bugs for bug submission from bzr-less users + + +time: Fri, 20 Nov 2009 13:26:59 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/72dab0c4-f04d-4ff0-9319-f55aafaea627/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/72dab0c4-f04d-4ff0-9319-f55aafaea627/body new file mode 100644 index 0000000..fd86659 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/72dab0c4-f04d-4ff0-9319-f55aafaea627/body @@ -0,0 +1 @@ +<html><head></head><body>Hello world</body></html> diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/72dab0c4-f04d-4ff0-9319-f55aafaea627/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/72dab0c4-f04d-4ff0-9319-f55aafaea627/values new file mode 100644 index 0000000..f460840 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/72dab0c4-f04d-4ff0-9319-f55aafaea627/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/html + + +Date: Mon, 22 Jun 2009 20:05:00 +0000 + + +In-reply-to: c454aa67-ca30-43e8-9be4-58cbddd01b63 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/c454aa67-ca30-43e8-9be4-58cbddd01b63/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/c454aa67-ca30-43e8-9be4-58cbddd01b63/body new file mode 100644 index 0000000..f673cc5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/c454aa67-ca30-43e8-9be4-58cbddd01b63/body @@ -0,0 +1,30 @@ +Excerpt from my mail to the list on Sat, 20 Jun 2009 21:55:54 -0400: + +On Mon, Nov 24, 2008 at 07:15:08PM -0500, Aaron Bentley wrote: +> 576:om: Allow attachments +> Sensible. + +I'm not as convinced they are a good idea as I once was. I've just +added comments-from-stdin, e.g. + some-invalid-command | be comment <bug-id> - +Which is mostly what I'd be using attachments for anyway. If you +really want to support the attachments/mime-types etc. like we had +maybe been leaning towards before, you'd need to look at the output of +`be show ...' with an email client, which seems a bit excessive. Do +we even want mime types at all? With the xml output a la Thomas, you +should be able to pipe into whatever sort of `viewer' you want, and it +doesn't end up being hardcoded into the main repo. + + +Notes since my email: + +be->xml->mutt has since been implemented, and it preserves comment +mime-type. This allows those that want to go crazy to attach whatever +they want to their comments: + + $ echo "<html><head></head><body>Hello world</body></html>" | be comment --content-type text/html 576:2 - + +I think non-text attachments without a browser/mail-viewer don't make +sense, so I'm closing this bug. Feel free to keep it open in your own +repo, or argue with me on the list ;). + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/c454aa67-ca30-43e8-9be4-58cbddd01b63/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/c454aa67-ca30-43e8-9be4-58cbddd01b63/values new file mode 100644 index 0000000..0c7cd6c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/c454aa67-ca30-43e8-9be4-58cbddd01b63/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 20:03:27 +0000 + + +In-reply-to: d83a5436-85e3-42c7-9a89-a6d50df9d279 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/d83a5436-85e3-42c7-9a89-a6d50df9d279/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/d83a5436-85e3-42c7-9a89-a6d50df9d279/body new file mode 100644 index 0000000..118afc8 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/d83a5436-85e3-42c7-9a89-a6d50df9d279/body @@ -0,0 +1,4 @@ +I've added comments-from-stdin, so we can add tracebacks, e.g. with + + $ be list --invalid-option | be comment <bug-id> - + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/d83a5436-85e3-42c7-9a89-a6d50df9d279/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/d83a5436-85e3-42c7-9a89-a6d50df9d279/values new file mode 100644 index 0000000..7c8dc1c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/d83a5436-85e3-42c7-9a89-a6d50df9d279/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Fri, 19 Jun 2009 20:22:19 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/values new file mode 100644 index 0000000..16906f1 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: Allow attachments + + +time: Wed, 25 Jan 2006 15:43:46 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5920ef40-ce56-44e0-9e2d-e9b888ab2880/comments/1b21dabc-a90c-4687-bea0-7e9e69956e23/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5920ef40-ce56-44e0-9e2d-e9b888ab2880/comments/1b21dabc-a90c-4687-bea0-7e9e69956e23/body new file mode 100644 index 0000000..7485ebd --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5920ef40-ce56-44e0-9e2d-e9b888ab2880/comments/1b21dabc-a90c-4687-bea0-7e9e69956e23/body @@ -0,0 +1,3 @@ +> libbe.mapfile.IllegalValue: Illegal value "Foo Bar <foo@example.com>\n" + +The trailing endline was the problem. Fixed now. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5920ef40-ce56-44e0-9e2d-e9b888ab2880/comments/1b21dabc-a90c-4687-bea0-7e9e69956e23/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5920ef40-ce56-44e0-9e2d-e9b888ab2880/comments/1b21dabc-a90c-4687-bea0-7e9e69956e23/values new file mode 100644 index 0000000..6275e96 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5920ef40-ce56-44e0-9e2d-e9b888ab2880/comments/1b21dabc-a90c-4687-bea0-7e9e69956e23/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Wed, 19 May 2010 11:16:23 +0000 + + +In-reply-to: 285006ba-16fc-4d09-86f1-893ff515e487 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5920ef40-ce56-44e0-9e2d-e9b888ab2880/comments/285006ba-16fc-4d09-86f1-893ff515e487/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5920ef40-ce56-44e0-9e2d-e9b888ab2880/comments/285006ba-16fc-4d09-86f1-893ff515e487/body new file mode 100644 index 0000000..8aa34de --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5920ef40-ce56-44e0-9e2d-e9b888ab2880/comments/285006ba-16fc-4d09-86f1-893ff515e487/body @@ -0,0 +1,4 @@ +If I have just "foo@example.com" in _darcs/prefs/author, be is perfectly happy. + +But having instead "Foo Bar <foo@example.com>" causes this error on be new: +libbe.mapfile.IllegalValue: Illegal value "Foo Bar <foo@example.com>\n" diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5920ef40-ce56-44e0-9e2d-e9b888ab2880/comments/285006ba-16fc-4d09-86f1-893ff515e487/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5920ef40-ce56-44e0-9e2d-e9b888ab2880/comments/285006ba-16fc-4d09-86f1-893ff515e487/values new file mode 100644 index 0000000..012e25d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5920ef40-ce56-44e0-9e2d-e9b888ab2880/comments/285006ba-16fc-4d09-86f1-893ff515e487/values @@ -0,0 +1,8 @@ +Author: Eric Kow <eric.kow@gmail.com> + + +Content-type: text/plain + + +Date: Mon, 29 Mar 2010 15:52:53 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5920ef40-ce56-44e0-9e2d-e9b888ab2880/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5920ef40-ce56-44e0-9e2d-e9b888ab2880/values new file mode 100644 index 0000000..de23989 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5920ef40-ce56-44e0-9e2d-e9b888ab2880/values @@ -0,0 +1,17 @@ +creator: Eric Kow <eric.kow@gmail.com> + + +reporter: Eric Kow <eric.kow@gmail.com> + + +severity: minor + + +status: fixed + + +summary: Fancy _darcs/prefs/author contents confuse be + + +time: Mon, 29 Mar 2010 15:50:39 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/8015d736-f3ea-4085-940c-552c01a287ef/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/8015d736-f3ea-4085-940c-552c01a287ef/body new file mode 100644 index 0000000..bd80264 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/8015d736-f3ea-4085-940c-552c01a287ef/body @@ -0,0 +1 @@ +Merged into bug 09f84059-fc8e-4954-b24d-a2b33ef21bf4
\ No newline at end of file diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/8015d736-f3ea-4085-940c-552c01a287ef/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/8015d736-f3ea-4085-940c-552c01a287ef/values new file mode 100644 index 0000000..929dce6 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/8015d736-f3ea-4085-940c-552c01a287ef/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 13:35:42 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/eff20807-07f0-444d-8992-f69ab3f526c5/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/eff20807-07f0-444d-8992-f69ab3f526c5/body new file mode 100644 index 0000000..9106d37 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/eff20807-07f0-444d-8992-f69ab3f526c5/body @@ -0,0 +1,7 @@ +This is an *rst* comment.
+Which means newlines don't matter, except when they gang up.
+
+lala
+
+ - Bullet
+ - Bullet
\ No newline at end of file diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values new file mode 100644 index 0000000..54570b2 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values @@ -0,0 +1,8 @@ +Author: abentley + + +Content-type: text/restructured + + +Date: Thu, 06 Apr 2006 16:54:57 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597a7386-643f-4559-8dc4-6871924229b6/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597a7386-643f-4559-8dc4-6871924229b6/values new file mode 100644 index 0000000..b0bfcf3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597a7386-643f-4559-8dc4-6871924229b6/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: RST test + + +time: Thu, 06 Apr 2006 16:52:46 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/0922b8f7-b5ce-4572-8e55-c4b34dafe6cf/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/0922b8f7-b5ce-4572-8e55-c4b34dafe6cf/body new file mode 100644 index 0000000..991501f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/0922b8f7-b5ce-4572-8e55-c4b34dafe6cf/body @@ -0,0 +1,9 @@ +Hello! + +--- 8< --- +$ be comment b35 +ERROR: +No comment supplied, and EDITOR not specified. +--- >8 --- + +When EDITOR enviroment variable not set try to use vi editor as default editor diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/0922b8f7-b5ce-4572-8e55-c4b34dafe6cf/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/0922b8f7-b5ce-4572-8e55-c4b34dafe6cf/values new file mode 100644 index 0000000..d6047ac --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/0922b8f7-b5ce-4572-8e55-c4b34dafe6cf/values @@ -0,0 +1,11 @@ +Alt-id: <201003161610.10674.antonbatenev@yandex.ru> + + +Author: Anton Batenev <antonbatenev@yandex.ru> + + +Content-type: text/plain + + +Date: Tue, 16 Mar 2010 20:10:10 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/212570d2-8116-462e-9664-9ea8d3976b99/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/212570d2-8116-462e-9664-9ea8d3976b99/body new file mode 100644 index 0000000..efebc29 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/212570d2-8116-462e-9664-9ea8d3976b99/body @@ -0,0 +1 @@ +I agree with Ben. Setting status to "wontfix". diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/212570d2-8116-462e-9664-9ea8d3976b99/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/212570d2-8116-462e-9664-9ea8d3976b99/values new file mode 100644 index 0000000..6abf782 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/212570d2-8116-462e-9664-9ea8d3976b99/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Fri, 19 Mar 2010 06:17:38 +0000 + + +In-reply-to: 5e8d490e-ee06-4403-96dd-ad8eac66b21c + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/5e8d490e-ee06-4403-96dd-ad8eac66b21c/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/5e8d490e-ee06-4403-96dd-ad8eac66b21c/body new file mode 100644 index 0000000..92264a7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/5e8d490e-ee06-4403-96dd-ad8eac66b21c/body @@ -0,0 +1,17 @@ +Anton Batenev <antonbatenev@yandex.ru> writes: + +> --- 8< --- +> $ be comment b35 +> ERROR: +> No comment supplied, and EDITOR not specified. +> --- >8 --- +> +> When EDITOR enviroment variable not set try to use vi editor as +> default editor + +-1. It's up to the local operating system configuration to set a default +EDITOR value, and up to the user to over-ride that if they want to. An +application shouldn't be guessing in the absence of those conventions. + +The behaviour is clear and the message provides a way for the user to +rectify the problem in a standard way. It doesn't need to change. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/5e8d490e-ee06-4403-96dd-ad8eac66b21c/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/5e8d490e-ee06-4403-96dd-ad8eac66b21c/values new file mode 100644 index 0000000..17ed58f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/comments/5e8d490e-ee06-4403-96dd-ad8eac66b21c/values @@ -0,0 +1,14 @@ +Alt-id: <877hp9w0zy.fsf@benfinney.id.au> + + +Author: Ben Finney <bignose+hates-spam@benfinney.id.au> + + +Content-type: text/plain + + +Date: Fri, 19 Mar 2010 16:31:29 +0000 + + +In-reply-to: <201003161610.10674.antonbatenev@yandex.ru> + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/values new file mode 100644 index 0000000..79ca1b1 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/597b03f5-76cb-4951-b370-a01573ad2f75/values @@ -0,0 +1,17 @@ +creator: W. Trevor King <wking@drexel.edu> + + +reporter: Anton Batenev <abbat@abbat> + + +severity: minor + + +status: wontfix + + +summary: Set a default EDITOR incase the user has not + + +time: Fri, 19 Mar 2010 06:11:12 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/628a050a-f969-4290-8468-f5e991528f40/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/628a050a-f969-4290-8468-f5e991528f40/body new file mode 100644 index 0000000..40d9e29 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/628a050a-f969-4290-8468-f5e991528f40/body @@ -0,0 +1,11 @@ +Either of these could be added at the + libbe.command.base.Command.run +level. + +The Git hooks would be 'pre-<command-name>' and 'post-<command-name>'. + +Oh, and the hooks are therefore command-level hooks, not storage-level +hooks. We still want storage-level hooks for notification emails, etc, +and they would definately have to follow the Git directory approach. +Hmm. Storage level hooks will be awkward... + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/628a050a-f969-4290-8468-f5e991528f40/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/628a050a-f969-4290-8468-f5e991528f40/values new file mode 100644 index 0000000..decd72f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/628a050a-f969-4290-8468-f5e991528f40/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 31 Jan 2010 18:04:49 +0000 + + +In-reply-to: f3e90a7e-b8c4-4a7c-8609-6a783ae59762 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/f3e90a7e-b8c4-4a7c-8609-6a783ae59762/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/f3e90a7e-b8c4-4a7c-8609-6a783ae59762/body new file mode 100644 index 0000000..80133bf --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/f3e90a7e-b8c4-4a7c-8609-6a783ae59762/body @@ -0,0 +1,18 @@ +Provide hooks so users can easily setup auto-commits, subscriber +notification, etc. Probably either Darcs-style options: + $ be COMMAND --help + ... + --posthook=COMMAND Specify command to run after this command. + --no-posthook Do not run posthook command. + --prompt-posthook Prompt before running posthook. [DEFAULT] + --run-posthook Run posthook command without prompting. + ... +or a Git-style hooks directory: + $ tree .be + .be/ + |-- version + |-- hooks + . |-- post-commit.sh + . |-- pre-commit.sh + `-- update.sh + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/f3e90a7e-b8c4-4a7c-8609-6a783ae59762/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/f3e90a7e-b8c4-4a7c-8609-6a783ae59762/values new file mode 100644 index 0000000..4cd8b7a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/f3e90a7e-b8c4-4a7c-8609-6a783ae59762/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 23 Jan 2010 19:17:10 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/values new file mode 100644 index 0000000..3e88ad1 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/values @@ -0,0 +1,21 @@ +creator: W. Trevor King <wking@drexel.edu> + + +extra_strings: +- BLOCKS:52034fd0-ec50-424d-b25d-2beaf2d2c317 + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: open + + +summary: Add change hooks to Storage class + + +time: Sat, 23 Jan 2010 19:08:40 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/62a74b85-0d4b-49f5-8794-74bafd871cd4/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/62a74b85-0d4b-49f5-8794-74bafd871cd4/values new file mode 100644 index 0000000..9f7616b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/62a74b85-0d4b-49f5-8794-74bafd871cd4/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: Indicate presence of Comments + + +time: Wed, 25 Jan 2006 15:18:58 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/65776f00-34d8-4b58-874d-333196a5e245/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/65776f00-34d8-4b58-874d-333196a5e245/values new file mode 100644 index 0000000..435c733 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/65776f00-34d8-4b58-874d-333196a5e245/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: Select severity filter + + +time: Wed, 04 Jan 2006 21:07:08 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/6622c06a-ed84-4d45-8011-a082fca219b6/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/6622c06a-ed84-4d45-8011-a082fca219b6/values new file mode 100644 index 0000000..ba3304f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/6622c06a-ed84-4d45-8011-a082fca219b6/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: Better word wrapping in comments. (kill <pre>.) + + +time: Mon, 30 Jan 2006 20:03:25 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/68ba7f0c-ca5f-4f49-a508-e39150c07e13/comments/be64734c-d9a8-4f6d-83eb-e9b6c9adc0bf/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/68ba7f0c-ca5f-4f49-a508-e39150c07e13/comments/be64734c-d9a8-4f6d-83eb-e9b6c9adc0bf/body new file mode 100644 index 0000000..dd464bf --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/68ba7f0c-ca5f-4f49-a508-e39150c07e13/comments/be64734c-d9a8-4f6d-83eb-e9b6c9adc0bf/body @@ -0,0 +1,3 @@ +Per-tree severity and target are now supported. + +I'm not sure what Aaron meant be "BE ids". diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/68ba7f0c-ca5f-4f49-a508-e39150c07e13/comments/be64734c-d9a8-4f6d-83eb-e9b6c9adc0bf/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/68ba7f0c-ca5f-4f49-a508-e39150c07e13/comments/be64734c-d9a8-4f6d-83eb-e9b6c9adc0bf/values new file mode 100644 index 0000000..9f4fd8c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/68ba7f0c-ca5f-4f49-a508-e39150c07e13/comments/be64734c-d9a8-4f6d-83eb-e9b6c9adc0bf/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 17:29:30 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/68ba7f0c-ca5f-4f49-a508-e39150c07e13/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/68ba7f0c-ca5f-4f49-a508-e39150c07e13/values new file mode 100644 index 0000000..b8e8291 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/68ba7f0c-ca5f-4f49-a508-e39150c07e13/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: wishlist + + +status: closed + + +summary: Support per-tree settings for severity, target, BE ids + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/comments/f2011471-56cb-46e2-813b-1ac336ee7bbc/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/comments/f2011471-56cb-46e2-813b-1ac336ee7bbc/body new file mode 100644 index 0000000..30f02d7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/comments/f2011471-56cb-46e2-813b-1ac336ee7bbc/body @@ -0,0 +1 @@ + On a new bug, if I add comment then click "Update", the Summary goes missing!
\ No newline at end of file diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/comments/f2011471-56cb-46e2-813b-1ac336ee7bbc/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/comments/f2011471-56cb-46e2-813b-1ac336ee7bbc/values new file mode 100644 index 0000000..a1e25ea --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/comments/f2011471-56cb-46e2-813b-1ac336ee7bbc/values @@ -0,0 +1,8 @@ +Author: abentley + + +Content-type: text/plain + + +Date: Fri, 27 Jan 2006 14:30:26 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/values new file mode 100644 index 0000000..fcc0b22 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/values @@ -0,0 +1,15 @@ +creator: abentley + + +severity: serious + + +status: closed + + +summary: On a new bug, if I add or edit a comment, then click "Update", the Summary + goes missing. + + +time: Fri, 27 Jan 2006 14:29:51 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/73a767f4-75e7-4cde-9e24-91bff99ab428/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/73a767f4-75e7-4cde-9e24-91bff99ab428/values new file mode 100644 index 0000000..9bd84b0 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/73a767f4-75e7-4cde-9e24-91bff99ab428/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: serious + + +status: fixed + + +summary: implement comments + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/74cccfbf-069d-4e99-8cab-adaa35f9a2eb/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/74cccfbf-069d-4e99-8cab-adaa35f9a2eb/values new file mode 100644 index 0000000..473f9a7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/74cccfbf-069d-4e99-8cab-adaa35f9a2eb/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: Can't close bugs + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/764b812f-a0bb-4f4d-8e2f-c255c9474a0e/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/764b812f-a0bb-4f4d-8e2f-c255c9474a0e/values new file mode 100644 index 0000000..4406356 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/764b812f-a0bb-4f4d-8e2f-c255c9474a0e/values @@ -0,0 +1,17 @@ +creator: W. Trevor King <wking@drexel.edu> + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: fixed + + +summary: Add docstrings explaining role of the libbe submodules. + + +time: Mon, 31 Aug 2009 13:57:54 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/body new file mode 100644 index 0000000..c006a1a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/body @@ -0,0 +1 @@ +The commands that can easily be tested are now being tested diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values new file mode 100644 index 0000000..ead68e1 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values @@ -0,0 +1,8 @@ +Author: abentley + + +Content-type: text/plain + + +Date: Thu, 24 Mar 2005 17:04:47 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/body new file mode 100644 index 0000000..e0b86a5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/body @@ -0,0 +1,4 @@ +We've got coverage of set_root and new. This leaves +close, comment, list, open, severity, show, target, upgrade + +It's quite nice, though, that doctest captures stdout. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values new file mode 100644 index 0000000..90beac0 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values @@ -0,0 +1,8 @@ +Author: abentley + + +Content-type: text/plain + + +Date: Thu, 24 Mar 2005 13:05:13 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/values new file mode 100644 index 0000000..e2a930c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: serious + + +status: fixed + + +summary: Add test cases + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/2f6b71c5-45b3-473f-bd14-a1fe41bafcee/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/2f6b71c5-45b3-473f-bd14-a1fe41bafcee/body new file mode 100644 index 0000000..c602969 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/2f6b71c5-45b3-473f-bd14-a1fe41bafcee/body @@ -0,0 +1 @@ +Fixed at least by commit 273, probably way before. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/2f6b71c5-45b3-473f-bd14-a1fe41bafcee/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/2f6b71c5-45b3-473f-bd14-a1fe41bafcee/values new file mode 100644 index 0000000..2d5059c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/2f6b71c5-45b3-473f-bd14-a1fe41bafcee/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 24 Nov 2008 13:08:07 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/5a6b44f5-9d1d-4e2e-a42c-f5423c43a1dc/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/5a6b44f5-9d1d-4e2e-a42c-f5423c43a1dc/body new file mode 100644 index 0000000..89160a2 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/5a6b44f5-9d1d-4e2e-a42c-f5423c43a1dc/body @@ -0,0 +1 @@ +It's hard to report bugs if you can't even get it working.
\ No newline at end of file diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/5a6b44f5-9d1d-4e2e-a42c-f5423c43a1dc/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/5a6b44f5-9d1d-4e2e-a42c-f5423c43a1dc/values new file mode 100644 index 0000000..dcdd529 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/5a6b44f5-9d1d-4e2e-a42c-f5423c43a1dc/values @@ -0,0 +1,8 @@ +Author: abentley + + +Content-type: text/plain + + +Date: Wed, 21 Dec 2005 21:53:47 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/values new file mode 100644 index 0000000..d05eed6 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: no tests for --help, -h, help, etc. + + +time: Tue, 17 May 2005 13:27:18 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7cb42a60-c977-40db-b2a1-19917c10cace/comments/a555d577-7f8c-49f2-96f6-263ce5fdff8e/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7cb42a60-c977-40db-b2a1-19917c10cace/comments/a555d577-7f8c-49f2-96f6-263ce5fdff8e/body new file mode 100644 index 0000000..c45d2c7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7cb42a60-c977-40db-b2a1-19917c10cace/comments/a555d577-7f8c-49f2-96f6-263ce5fdff8e/body @@ -0,0 +1,23 @@ +Usage case: + * User A installs version 1.0 which contains bug /abc. + * Development continues, fixing bug /abc. + * User A wants to see which bugs affect their version, and query the + main bug repository. + $ be --repo http://bugseverywhere.org/bugs list --this-version + bea/abc:om: Whatsit not implemented. + $ be --repo http://bugseverywhere.org/bugs show bea/abc + ID : abc... + Short name : bea/abc + Severity : minor + Status : fixed + ... + Whatsit not implemented. + --------- Comment --------- + Name: bea/abc/def + From: ... + Date: Sat, 23 Jan 2010 14:00 ... + + Whatsit implemented. + "Aha!", says the user, "I need to upgrade to a version of BE + that's more recent than 2010/01/23 to get Whatsit functionality." + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7cb42a60-c977-40db-b2a1-19917c10cace/comments/a555d577-7f8c-49f2-96f6-263ce5fdff8e/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7cb42a60-c977-40db-b2a1-19917c10cace/comments/a555d577-7f8c-49f2-96f6-263ce5fdff8e/values new file mode 100644 index 0000000..3b8ba33 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7cb42a60-c977-40db-b2a1-19917c10cace/comments/a555d577-7f8c-49f2-96f6-263ce5fdff8e/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 23 Jan 2010 18:59:03 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7cb42a60-c977-40db-b2a1-19917c10cace/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7cb42a60-c977-40db-b2a1-19917c10cace/values new file mode 100644 index 0000000..cb9a372 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7cb42a60-c977-40db-b2a1-19917c10cace/values @@ -0,0 +1,17 @@ +creator: W. Trevor King <wking@drexel.edu> + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: open + + +summary: '`be list --this-version` listing bugs affecting your version of BE' + + +time: Sat, 23 Jan 2010 18:49:03 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7d182ab9-9c0c-4b4f-885e-c5762d7a2437/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7d182ab9-9c0c-4b4f-885e-c5762d7a2437/values new file mode 100644 index 0000000..ae215bc --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7d182ab9-9c0c-4b4f-885e-c5762d7a2437/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: BEweb fails to set bug creation date + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/401950a0-a5ff-46f3-afac-a9cfb300f94b/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/401950a0-a5ff-46f3-afac-a9cfb300f94b/body new file mode 100644 index 0000000..6ad8230 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/401950a0-a5ff-46f3-afac-a9cfb300f94b/body @@ -0,0 +1,39 @@ +In my Tue, 25 Nov 2008 08:30:19 -0500 email: + +Implemented as a free-form value field similar to target? A +comma-seperated list of tags? Perhaps once we have per-bug/comment +attribute searching it would be easier to have a 'create-attribute' +becommand, e.g. + be create-attribute [-valid=X,Y,Z] [bugdir|bug|comment] [NAME] [DEFAULT] + +We could ship some suggested configuration scripts to set people up, +and keep the core code more general/flexible. + + +Plan: + +Extend and make more consitent the settings_property() attributes. +Create becommand/(create/remove)-attribute for logic-less attributes. +Create a few mix-ins for logic-ed attributes + +Usage example: + Goal: + set up for `be depends BUGA BUGB`, `be depends --tree BUGA`, etc + Procedure: + be set --apend mixins bug:dependency + Where we've defined + becommands/depends.py, but it is hidden until the mixin is activated + libbe/mixins/bug/dependency.Mixin (inheriting from BugMixin) + to + parse/generate comma seperated dependency uuids for saving/loading + pretty-print the dependency list (e.g. uuid->shortname) + walk the dependency tree and check target bug status. + +With more complicated mixins, there could be inter-mixin dependencies, +e.g. a dependency tracker that searches depends based on bug.status +might depend on the base dependency mixin. This way people who need +it could make rich interfaces without confusing the people who don't. + +How does that sound? + + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/401950a0-a5ff-46f3-afac-a9cfb300f94b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/401950a0-a5ff-46f3-afac-a9cfb300f94b/values new file mode 100644 index 0000000..c054670 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/401950a0-a5ff-46f3-afac-a9cfb300f94b/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 20:39:39 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/6010e186-0260-44e5-8442-8df2269910ce/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/6010e186-0260-44e5-8442-8df2269910ce/body new file mode 100644 index 0000000..33638ab --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/6010e186-0260-44e5-8442-8df2269910ce/body @@ -0,0 +1,5 @@ +It's tricky to say whether we should have dependencies or reverse dependencies +or both. + +In the case where a bug is removed, normal dependencies mean that its +dependencies are erased from this system. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/6010e186-0260-44e5-8442-8df2269910ce/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/6010e186-0260-44e5-8442-8df2269910ce/values new file mode 100644 index 0000000..32e49e7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/6010e186-0260-44e5-8442-8df2269910ce/values @@ -0,0 +1,11 @@ +Author: abentley + + +Content-type: text/plain + + +Date: Mon, 17 Apr 2006 20:59:15 +0000 + + +In-reply-to: f87fd684-6af1-498d-98d5-f915bcee76a9 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/80780fa9-69f8-438c-8fbf-5a702b3badc1/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/80780fa9-69f8-438c-8fbf-5a702b3badc1/body new file mode 100644 index 0000000..f1ce046 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/80780fa9-69f8-438c-8fbf-5a702b3badc1/body @@ -0,0 +1,2 @@ +Arbitrary tagging now supported via `be tag'. +Dependencies supported via `be depend'. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/80780fa9-69f8-438c-8fbf-5a702b3badc1/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/80780fa9-69f8-438c-8fbf-5a702b3badc1/values new file mode 100644 index 0000000..d2f0f5c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/80780fa9-69f8-438c-8fbf-5a702b3badc1/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 25 Jun 2009 12:39:26 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/bb988ed4-d3d5-4e49-b67e-c7ccb8ae44d3/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/bb988ed4-d3d5-4e49-b67e-c7ccb8ae44d3/body new file mode 100644 index 0000000..57439b7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/bb988ed4-d3d5-4e49-b67e-c7ccb8ae44d3/body @@ -0,0 +1,47 @@ +From my Tue, 25 Nov 2008 13:27:12 -0500 email: + +> >> 7ec:om: Arbitrary tags +> >> Sensible +> > +> > Implemented as a free-form value field similar to target? A +> > comma-seperated list of tags? +> + +That is a much better format than my unmergable one ;). + +> "append" usually has two "p"s. Is the omission deliberate? + +Nope, sorry :p + +> It sounds pretty complicated. I would probably use a type system rather +> than "mixins", and define types as "scalar", "set" and maybe "list" and +> "map". Dependencies would be a set, and their special behaviour would +> be hardcoded according to their name, not a property of their type. + +Ok. I'm just worried about bloat. It's pretty easy to move things +around at the moment, but I'm worried that adding lots of attributes +with special code will start a slippery slope of trying to satisfy +everybody internally. Then things start looking more like Arch, with +newbies scared off by the confusion. I know the Arch people like the +power, but it took me several hours to figure out how to create a +repository ;). Some people like bug dependencies, and some do not + e.g. + https://bugs.launchpad.net/malone/+bug/95419 + http://trac.edgewall.org/ticket/31 + +From the *long* Trac post, you can see that this is divisive issue. + +I would be in favor of emulating TracCrossReferences +(http://trac.edgewall.org/wiki/TracCrossReferences) in our core. We +could have references and backlinks fields for bugs (and comments?). +But I'd rather not add blocking, etc. However, having a seperate +plugin obviously doesn't work for some people ;). We'd like to bundle +lots of functionality, but keep the core fairly clean and flexible. + +Therefore, I'd like a way to put non-core implememtation code in a +seperate submod. We already call our libbe code "plugins", and we're +extending the builtin BugDir, Bug, etc code, so I thought we'd call +the non-core submods mixins (see http://en.wikipedia.org/wiki/Mixin). + +Anyhow, just my 2c. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/bb988ed4-d3d5-4e49-b67e-c7ccb8ae44d3/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/bb988ed4-d3d5-4e49-b67e-c7ccb8ae44d3/values new file mode 100644 index 0000000..8874446 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/bb988ed4-d3d5-4e49-b67e-c7ccb8ae44d3/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 20:42:12 +0000 + + +In-reply-to: ec133a4e-c9ff-4499-b469-cb0a2ca9a685 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/body new file mode 100644 index 0000000..4ac7a33 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/body @@ -0,0 +1,3 @@ +This could be implemented with an external frontend storing the +dependency data in arbitrary tags. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values new file mode 100644 index 0000000..fe86bd4 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 21:29:13 +0000 + + +In-reply-to: f87fd684-6af1-498d-98d5-f915bcee76a9 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/ec133a4e-c9ff-4499-b469-cb0a2ca9a685/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/ec133a4e-c9ff-4499-b469-cb0a2ca9a685/body new file mode 100644 index 0000000..a06e236 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/ec133a4e-c9ff-4499-b469-cb0a2ca9a685/body @@ -0,0 +1,55 @@ +In Aaron's Tue, 25 Nov 2008 09:32:29 -0500 email: + +>> 7ec:om: Arbitrary tags +>> Sensible +> +> Implemented as a free-form value field similar to target? A +> comma-seperated list of tags? + +I believe I planned to store it as an alpha-sorted, one-entry-per-line +list, so it would support merging easily. + +> Perhaps once we have per-bug/comment +> attribute searching it would be easier to have a 'create-attribute' +> becommand, e.g. +> be create-attribute [-valid=X,Y,Z] [bugdir|bug|comment] [NAME] [DEFAULT] + +Well, it really depends how much semantics you want to embed in the data +format. Some values are scalars, some may be sets (i.e. tags), some may +be ordered lists or even mappings. How much you want to reflect that in +the data format is up to you. + +> Extend and make more consitent the settings_property() attributes. +> Create becommand/(create/remove)-attribute for logic-less attributes. +> Create a few mix-ins for logic-ed attributes + +I don't find the term mix-in very intuitive here. + +> Usage example: +> Goal: +> set up for `be depends BUGA BUGB`, `be depends --tree BUGA`, etc +> Procedure: +> be set --apend mixins bug:dependency + +"append" usually has two "p"s. Is the omission deliberate? + +> Where we've defined +> becommands/depends.py, but it is hidden until the mixin is activated +> libbe/mixins/bug/dependency.Mixin (inheriting from BugMixin) +> to +> parse/generate comma seperated dependency uuids for saving/loading +> pretty-print the dependency list (e.g. uuid->shortname) +> walk the dependency tree and check target bug status. +> +> With more complicated mixins, there could be inter-mixin dependencies, +> e.g. a dependency tracker that searches depends based on bug.status +> might depend on the base dependency mixin. This way people who need +> it could make rich interfaces without confusing the people who don't. +> +> How does that sound? + +It sounds pretty complicated. I would probably use a type system rather +than "mixins", and define types as "scalar", "set" and maybe "list" and +"map". Dependencies would be a set, and their special behaviour would +be hardcoded according to their name, not a property of their type. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/ec133a4e-c9ff-4499-b469-cb0a2ca9a685/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/ec133a4e-c9ff-4499-b469-cb0a2ca9a685/values new file mode 100644 index 0000000..c85b16f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/ec133a4e-c9ff-4499-b469-cb0a2ca9a685/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 20:40:54 +0000 + + +In-reply-to: 401950a0-a5ff-46f3-afac-a9cfb300f94b + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/f87fd684-6af1-498d-98d5-f915bcee76a9/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/f87fd684-6af1-498d-98d5-f915bcee76a9/body new file mode 100644 index 0000000..a28cfe4 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/f87fd684-6af1-498d-98d5-f915bcee76a9/body @@ -0,0 +1 @@ +Merged from bug 17921fbc-e7f0-4f31-8cdd-598e5ba7237b
\ No newline at end of file diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/f87fd684-6af1-498d-98d5-f915bcee76a9/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/f87fd684-6af1-498d-98d5-f915bcee76a9/values new file mode 100644 index 0000000..2b6307e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/f87fd684-6af1-498d-98d5-f915bcee76a9/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 21:29:32 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/values new file mode 100644 index 0000000..1059e1b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: Arbitrary tags + + +time: Wed, 04 Jan 2006 21:06:38 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/814e39c0-68ee-4165-9166-19e2aee9c07d/comments/17d045d1-3b21-4d3d-8f81-29a5bbc5e6c1/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/814e39c0-68ee-4165-9166-19e2aee9c07d/comments/17d045d1-3b21-4d3d-8f81-29a5bbc5e6c1/body new file mode 100644 index 0000000..6e3f1e7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/814e39c0-68ee-4165-9166-19e2aee9c07d/comments/17d045d1-3b21-4d3d-8f81-29a5bbc5e6c1/body @@ -0,0 +1,14 @@ +> Roundup's great strength is the flexibility of its data model and +> range of generic support. It's very easy to extend... +> ... +> As far as postponed customization goes, it would be easy enough to +> duplicate Roundup's schema.py and provide a default schema.py for +> bugtracking. This would improve our current system by keeping all the +> configurable bits under version control from the start (equivalent to +> setting _versioned_property(require_save=True) for all properties). + +How will we handle diffs between with revisions with different +schema.py? This re-raises #bea86499-824e-4e77-b085-2d581fa9ccab/ed5eac05-80ed-411d-88a4-d2261b879713/c664b7be-ded5-42dd-a16a-82b2bdb52e36# (#bea86499-824e-4e77-b085-2d581fa9ccab/1100c966-9671-4bc6-8b68-6d408a910da1/bd1207ef-f97e-4078-8c5d-046072012082#), but we +_expect_ schema.py to evolve, while before we had expected on-disk +versions to stabilize. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/814e39c0-68ee-4165-9166-19e2aee9c07d/comments/17d045d1-3b21-4d3d-8f81-29a5bbc5e6c1/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/814e39c0-68ee-4165-9166-19e2aee9c07d/comments/17d045d1-3b21-4d3d-8f81-29a5bbc5e6c1/values new file mode 100644 index 0000000..5bc6769 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/814e39c0-68ee-4165-9166-19e2aee9c07d/comments/17d045d1-3b21-4d3d-8f81-29a5bbc5e6c1/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 03 Jan 2010 16:02:57 +0000 + + +In-reply-to: d463e2d9-6dcc-41a4-a6b2-647fb3bddf88 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/814e39c0-68ee-4165-9166-19e2aee9c07d/comments/d463e2d9-6dcc-41a4-a6b2-647fb3bddf88/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/814e39c0-68ee-4165-9166-19e2aee9c07d/comments/d463e2d9-6dcc-41a4-a6b2-647fb3bddf88/body new file mode 100644 index 0000000..b953536 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/814e39c0-68ee-4165-9166-19e2aee9c07d/comments/d463e2d9-6dcc-41a4-a6b2-647fb3bddf88/body @@ -0,0 +1,67 @@ +The Roundup issue tracker + http://roundup.sourceforge.net/ +has been around for a while, and provides a nice, flexible design + http://roundup.sourceforge.net/docs/design.html +What ideas from Roundup are worth incorperating in our setup? + +Roundup's great strength is the flexibility of its data model and +range of generic support. It's very easy to extend. However, there +is only so far you can go with generic support. Roundup lacks analogs +to the following Command subclasses (as far as I know): + Diff + Has per-issue logs, but no repository-wide summary + Merge + Commit + No VCS backends, see http://issues.roundup-tracker.org/issue2550547 + Import_xml + Serve + Has HTML server, but no remote command-line access +Of course, none of these would be particularly hard to add to Roundup, +with the possible exception of VCS backends, which appears to be +in-progress anyway. However, I really like the simplicity of + `be init` +and the ability to postpone repository customization until you need +it. So, can we trim down the BE internals to make BE more extensible +without sacrificing our nice default setup and its tools? The problem +is, how to the commands do their thing if they don't know what they're +working with? + +Say, for example, I want to run `be depend bugA bugB`, but my bugs +don't have blocks or blocked_by link properties. That could be easily +handled by having each command would have to keep track of which +properties it needed and raise appropriate exceptions. + +List, Show, Import_xml, etc. would presumably use templates to define +their output/input formats. + +As far as postponed customization goes, it would be easy enough to +duplicate Roundup's schema.py and provide a default schema.py for +bugtracking. This would improve our current system by keeping all the +configurable bits under version control from the start (equivalent to +setting _versioned_property(require_save=True) for all properties). + +Another part of the difference between BE and Roundup seems to be due +to the initial backend selection. Roundup is built on databases, +which encourages their keyed-Class approach with (property, value) +pairs of predefined types. They use Classes for everything, down to +status values, etc., while we've built those sorts of things into +_versioned_property()s. +Benefits of Roundup approach: + * easy to configure/alter/retrieve list of allowed values + * no need to hard-code properties or resort to extra_strings + * assigned values are actually links to centralized definitions + - easy updates +Benefits of BE approach: + * single file for all properties + - one read and you're done + - many file systems don't handle 'lots of tiny files' well + * assigned values are actual values, not links to centralized defs. + - easy to merge by hand, no need to look up references. +Since it would be fairly simple to add a merging tool that handled the +reference lookup transparently, we can move to a Roundup-like Class +structure by using our current mapfile implementation to store small +Classes. + +Finally, would it be easier to merge these Roundup features into BE, +or merge the BE features into Roundup... + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/814e39c0-68ee-4165-9166-19e2aee9c07d/comments/d463e2d9-6dcc-41a4-a6b2-647fb3bddf88/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/814e39c0-68ee-4165-9166-19e2aee9c07d/comments/d463e2d9-6dcc-41a4-a6b2-647fb3bddf88/values new file mode 100644 index 0000000..cb7278c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/814e39c0-68ee-4165-9166-19e2aee9c07d/comments/d463e2d9-6dcc-41a4-a6b2-647fb3bddf88/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 03 Jan 2010 14:16:55 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/814e39c0-68ee-4165-9166-19e2aee9c07d/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/814e39c0-68ee-4165-9166-19e2aee9c07d/values new file mode 100644 index 0000000..5feb832 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/814e39c0-68ee-4165-9166-19e2aee9c07d/values @@ -0,0 +1,14 @@ +creator: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: open + + +summary: Add Roundup-like flexibility + + +time: Sun, 03 Jan 2010 13:12:38 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8385a1fb-63df-4ca6-81cd-28ede83bb0c2/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8385a1fb-63df-4ca6-81cd-28ede83bb0c2/values new file mode 100644 index 0000000..5d80e70 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8385a1fb-63df-4ca6-81cd-28ede83bb0c2/values @@ -0,0 +1,17 @@ +creator: gianluca <gian@galactica> + + +reporter: gianluca <gian@galactica> + + +severity: minor + + +status: wontfix + + +summary: Add the html files for the status detail to "be html" output + + +time: Fri, 03 Jul 2009 22:56:09 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/4d642e39-a8f3-41d8-93da-bea7e05ef9a6/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/4d642e39-a8f3-41d8-93da-bea7e05ef9a6/body new file mode 100644 index 0000000..8d1ec26 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/4d642e39-a8f3-41d8-93da-bea7e05ef9a6/body @@ -0,0 +1 @@ +A rough implemention is now sketched out in becommands/list.py diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/4d642e39-a8f3-41d8-93da-bea7e05ef9a6/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/4d642e39-a8f3-41d8-93da-bea7e05ef9a6/values new file mode 100644 index 0000000..924ca86 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/4d642e39-a8f3-41d8-93da-bea7e05ef9a6/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 27 Nov 2008 14:26:18 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/bf0c3752-6338-4919-93ba-4c9252945fb1/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/bf0c3752-6338-4919-93ba-4c9252945fb1/body new file mode 100644 index 0000000..bb443b8 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/bf0c3752-6338-4919-93ba-4c9252945fb1/body @@ -0,0 +1,15 @@ +For example: + $ be list --status --options + File "/home/wking/bin/be", line 35, in <module> + sys.exit(cmdutil.execute(sys.argv[1], sys.argv[2:])) + File "/home/wking/lib/python2.5/site-packages/libbe/cmdutil.py", line 67, in execute + get_command(cmd).execute([a.decode(enc) for a in args]) + File "/home/wking/lib/python2.5/site-packages/becommands/list.py", line 36, in execute + raise Exception, "parsed options" + Exception: parsed options + +The reason for this is that --status takes an argument, so 'be list' +thinks it should list all the bugs with status == "--options". +Ideally what should happen is that an argument-taking option would +check for argument --options, and if so, would raise an exception +returning a list of appropriate completions *for that argument*. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/bf0c3752-6338-4919-93ba-4c9252945fb1/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/bf0c3752-6338-4919-93ba-4c9252945fb1/values new file mode 100644 index 0000000..fb952c2 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/bf0c3752-6338-4919-93ba-4c9252945fb1/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 27 Nov 2008 13:43:47 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/values new file mode 100644 index 0000000..73915d5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/values @@ -0,0 +1,14 @@ +creator: W. Trevor King <wking@drexel.edu> + + +severity: serious + + +status: fixed + + +summary: be <cmmd> <argopt> --options doesn't raise GetOptions + + +time: Thu, 27 Nov 2008 13:39:25 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/body new file mode 100644 index 0000000..d10b444 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/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 "<doctest becommands.status.execute[1]>", line 1, in <module> + 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 <lambda> + 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 <module> + import bzrlib + ImportError: No module named bzrlib + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/values new file mode 100644 index 0000000..3453fd9 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Fri, 21 Nov 2008 18:41:47 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/body new file mode 100644 index 0000000..3d7d3aa --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/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/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/values new file mode 100644 index 0000000..2a76a4e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Fri, 21 Nov 2008 19:12:42 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/body new file mode 100644 index 0000000..1fe5ce3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/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 "<doctest becommands.severity.execute[1]>", line 1, in <module> + 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 <lambda> + 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 <module> + 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 "<doctest becommands.severity.execute[2]>", line 1, in <module> + 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 "<doctest becommands.severity.execute[3]>", line 1, in <module> + 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 "<doctest becommands.severity.execute[4]>", line 1, in <module> + 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 "<doctest becommands.severity.execute[5]>", line 1, in <module> + 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 "<doctest becommands.severity.execute[6]>", line 1, in <module> + 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/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/values new file mode 100644 index 0000000..d63f4e1 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Fri, 21 Nov 2008 19:01:19 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/values new file mode 100644 index 0000000..93c746c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e83da06-26f1-4763-a972-dae7e7062233/values @@ -0,0 +1,14 @@ +creator: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: fixed + + +summary: test.py removes path to bzrlib + + +time: Fri, 21 Nov 2008 18:41:03 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/3e7144eb-c934-4b62-94b7-7dbfa90ed6ee/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/3e7144eb-c934-4b62-94b7-7dbfa90ed6ee/body new file mode 100644 index 0000000..635893b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/3e7144eb-c934-4b62-94b7-7dbfa90ed6ee/body @@ -0,0 +1,4 @@ +From Aaron's Mon, 24 Nov 2008 19:15:09 -0500 email: + +8e9:om: list X most recent entries +Closeable. (And yes, I would do it instead of 'be diff') diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/3e7144eb-c934-4b62-94b7-7dbfa90ed6ee/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/3e7144eb-c934-4b62-94b7-7dbfa90ed6ee/values new file mode 100644 index 0000000..dea0808 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/3e7144eb-c934-4b62-94b7-7dbfa90ed6ee/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 19:46:45 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/7d7e703f-22f2-4c47-86a3-fcc3c8ead576/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/7d7e703f-22f2-4c47-86a3-fcc3c8ead576/body new file mode 100644 index 0000000..6d75610 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/7d7e703f-22f2-4c47-86a3-fcc3c8ead576/body @@ -0,0 +1 @@ +Would you do this instead of `be diff`? diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/7d7e703f-22f2-4c47-86a3-fcc3c8ead576/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/7d7e703f-22f2-4c47-86a3-fcc3c8ead576/values new file mode 100644 index 0000000..ca6c353 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/7d7e703f-22f2-4c47-86a3-fcc3c8ead576/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 24 Nov 2008 13:10:38 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/values new file mode 100644 index 0000000..391d092 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: list X most recent entries + + +time: Wed, 25 Jan 2006 15:44:18 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8fc5d6fa-cae1-451f-9817-3e4da6d0aac1/comments/432e994f-3759-42bf-a80d-7cd626c7ce7c/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8fc5d6fa-cae1-451f-9817-3e4da6d0aac1/comments/432e994f-3759-42bf-a80d-7cd626c7ce7c/body new file mode 100644 index 0000000..359c90f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8fc5d6fa-cae1-451f-9817-3e4da6d0aac1/comments/432e994f-3759-42bf-a80d-7cd626c7ce7c/body @@ -0,0 +1,40 @@ +For example, after merging in a branch with new bugs, the id-cache is +incomplete. An example traceback (from `be list`) is + +Traceback (most recent call last): + File "./be", line 21, in <module> + sys.exit(libbe.ui.command_line.main()) + File ".../be.wtk/libbe/ui/command_line.py", line 327, in main + ret = dispatch(ui, command, args) + File ".../be.wtk/libbe/ui/command_line.py", line 267, in dispatch + ret = ui.run(command, options, args) + File ".../be.wtk/libbe/command/base.py", line 504, in run + return command.run(options, args) + File ".../be.wtk/libbe/command/base.py", line 233, in run + self.status = self._run(**params) + File ".../be.wtk/libbe/command/list.py", line 168, in _run + bugs = self._sort_bugs(bugs, cmp_list) + File ".../be.wtk/libbe/command/list.py", line 229, in _sort_bugs + bugs.sort(cmp_fn) + File ".../be.wtk/libbe/bug.py", line 818, in __call__ + val = comparison(bug_1, bug_2) + File ".../be.wtk/libbe/bug.py", line 798, in cmp_comments + comms_1 = sorted(bug_1.comments(), key = lambda comm : comm.uuid) + File ".../be.wtk/libbe/bug.py", line 687, in comments + for comment in self.comment_root.traverse(): + File ".../be.wtk/libbe/storage/util/properties.py", line 297, in _fget + value = generator(self) + File ".../be.wtk/libbe/bug.py", line 225, in _get_comment_root + return comment.load_comments(self, load_full=load_full) + File ".../be.wtk/libbe/comment.py", line 85, in load_comments + bug.id.storage())): + File ".../be.wtk/libbe/storage/base.py", line 314, in children + return self._children(*args, **kwargs) + File ".../be.wtk/libbe/storage/vcs/base.py", line 804, in _children + path = self.path(id, revision, relpath=False) + File ".../be.wtk/libbe/storage/vcs/base.py", line 705, in path + path = self._cached_path_id.path(id) + File ".../be.wtk/libbe/storage/vcs/base.py", line 242, in path + raise InvalidID(uuid) +libbe.storage.base.InvalidID: cf56e648-3b09-4131-8847-02dff12b4db2 in revision None + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8fc5d6fa-cae1-451f-9817-3e4da6d0aac1/comments/432e994f-3759-42bf-a80d-7cd626c7ce7c/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8fc5d6fa-cae1-451f-9817-3e4da6d0aac1/comments/432e994f-3759-42bf-a80d-7cd626c7ce7c/values new file mode 100644 index 0000000..993dce1 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8fc5d6fa-cae1-451f-9817-3e4da6d0aac1/comments/432e994f-3759-42bf-a80d-7cd626c7ce7c/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 24 Jan 2010 16:29:46 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8fc5d6fa-cae1-451f-9817-3e4da6d0aac1/comments/e3d802cf-1fff-4a48-a61c-a07578969333/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8fc5d6fa-cae1-451f-9817-3e4da6d0aac1/comments/e3d802cf-1fff-4a48-a61c-a07578969333/body new file mode 100644 index 0000000..450a208 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8fc5d6fa-cae1-451f-9817-3e4da6d0aac1/comments/e3d802cf-1fff-4a48-a61c-a07578969333/body @@ -0,0 +1,5 @@ +Work around by removing id-cache (forcing recreation). + +A better solution would be detecting the problem and recreating the +cache automatically. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8fc5d6fa-cae1-451f-9817-3e4da6d0aac1/comments/e3d802cf-1fff-4a48-a61c-a07578969333/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8fc5d6fa-cae1-451f-9817-3e4da6d0aac1/comments/e3d802cf-1fff-4a48-a61c-a07578969333/values new file mode 100644 index 0000000..1c44d10 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8fc5d6fa-cae1-451f-9817-3e4da6d0aac1/comments/e3d802cf-1fff-4a48-a61c-a07578969333/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 25 Jan 2010 00:50:17 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8fc5d6fa-cae1-451f-9817-3e4da6d0aac1/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8fc5d6fa-cae1-451f-9817-3e4da6d0aac1/values new file mode 100644 index 0000000..28975af --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/8fc5d6fa-cae1-451f-9817-3e4da6d0aac1/values @@ -0,0 +1,17 @@ +creator: W. Trevor King <wking@drexel.edu> + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: fixed + + +summary: be crashes on outdated id-cache + + +time: Sun, 24 Jan 2010 16:28:06 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/209e2a60-ddd0-4a71-90ef-e57547ed6d76/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/209e2a60-ddd0-4a71-90ef-e57547ed6d76/body new file mode 100644 index 0000000..dd9b459 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/209e2a60-ddd0-4a71-90ef-e57547ed6d76/body @@ -0,0 +1,3 @@ +From the command line, + $ be show `be list --status all --uuids` | grep -A5 -B5 XYZ +works pretty well... diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/209e2a60-ddd0-4a71-90ef-e57547ed6d76/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/209e2a60-ddd0-4a71-90ef-e57547ed6d76/values new file mode 100644 index 0000000..901c32f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/209e2a60-ddd0-4a71-90ef-e57547ed6d76/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 18:05:38 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/body new file mode 100644 index 0000000..a27ff59 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/body @@ -0,0 +1 @@ +Hmm. This is already done... diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values new file mode 100644 index 0000000..4031ab2 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values @@ -0,0 +1,8 @@ +Author: abentley + + +Content-type: text/plain + + +Date: Fri, 31 Mar 2006 22:15:09 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/values new file mode 100644 index 0000000..5d35985 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: Provide search + + +time: Wed, 25 Jan 2006 15:43:59 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9b1a0e71-4f7d-40b1-ab32-18496bf19a3f/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9b1a0e71-4f7d-40b1-ab32-18496bf19a3f/values new file mode 100644 index 0000000..4b6bb4f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9b1a0e71-4f7d-40b1-ab32-18496bf19a3f/values @@ -0,0 +1,17 @@ +creator: gianluca <gian@galactica> + + +reporter: gianluca <gian@galactica> + + +severity: minor + + +status: wontfix + + +summary: Add the html files for the severity detail to "be html" output + + +time: Fri, 03 Jul 2009 22:56:19 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9c25fd46-5e2b-478f-8beb-01b89e27c1f2/comments/7cd2d475-676f-4d60-b431-c7635468e9bd/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9c25fd46-5e2b-478f-8beb-01b89e27c1f2/comments/7cd2d475-676f-4d60-b431-c7635468e9bd/body new file mode 100644 index 0000000..73ce487 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9c25fd46-5e2b-478f-8beb-01b89e27c1f2/comments/7cd2d475-676f-4d60-b431-c7635468e9bd/body @@ -0,0 +1,9 @@ +The comment class could be streamlined and standardized by making it +subclass (Tree, email.Message). This should make the per-bug, mini +mailing list more expressive, and add support for fancy email +features. On the other hand, it could make the Comment/xml interface, +HTML production, etc. more awkward. + +Time for another look at Debian's tracker, or do they only allow +text/plain, single-part messages? + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9c25fd46-5e2b-478f-8beb-01b89e27c1f2/comments/7cd2d475-676f-4d60-b431-c7635468e9bd/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9c25fd46-5e2b-478f-8beb-01b89e27c1f2/comments/7cd2d475-676f-4d60-b431-c7635468e9bd/values new file mode 100644 index 0000000..a774eb2 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9c25fd46-5e2b-478f-8beb-01b89e27c1f2/comments/7cd2d475-676f-4d60-b431-c7635468e9bd/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 28 Jan 2010 15:41:07 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9c25fd46-5e2b-478f-8beb-01b89e27c1f2/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9c25fd46-5e2b-478f-8beb-01b89e27c1f2/values new file mode 100644 index 0000000..e6a7689 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9c25fd46-5e2b-478f-8beb-01b89e27c1f2/values @@ -0,0 +1,17 @@ +creator: W. Trevor King <wking@drexel.edu> + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: wishlist + + +status: open + + +summary: Can comment punt functionality to email.Message? + + +time: Thu, 28 Jan 2010 15:36:16 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/095ade7c-9378-41bd-8137-f2731c6afcac/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/095ade7c-9378-41bd-8137-f2731c6afcac/body new file mode 100644 index 0000000..bfb1037 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/095ade7c-9378-41bd-8137-f2731c6afcac/body @@ -0,0 +1,19 @@ +Presumably this would be to allow sorting of bugs by last-modified +date instead of by creation date. With the xml output, this is no +longer needed. For example, I view bugs in mutt with + $ be list | xml/be-xml-to-mbox | xml/catmutt +and use mutt to sort the threads by last-modified, e.g. by adding + set sort=threads + set sort_aux=last-date +to my ~/.muttrc. + +That being said, I could go for a user-specified sort command in +becommands/list.py, rather than the current bug.cmp_full, since other +mail readers may suck more than mutt ;), and even mutt might not have +that perfect sort you desire coded into it :p. The problem is that +while the cmp_* functions in bug are short, they are not really the +sort of thing you'd want to type in on the command line. Perhaps we +can just slowly accumulate a rich array of bug.cmp_* functions as +they are requested, and allow the user to prepend their favorites to +the default cmp_full list... + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/095ade7c-9378-41bd-8137-f2731c6afcac/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/095ade7c-9378-41bd-8137-f2731c6afcac/values new file mode 100644 index 0000000..98f869b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/095ade7c-9378-41bd-8137-f2731c6afcac/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 18:40:43 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/4be35966-373b-438c-a35a-824f5c7a940a/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/4be35966-373b-438c-a35a-824f5c7a940a/body new file mode 100644 index 0000000..777975d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/4be35966-373b-438c-a35a-824f5c7a940a/body @@ -0,0 +1,17 @@ +No need for RCS-expansion for the history. If the user is versioning +their code with some RCS, they presumably know how to use the RCS to +investigate the history already. The .be/ directory structure is not +so complicated that it's worth much work to avoid their having to peer +inside it by hand. + +In rare cases where people really do want to peer into history using +only BE or sort by e.g. bug closing time, they could add those +comments by hand, e.g. + $ echo 'bug closed' | be comment <bug> - + $ be close <bug> +So the already-implemented cmp_last_modified would handle it. + +If you want, you could add (optional) comment-generation to the +becommands themselves. For example becommand/merge.py already does +this. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/4be35966-373b-438c-a35a-824f5c7a940a/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/4be35966-373b-438c-a35a-824f5c7a940a/values new file mode 100644 index 0000000..cebbded --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/4be35966-373b-438c-a35a-824f5c7a940a/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 21:12:00 +0000 + + +In-reply-to: d81d0df9-e6d9-4fe8-8dbe-989ef2c81f00 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/d81d0df9-e6d9-4fe8-8dbe-989ef2c81f00/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/d81d0df9-e6d9-4fe8-8dbe-989ef2c81f00/body new file mode 100644 index 0000000..5e3ef6b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/d81d0df9-e6d9-4fe8-8dbe-989ef2c81f00/body @@ -0,0 +1,9 @@ +User specfied sort added, along with bug.cmp_last_modified. + +Hmm, perhaps you don't want the last comment date, but e.g. the last +time one of the bug attributes are changed. In that case, I suggest + bzr log .be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/ + +Maybe log(file) functionality should be incorperated into libbe/rcs... +Perhaps accessed through a --history. I'm not sure I remember enough +Arch to do that ;). diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/d81d0df9-e6d9-4fe8-8dbe-989ef2c81f00/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/d81d0df9-e6d9-4fe8-8dbe-989ef2c81f00/values new file mode 100644 index 0000000..c2f0da8 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/d81d0df9-e6d9-4fe8-8dbe-989ef2c81f00/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 19:43:21 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/values new file mode 100644 index 0000000..f124ccb --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: Add last-modified field to bugs + + +time: Thu, 14 Sep 2006 18:08:53 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9daa72ee-0721-4f68-99ee-f06fec0b340e/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9daa72ee-0721-4f68-99ee-f06fec0b340e/values new file mode 100644 index 0000000..f110a26 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9daa72ee-0721-4f68-99ee-f06fec0b340e/values @@ -0,0 +1,14 @@ +assigned: abentley + + +creator: abentley + + +severity: minor + + +status: closed + + +summary: Organize list by whether it's assigned to the current target + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9f910ee0-ff0f-4fa3-b1e3-79a4118e48e9/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9f910ee0-ff0f-4fa3-b1e3-79a4118e48e9/values new file mode 100644 index 0000000..d305d6c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/9f910ee0-ff0f-4fa3-b1e3-79a4118e48e9/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: prevent collisions in different branches + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1/body new file mode 100644 index 0000000..073f0b8 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1/body @@ -0,0 +1 @@ +Merged from bug c894f10f-197d-4b22-9c5b-19f394df40d4
\ No newline at end of file diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1/values new file mode 100644 index 0000000..1bd47bf --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 25 Nov 2008 02:24:04 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/body new file mode 100644 index 0000000..7f46872 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/body @@ -0,0 +1,17 @@ +Example: + +We're working happily in a versioned bugdir, and our RCS knows who we +are. We create a temporary repository copy from a previous revision +for diff generation. We set the RCS for the copy to "None", since we +didn't bother initializing our normal RCS in the snapshot copy. But +now the BugDir instantized on the copy doesn't know who we are! + +Solution: + +Track user id in the bugdir settings file. If you +bugdir.settings["user_id"], it will be saved and loaded. When loaded, +it will also set bugdir.user_id. If you set rcs.user_id, it will be +returned by rcs.get_user_id(), instead of returing the output of +rcs._rcs_get_user_id(). We should be caching the output of +_rcs_get_user_id() anyway. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values new file mode 100644 index 0000000..7a04fc3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 22 Nov 2008 21:43:29 +0000 + + +In-reply-to: 0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/25c67b0b-1afd-4613-a787-e0f018614966/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/25c67b0b-1afd-4613-a787-e0f018614966/body new file mode 100644 index 0000000..62c14e6 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/25c67b0b-1afd-4613-a787-e0f018614966/body @@ -0,0 +1 @@ +This bug duplicates a403de79-8f39-41f2-b9ec-15053b175ee2 diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/25c67b0b-1afd-4613-a787-e0f018614966/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/25c67b0b-1afd-4613-a787-e0f018614966/values new file mode 100644 index 0000000..0c87273 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/25c67b0b-1afd-4613-a787-e0f018614966/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 23 Nov 2008 12:37:57 +0000 + + +In-reply-to: 0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/values new file mode 100644 index 0000000..db04837 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: Beweb should support declaring username + + +time: Wed, 04 Jan 2006 21:07:25 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/3415fbd7-5a7e-4a7f-af30-82f8ce6ca85b/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/3415fbd7-5a7e-4a7f-af30-82f8ce6ca85b/body new file mode 100644 index 0000000..05022e8 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/3415fbd7-5a7e-4a7f-af30-82f8ce6ca85b/body @@ -0,0 +1 @@ +Fixed by 273. Probably around 253. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/3415fbd7-5a7e-4a7f-af30-82f8ce6ca85b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/3415fbd7-5a7e-4a7f-af30-82f8ce6ca85b/values new file mode 100644 index 0000000..8086b48 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/3415fbd7-5a7e-4a7f-af30-82f8ce6ca85b/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 24 Nov 2008 13:05:07 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/b0e7165b-7099-45ca-9513-412225f7bd52/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/b0e7165b-7099-45ca-9513-412225f7bd52/body new file mode 100644 index 0000000..0a004ac --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/b0e7165b-7099-45ca-9513-412225f7bd52/body @@ -0,0 +1,21 @@ +Err, perhaps we should use revision ids. Or status. Or something... + + +$ be diff +Traceback (most recent call last): + File "/usr/bin/be", line 55, in ? + sys.exit(execute(sys.argv[1], sys.argv[2:])) + File "/usr/lib/python2.4/site-packages/libbe/cmdutil.py", line 105, in execute return get_command(cmd).execute([a.decode(encoding) for a in args]) + File "/usr/lib/python2.4/site-packages/becommands/diff.py", line 33, in execute + diff.diff_report(diff.reference_diff(tree, spec), tree) + File "/usr/lib/python2.4/site-packages/libbe/diff.py", line 41, in reference_diff + return diff(bugdir.get_reference_bugdir(spec), bugdir) + File "/usr/lib/python2.4/site-packages/libbe/diff.py", line 22, in diff + old_bug_map = old_tree.bug_map() + File "/usr/lib/python2.4/site-packages/libbe/bugdir.py", line 169, in bug_map + for bug in self.list(): + File "/usr/lib/python2.4/site-packages/libbe/bugdir.py", line 164, in list + for uuid in self.list_uuids(): + File "/usr/lib/python2.4/site-packages/libbe/bugdir.py", line 177, in list_uuids + for uuid in os.listdir(self.bugs_path): +OSError: [Errno 2] No such file or directory: '/home/abentley/.bzrrevs/None/.be/bugs' diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/b0e7165b-7099-45ca-9513-412225f7bd52/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/b0e7165b-7099-45ca-9513-412225f7bd52/values new file mode 100644 index 0000000..e94fece --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/b0e7165b-7099-45ca-9513-412225f7bd52/values @@ -0,0 +1,8 @@ +Author: abentley + + +Content-type: text/plain + + +Date: Mon, 10 Apr 2006 23:23:25 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/values new file mode 100644 index 0000000..5de8e08 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: be diff doesn't work with bzr in directories that have no commits + + +time: Mon, 10 Apr 2006 23:22:17 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a63bd76a-cd43-4f97-88ba-2323546d4572/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a63bd76a-cd43-4f97-88ba-2323546d4572/values new file mode 100644 index 0000000..e5e6b0b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/a63bd76a-cd43-4f97-88ba-2323546d4572/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: 'Beweb: New comment button should save any changes' + + +time: Wed, 04 Jan 2006 21:05:20 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/2628eeca-96c6-4933-8484-d55bb1dbf985/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/2628eeca-96c6-4933-8484-d55bb1dbf985/body new file mode 100644 index 0000000..6c46db0 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/2628eeca-96c6-4933-8484-d55bb1dbf985/body @@ -0,0 +1 @@ +Merged from bug 4a4609c8-1882-47de-9d30-fee410b8a802
\ No newline at end of file diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/2628eeca-96c6-4933-8484-d55bb1dbf985/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/2628eeca-96c6-4933-8484-d55bb1dbf985/values new file mode 100644 index 0000000..8fcd9d4 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/2628eeca-96c6-4933-8484-d55bb1dbf985/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 17:05:49 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/942cd941-583d-4020-99e4-80de7e836129/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/942cd941-583d-4020-99e4-80de7e836129/body new file mode 100644 index 0000000..d0b8404 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/942cd941-583d-4020-99e4-80de7e836129/body @@ -0,0 +1 @@ +Implemented. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/942cd941-583d-4020-99e4-80de7e836129/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/942cd941-583d-4020-99e4-80de7e836129/values new file mode 100644 index 0000000..3033bc1 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/942cd941-583d-4020-99e4-80de7e836129/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 15:42:07 +0000 + + +In-reply-to: 2628eeca-96c6-4933-8484-d55bb1dbf985 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/ae0f9aea-960c-42b4-82df-943bbbe17d58/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/ae0f9aea-960c-42b4-82df-943bbbe17d58/body new file mode 100644 index 0000000..f7659c3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/ae0f9aea-960c-42b4-82df-943bbbe17d58/body @@ -0,0 +1 @@ +Per-tree severity and status levels are now supported. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/ae0f9aea-960c-42b4-82df-943bbbe17d58/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/ae0f9aea-960c-42b4-82df-943bbbe17d58/values new file mode 100644 index 0000000..4a24d7e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/ae0f9aea-960c-42b4-82df-943bbbe17d58/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 17:07:25 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/values new file mode 100644 index 0000000..2f65fbc --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: Customizable severity levels? + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b187fbce-fb10-4819-ace2-c8b0b4a45c57/comments/e757d2ae-085a-4539-99be-096386de5352/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b187fbce-fb10-4819-ace2-c8b0b4a45c57/comments/e757d2ae-085a-4539-99be-096386de5352/body new file mode 100644 index 0000000..0dedcdd --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b187fbce-fb10-4819-ace2-c8b0b4a45c57/comments/e757d2ae-085a-4539-99be-096386de5352/body @@ -0,0 +1,3 @@ +The 'be' command should have a Unix manpage, describing it like any +other command on the system. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b187fbce-fb10-4819-ace2-c8b0b4a45c57/comments/e757d2ae-085a-4539-99be-096386de5352/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b187fbce-fb10-4819-ace2-c8b0b4a45c57/comments/e757d2ae-085a-4539-99be-096386de5352/values new file mode 100644 index 0000000..251453e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b187fbce-fb10-4819-ace2-c8b0b4a45c57/comments/e757d2ae-085a-4539-99be-096386de5352/values @@ -0,0 +1,8 @@ +Author: benf + + +Content-type: text/plain + + +Date: Mon, 21 Apr 2008 03:24:11 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b187fbce-fb10-4819-ace2-c8b0b4a45c57/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b187fbce-fb10-4819-ace2-c8b0b4a45c57/values new file mode 100644 index 0000000..4dc6a1e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b187fbce-fb10-4819-ace2-c8b0b4a45c57/values @@ -0,0 +1,17 @@ +assigned: benf + + +creator: benf + + +severity: minor + + +status: fixed + + +summary: Manual page for 'be' command + + +time: Mon, 21 Apr 2008 03:21:35 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b1bc6f39-8166-46c5-a724-4c4a3e1e7d74/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b1bc6f39-8166-46c5-a724-4c4a3e1e7d74/values new file mode 100644 index 0000000..eda617b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b1bc6f39-8166-46c5-a724-4c4a3e1e7d74/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: Comments do not appear in web UI + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3562f08-ad27-4b9f-8d21-8b58ba6d9eac/comments/2a51d90a-d47e-4a67-abe7-cce19c1eafad/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3562f08-ad27-4b9f-8d21-8b58ba6d9eac/comments/2a51d90a-d47e-4a67-abe7-cce19c1eafad/body new file mode 100644 index 0000000..3195bea --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3562f08-ad27-4b9f-8d21-8b58ba6d9eac/comments/2a51d90a-d47e-4a67-abe7-cce19c1eafad/body @@ -0,0 +1,21 @@ +> $ be new 'utf8 string' +> Traceback (most recent call last): +> ... +> UnicodeDecodeError: 'ascii' codec can't decode byte 0xd0 in position 95: ordinal not in range(128) + +(bug reported against cjb@laptop.org-20091006145647-kqkmoh481tl5hvt4) + +This was fixed with revision + wking@drexel.edu-20091117145118-jltbju9thsn5xvkv +in my branch on Nov. 17, 2009. + +> I think it is more correct to use UTF-8 everywhere or use +> locale.getdefaultlocale() instead sys.getdefaultencoding(). + +We try to use unicode strings internally, it's input/output that's +difficult. This particular bug turned out to be related to our +mapfile storage handling. Take a look at the be.unicode-hg branch +leading up to revision + wking@drexel.edu-20091117145118-jltbju9thsn5xvkv +for details. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3562f08-ad27-4b9f-8d21-8b58ba6d9eac/comments/2a51d90a-d47e-4a67-abe7-cce19c1eafad/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3562f08-ad27-4b9f-8d21-8b58ba6d9eac/comments/2a51d90a-d47e-4a67-abe7-cce19c1eafad/values new file mode 100644 index 0000000..c79c578 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3562f08-ad27-4b9f-8d21-8b58ba6d9eac/comments/2a51d90a-d47e-4a67-abe7-cce19c1eafad/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Fri, 19 Mar 2010 11:16:16 +0000 + + +In-reply-to: 854eec21-2eeb-4ed4-af35-7a4a2e1f2e98 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3562f08-ad27-4b9f-8d21-8b58ba6d9eac/comments/854eec21-2eeb-4ed4-af35-7a4a2e1f2e98/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3562f08-ad27-4b9f-8d21-8b58ba6d9eac/comments/854eec21-2eeb-4ed4-af35-7a4a2e1f2e98/body new file mode 100644 index 0000000..2a05cbd --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3562f08-ad27-4b9f-8d21-8b58ba6d9eac/comments/854eec21-2eeb-4ed4-af35-7a4a2e1f2e98/body @@ -0,0 +1,40 @@ +When I try to create bug with utf8 string I get error: + +$ be new 'utf8 string' +Traceback (most recent call last): + File "/usr/bin/be", line 64, in <module> + sys.exit(cmdutil.execute(args[0], args[1:])) + File "/usr/lib/python2.6/site-packages/libbe/cmdutil.py", line 82, in execute + manipulate_encodings=manipulate_encodings) + File "/usr/lib/python2.6/site-packages/becommands/new.py", line 54, in execute + bug = bd.new_bug(summary=summary.strip()) + File "/usr/lib/python2.6/site-packages/libbe/bugdir.py", line 584, in new_bug + bg.save() + File "/usr/lib/python2.6/site-packages/libbe/bug.py", line 388, in save + self.save_settings() + File "/usr/lib/python2.6/site-packages/libbe/bug.py", line 373, in save_settings + mapfile.map_save(self.vcs, path, self._get_saved_settings()) + File "/usr/lib/python2.6/site-packages/libbe/mapfile.py", line 110, in map_save + vcs.set_file_contents(path, contents, allow_no_vcs) + File "/usr/lib/python2.6/site-packages/libbe/vcs.py", line 354, in set_file_contents + f.write(contents) + File "/usr/lib/python2.6/codecs.py", line 686, in write + return self.writer.write(data) + File "/usr/lib/python2.6/codecs.py", line 351, in write + data, consumed = self.encode(object, self.errors) +UnicodeDecodeError: 'ascii' codec can't decode byte 0xd0 in position 95: ordinal not in range(128) +--- + +$ python +Python 2.6.1 (r261:67515, Jan 8 2010, 16:07:38) +[GCC 4.3.2] on linux2 +Type "help", "copyright", "credits" or "license" for more information. +>>> import sys +>>> import locale +>>> sys.getdefaultencoding() +'ascii' +>>> locale.getdefaultlocale() +('ru_RU', 'UTF-8') + +I think it is more correct to use UTF-8 everywhere or use locale.getdefaultlocale() instead sys.getdefaultencoding(). + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3562f08-ad27-4b9f-8d21-8b58ba6d9eac/comments/854eec21-2eeb-4ed4-af35-7a4a2e1f2e98/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3562f08-ad27-4b9f-8d21-8b58ba6d9eac/comments/854eec21-2eeb-4ed4-af35-7a4a2e1f2e98/values new file mode 100644 index 0000000..68e1d55 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3562f08-ad27-4b9f-8d21-8b58ba6d9eac/comments/854eec21-2eeb-4ed4-af35-7a4a2e1f2e98/values @@ -0,0 +1,8 @@ +Author: Anton Batenev <abbat@abbat> + + +Content-type: text/plain + + +Date: Tue, 16 Mar 2010 12:53:45 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3562f08-ad27-4b9f-8d21-8b58ba6d9eac/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3562f08-ad27-4b9f-8d21-8b58ba6d9eac/values new file mode 100644 index 0000000..5dda50d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3562f08-ad27-4b9f-8d21-8b58ba6d9eac/values @@ -0,0 +1,17 @@ +creator: Anton Batenev <abbat@abbat> + + +reporter: Anton Batenev <abbat@abbat> + + +severity: minor + + +status: fixed + + +summary: UTF-8 problems + + +time: Tue, 16 Mar 2010 12:40:01 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3c6da51-3a30-42c9-8c75-587c7a1705c5/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3c6da51-3a30-42c9-8c75-587c7a1705c5/values new file mode 100644 index 0000000..ab12d24 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b3c6da51-3a30-42c9-8c75-587c7a1705c5/values @@ -0,0 +1,14 @@ +creator: W. Trevor King <wking@drexel.edu> + + +severity: critical + + +status: fixed + + +summary: Slow be commands due to bugdir loading, go back to lazy bug loading. + + +time: Sun, 23 Nov 2008 13:48:01 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b8d95763-1825-4e09-bf52-cbd884b916af/comments/ae56365e-7a9c-4cc3-ba67-7addbeeeff49/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b8d95763-1825-4e09-bf52-cbd884b916af/comments/ae56365e-7a9c-4cc3-ba67-7addbeeeff49/body new file mode 100644 index 0000000..dd40bfa --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b8d95763-1825-4e09-bf52-cbd884b916af/comments/ae56365e-7a9c-4cc3-ba67-7addbeeeff49/body @@ -0,0 +1 @@ +Aaron said this was closeable in Nov. 24th email to the BE list. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b8d95763-1825-4e09-bf52-cbd884b916af/comments/ae56365e-7a9c-4cc3-ba67-7addbeeeff49/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b8d95763-1825-4e09-bf52-cbd884b916af/comments/ae56365e-7a9c-4cc3-ba67-7addbeeeff49/values new file mode 100644 index 0000000..549a346 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b8d95763-1825-4e09-bf52-cbd884b916af/comments/ae56365e-7a9c-4cc3-ba67-7addbeeeff49/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 13:48:47 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b8d95763-1825-4e09-bf52-cbd884b916af/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b8d95763-1825-4e09-bf52-cbd884b916af/values new file mode 100644 index 0000000..594433d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/b8d95763-1825-4e09-bf52-cbd884b916af/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: support multi-rcs configurations + + +time: Thu, 07 Apr 2005 16:09:10 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/bd0ebb56-fb46-45bc-af08-1e4a94e8ef3c/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/bd0ebb56-fb46-45bc-af08-1e4a94e8ef3c/values new file mode 100644 index 0000000..e42beab --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/bd0ebb56-fb46-45bc-af08-1e4a94e8ef3c/values @@ -0,0 +1,20 @@ +creator: W. Trevor King <wking@drexel.edu> + + +extra_strings: +- BLOCKED-BY:47c8fd5f-1f5a-4048-bef7-bb4c9a37c411 +- BLOCKED-BY:4fc71206-4285-417f-8a3c-ed6fb31bbbda +- BLOCKED-BY:f5c06914-dc64-4658-8ec7-32a026a53f55 + + +severity: target + + +status: fixed + + +summary: '0.2' + + +time: Sun, 06 Dec 2009 00:37:15 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c1b76442-eab6-4796-9517-8454425d7757/comments/27a5a4cc-1782-4509-a3d2-db00c190f97d/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c1b76442-eab6-4796-9517-8454425d7757/comments/27a5a4cc-1782-4509-a3d2-db00c190f97d/body new file mode 100644 index 0000000..f245ea4 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c1b76442-eab6-4796-9517-8454425d7757/comments/27a5a4cc-1782-4509-a3d2-db00c190f97d/body @@ -0,0 +1,12 @@ +Added rudimentary authorization with `be serve --auth FILE`. + +Special username 'guest' is not allowed to change name,password or +write to the repository. All other users in the auth file are allowed +to do all of that. A more robust solution would be to have POSIX +permissions on each storage item, or something. + +Note that while the server supports name/password changes for +non-guest users, there is no command-line interface to this +functionality. There is also no automatic way to register +(i.e. create entries). + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c1b76442-eab6-4796-9517-8454425d7757/comments/27a5a4cc-1782-4509-a3d2-db00c190f97d/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c1b76442-eab6-4796-9517-8454425d7757/comments/27a5a4cc-1782-4509-a3d2-db00c190f97d/values new file mode 100644 index 0000000..2169b75 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c1b76442-eab6-4796-9517-8454425d7757/comments/27a5a4cc-1782-4509-a3d2-db00c190f97d/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Wed, 27 Jan 2010 13:05:47 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c1b76442-eab6-4796-9517-8454425d7757/comments/76d54016-755b-42ca-ad07-eb9a1c77c33d/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c1b76442-eab6-4796-9517-8454425d7757/comments/76d54016-755b-42ca-ad07-eb9a1c77c33d/body new file mode 100644 index 0000000..f890566 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c1b76442-eab6-4796-9517-8454425d7757/comments/76d54016-755b-42ca-ad07-eb9a1c77c33d/body @@ -0,0 +1,2 @@ +Steve's had some related thoughts on authentication for CFBE: +#bea86499-824e-4e77-b085-2d581fa9ccab/d9959864-ea91-475a-a075-f39aa6760f98/21c90231-d7f2-49bb-97d9-99e16459d799#. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c1b76442-eab6-4796-9517-8454425d7757/comments/76d54016-755b-42ca-ad07-eb9a1c77c33d/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c1b76442-eab6-4796-9517-8454425d7757/comments/76d54016-755b-42ca-ad07-eb9a1c77c33d/values new file mode 100644 index 0000000..1566702 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c1b76442-eab6-4796-9517-8454425d7757/comments/76d54016-755b-42ca-ad07-eb9a1c77c33d/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 28 Jan 2010 22:58:08 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c1b76442-eab6-4796-9517-8454425d7757/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c1b76442-eab6-4796-9517-8454425d7757/values new file mode 100644 index 0000000..364629d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c1b76442-eab6-4796-9517-8454425d7757/values @@ -0,0 +1,17 @@ +creator: W. Trevor King <wking@drexel.edu> + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: open + + +summary: '`be serve` authentication / authorization' + + +time: Mon, 25 Jan 2010 21:59:03 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/comments/06e45775-1c46-4793-a34e-2cc86a8db097/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/comments/06e45775-1c46-4793-a34e-2cc86a8db097/body new file mode 100644 index 0000000..c5f1b4f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/comments/06e45775-1c46-4793-a34e-2cc86a8db097/body @@ -0,0 +1 @@ +Added the option in my be-html branch diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/comments/06e45775-1c46-4793-a34e-2cc86a8db097/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/comments/06e45775-1c46-4793-a34e-2cc86a8db097/values new file mode 100644 index 0000000..f7b7498 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/comments/06e45775-1c46-4793-a34e-2cc86a8db097/values @@ -0,0 +1,8 @@ +Author: Gianluca Montecchi <gian@grys.it> + + +Content-type: text/plain + + +Date: Thu, 08 Oct 2009 20:16:46 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/values new file mode 100644 index 0000000..27cfc2f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/values @@ -0,0 +1,17 @@ +creator: gianluca <gian@galactica> + + +reporter: gianluca <gian@galactica> + + +severity: wishlist + + +status: fixed + + +summary: Add a verbose option to "be html"? + + +time: Fri, 03 Jul 2009 21:17:41 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/04d71e10-9e44-4006-ab37-b4cc71647671/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/04d71e10-9e44-4006-ab37-b4cc71647671/body new file mode 100644 index 0000000..832cfd3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/04d71e10-9e44-4006-ab37-b4cc71647671/body @@ -0,0 +1,3 @@ +Bugs-Everywhere-Web/start-beweb.py should call python not python2.4 +it works great with python2.5 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/04d71e10-9e44-4006-ab37-b4cc71647671/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/04d71e10-9e44-4006-ab37-b4cc71647671/values new file mode 100644 index 0000000..cdba2a5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/04d71e10-9e44-4006-ab37-b4cc71647671/values @@ -0,0 +1,8 @@ +Author: j + + +Content-type: text/plain + + +Date: Mon, 14 Apr 2008 16:43:07 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/1cb7063f-07ce-4a76-98f9-d184e1ee7282/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/1cb7063f-07ce-4a76-98f9-d184e1ee7282/body new file mode 100644 index 0000000..a490992 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/1cb7063f-07ce-4a76-98f9-d184e1ee7282/body @@ -0,0 +1 @@ +Looks like j@oil21.org fixed this in 211.3.1. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/1cb7063f-07ce-4a76-98f9-d184e1ee7282/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/1cb7063f-07ce-4a76-98f9-d184e1ee7282/values new file mode 100644 index 0000000..6a0464f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/1cb7063f-07ce-4a76-98f9-d184e1ee7282/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 24 Nov 2008 13:23:43 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/values new file mode 100644 index 0000000..90fedbc --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/values @@ -0,0 +1,14 @@ +creator: j + + +severity: minor + + +status: fixed + + +summary: use python instead of python2.4 in Bugs-Everywhere-Web/start-beweb.py + + +time: Mon, 14 Apr 2008 16:42:37 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/2ca25dd6-e9d1-4581-bd29-50f2eaa32fe4/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/2ca25dd6-e9d1-4581-bd29-50f2eaa32fe4/body new file mode 100644 index 0000000..d589f18 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/2ca25dd6-e9d1-4581-bd29-50f2eaa32fe4/body @@ -0,0 +1,7 @@ +When running `python test.py` I recieved lots of errors due to 'tla' +(the GNU Arch revision control system binary) not being installed. +I had expected test.py to only test the backends for installed VCSs. + +I've added a note saying that `python test.py` tests *all* the +backends, but someone who understands the usage better can probably +write a nicer version. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/2ca25dd6-e9d1-4581-bd29-50f2eaa32fe4/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/2ca25dd6-e9d1-4581-bd29-50f2eaa32fe4/values new file mode 100644 index 0000000..4255708 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/2ca25dd6-e9d1-4581-bd29-50f2eaa32fe4/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 13 Nov 2008 16:35:24 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/acbecd72-988c-4899-a340-fea370ce15a8/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/acbecd72-988c-4899-a340-fea370ce15a8/body new file mode 100644 index 0000000..cf9af30 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/acbecd72-988c-4899-a340-fea370ce15a8/body @@ -0,0 +1,5 @@ +I rewrote test.py, so I suppose I'm the person who understands it +better now ;). The usage is now documented in the test.py lead +comment. The becommand tests now attempt to run with the first +*installed* versioning system, which should reduce cryptic errors. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/acbecd72-988c-4899-a340-fea370ce15a8/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/acbecd72-988c-4899-a340-fea370ce15a8/values new file mode 100644 index 0000000..cf27de6 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/acbecd72-988c-4899-a340-fea370ce15a8/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Wed, 19 Nov 2008 17:11:51 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/b3fabbe0-f05d-42a1-9037-e59e628a83e2/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/b3fabbe0-f05d-42a1-9037-e59e628a83e2/body new file mode 100644 index 0000000..77d75fb --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/b3fabbe0-f05d-42a1-9037-e59e628a83e2/body @@ -0,0 +1,3 @@ +Ideally the tests would fail gracefully with some simple message like +"tla version control system not found", and we could skip the message +in the test.py docstring. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/b3fabbe0-f05d-42a1-9037-e59e628a83e2/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/b3fabbe0-f05d-42a1-9037-e59e628a83e2/values new file mode 100644 index 0000000..f38cb7f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/b3fabbe0-f05d-42a1-9037-e59e628a83e2/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 13 Nov 2008 16:38:36 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/values new file mode 100644 index 0000000..354de99 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/values @@ -0,0 +1,14 @@ +creator: wking + + +severity: minor + + +status: fixed + + +summary: Usage of be/test.py is unclear + + +time: Thu, 13 Nov 2008 16:31:41 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c592a1e8-f2c8-4dfb-8550-955123073947/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c592a1e8-f2c8-4dfb-8550-955123073947/values new file mode 100644 index 0000000..e8ba31c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c592a1e8-f2c8-4dfb-8550-955123073947/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: Needs ability to create comments + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/22348320-40d3-422c-bdf0-0f6a6bde3fab/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/22348320-40d3-422c-bdf0-0f6a6bde3fab/body new file mode 100644 index 0000000..2767d69 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/22348320-40d3-422c-bdf0-0f6a6bde3fab/body @@ -0,0 +1,6 @@ +In my Tue, 25 Nov 2008 08:30:19 -0500 email: + +I thought feature requests would just have "wishlist" severity. What +would be an example of a to-do item that is not a feature request or a +bug? + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/22348320-40d3-422c-bdf0-0f6a6bde3fab/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/22348320-40d3-422c-bdf0-0f6a6bde3fab/values new file mode 100644 index 0000000..6bc9258 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/22348320-40d3-422c-bdf0-0f6a6bde3fab/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 20:12:35 +0000 + + +In-reply-to: 354dcfc6-5997-4ffe-b7a0-baa852213539 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/354dcfc6-5997-4ffe-b7a0-baa852213539/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/354dcfc6-5997-4ffe-b7a0-baa852213539/body new file mode 100644 index 0000000..7fcd766 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/354dcfc6-5997-4ffe-b7a0-baa852213539/body @@ -0,0 +1,7 @@ +In Aaron's Mon, 24 Nov 2008 19:15:08 -0500 email, he adds: + +Issue trackers should provide tracking of +1. bugs +2. feature requests +3. to-do items. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/354dcfc6-5997-4ffe-b7a0-baa852213539/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/354dcfc6-5997-4ffe-b7a0-baa852213539/values new file mode 100644 index 0000000..d000e42 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/354dcfc6-5997-4ffe-b7a0-baa852213539/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 20:11:02 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/c129067c-2341-4e7a-92a6-2dcd30d3bbf5/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/c129067c-2341-4e7a-92a6-2dcd30d3bbf5/body new file mode 100644 index 0000000..04a97cd --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/c129067c-2341-4e7a-92a6-2dcd30d3bbf5/body @@ -0,0 +1,14 @@ +If you want more granularity than just `wishlist' what about the +`severities': + todo-critical + todo-minor + todo-... +Then get a list of available severities with + $ be list --help | grep -A1 '^severity' + severity + wishlist,minor,serious,critical,fatal,todo-critical,todo-minor +And show all the todos: + $ be list --severity todo-critical,todo-minor + +You can configure all the severities you'd like with + $ be set severity wishlist,minor,... diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/c129067c-2341-4e7a-92a6-2dcd30d3bbf5/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/c129067c-2341-4e7a-92a6-2dcd30d3bbf5/values new file mode 100644 index 0000000..34d6548 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/c129067c-2341-4e7a-92a6-2dcd30d3bbf5/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 20:20:39 +0000 + + +In-reply-to: f847c981-873e-41ae-b5ce-83dfe60b9afe + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/f847c981-873e-41ae-b5ce-83dfe60b9afe/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/f847c981-873e-41ae-b5ce-83dfe60b9afe/body new file mode 100644 index 0000000..cc02836 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/f847c981-873e-41ae-b5ce-83dfe60b9afe/body @@ -0,0 +1,10 @@ +In Aaron's Tue, 25 Nov 2008 09:32:29 -0500 email: + +I think that approach doesn't give features the richness they need. +Features also have severities-- some features are important, and others +are just nice-to-have. And there should be a way to list *only* bugs, +or *only* features. + +In a bug tracker, "wishlist" is either an aberration, or it means a very +low severity. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/f847c981-873e-41ae-b5ce-83dfe60b9afe/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/f847c981-873e-41ae-b5ce-83dfe60b9afe/values new file mode 100644 index 0000000..62a3fb7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/f847c981-873e-41ae-b5ce-83dfe60b9afe/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 20:14:26 +0000 + + +In-reply-to: 22348320-40d3-422c-bdf0-0f6a6bde3fab + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/values new file mode 100644 index 0000000..d000d2e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c76d7899-d495-4103-9355-012c0a6fece3/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: Support 'issues', like todo, better + + +time: Wed, 04 Jan 2006 21:09:02 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/body new file mode 100644 index 0000000..7f46872 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/body @@ -0,0 +1,17 @@ +Example: + +We're working happily in a versioned bugdir, and our RCS knows who we +are. We create a temporary repository copy from a previous revision +for diff generation. We set the RCS for the copy to "None", since we +didn't bother initializing our normal RCS in the snapshot copy. But +now the BugDir instantized on the copy doesn't know who we are! + +Solution: + +Track user id in the bugdir settings file. If you +bugdir.settings["user_id"], it will be saved and loaded. When loaded, +it will also set bugdir.user_id. If you set rcs.user_id, it will be +returned by rcs.get_user_id(), instead of returing the output of +rcs._rcs_get_user_id(). We should be caching the output of +_rcs_get_user_id() anyway. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values new file mode 100644 index 0000000..645e8c9 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 22 Nov 2008 21:43:29 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/25c67b0b-1afd-4613-a787-e0f018614966/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/25c67b0b-1afd-4613-a787-e0f018614966/body new file mode 100644 index 0000000..62c14e6 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/25c67b0b-1afd-4613-a787-e0f018614966/body @@ -0,0 +1 @@ +This bug duplicates a403de79-8f39-41f2-b9ec-15053b175ee2 diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/25c67b0b-1afd-4613-a787-e0f018614966/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/25c67b0b-1afd-4613-a787-e0f018614966/values new file mode 100644 index 0000000..32491b7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/25c67b0b-1afd-4613-a787-e0f018614966/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 23 Nov 2008 12:37:57 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/7dfdf230-231b-43e0-9b46-58d4d18eded1/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/7dfdf230-231b-43e0-9b46-58d4d18eded1/body new file mode 100644 index 0000000..090895e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/7dfdf230-231b-43e0-9b46-58d4d18eded1/body @@ -0,0 +1 @@ +Merged into bug a403de79-8f39-41f2-b9ec-15053b175ee2
\ No newline at end of file diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/7dfdf230-231b-43e0-9b46-58d4d18eded1/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/7dfdf230-231b-43e0-9b46-58d4d18eded1/values new file mode 100644 index 0000000..a0c3bd7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/7dfdf230-231b-43e0-9b46-58d4d18eded1/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 25 Nov 2008 02:24:05 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/values new file mode 100644 index 0000000..947a596 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/values @@ -0,0 +1,14 @@ +creator: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: closed + + +summary: Allow user id to be cached in settings for duplicate bugdirs + + +time: Sat, 22 Nov 2008 21:36:06 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cb56c990-a757-4aef-9888-a30918a7b3d7/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cb56c990-a757-4aef-9888-a30918a7b3d7/values new file mode 100644 index 0000000..804f631 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cb56c990-a757-4aef-9888-a30918a7b3d7/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: 'Beweb: Stripey tables' + + +time: Wed, 04 Jan 2006 21:06:10 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/0e5fab2a-66eb-4f7d-979f-b50181f604d4/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/0e5fab2a-66eb-4f7d-979f-b50181f604d4/body new file mode 100644 index 0000000..89d64c5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/0e5fab2a-66eb-4f7d-979f-b50181f604d4/body @@ -0,0 +1,8 @@ +From Aaron's Mon, 24 Nov 2008 19:15:09 -0500 email + +cf5:oc: OK, maybe not fatal, but how about a new name that suggests +process tracking, not just bugs? + +If you can come with a better name, that would be great. But naming an +issue tracker for its bug-tracking features isn't a terrible idea. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/0e5fab2a-66eb-4f7d-979f-b50181f604d4/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/0e5fab2a-66eb-4f7d-979f-b50181f604d4/values new file mode 100644 index 0000000..2dea024 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/0e5fab2a-66eb-4f7d-979f-b50181f604d4/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 22 Jun 2009 19:48:44 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/f05359f6-1bfc-4aa6-9a6d-673516bc0f94/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/f05359f6-1bfc-4aa6-9a6d-673516bc0f94/body new file mode 100644 index 0000000..d7a57d9 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/f05359f6-1bfc-4aa6-9a6d-673516bc0f94/body @@ -0,0 +1 @@ +I dunno, bugs everywhere is such a great mental image... ;) diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/f05359f6-1bfc-4aa6-9a6d-673516bc0f94/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/f05359f6-1bfc-4aa6-9a6d-673516bc0f94/values new file mode 100644 index 0000000..06d6017 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/f05359f6-1bfc-4aa6-9a6d-673516bc0f94/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 15 Nov 2008 23:56:51 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/values new file mode 100644 index 0000000..29d76c7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/values @@ -0,0 +1,15 @@ +creator: abentley + + +severity: critical + + +status: closed + + +summary: OK, maybe not fatal, but how about a new name that suggests process tracking, + not just bugs? + + +time: Fri, 27 Jan 2006 14:37:25 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf77c72d-b099-413a-802e-a8892ac8c26b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf77c72d-b099-413a-802e-a8892ac8c26b/values new file mode 100644 index 0000000..4e6607c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/cf77c72d-b099-413a-802e-a8892ac8c26b/values @@ -0,0 +1,14 @@ +assigned: abentley + + +creator: abentley + + +severity: minor + + +status: fixed + + +summary: date-stamp bugs + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/comments/5b2e1ec8-3bb7-40cd-9f4f-74e5c59838f6/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/comments/5b2e1ec8-3bb7-40cd-9f4f-74e5c59838f6/body new file mode 100644 index 0000000..1090ace --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/comments/5b2e1ec8-3bb7-40cd-9f4f-74e5c59838f6/body @@ -0,0 +1,2 @@ +Available with + be -d DIR html diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/comments/5b2e1ec8-3bb7-40cd-9f4f-74e5c59838f6/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/comments/5b2e1ec8-3bb7-40cd-9f4f-74e5c59838f6/values new file mode 100644 index 0000000..9ac4884 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/comments/5b2e1ec8-3bb7-40cd-9f4f-74e5c59838f6/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Fri, 07 Aug 2009 17:58:58 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/values new file mode 100644 index 0000000..ce4bc92 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/values @@ -0,0 +1,17 @@ +creator: gianluca <gian@galactica> + + +reporter: gianluca <gian@galactica> + + +severity: wishlist + + +status: fixed + + +summary: Add the possibility to specify the repository directory to "be html"? + + +time: Fri, 03 Jul 2009 21:18:13 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/00c6f4d8-f965-4d2f-a652-17e58c20ab8c/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/00c6f4d8-f965-4d2f-a652-17e58c20ab8c/body new file mode 100644 index 0000000..a6ce192 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/00c6f4d8-f965-4d2f-a652-17e58c20ab8c/body @@ -0,0 +1,19 @@ +Hi, + + > http://bitbucket.org/sjl/cherryflavoredbugseverywhere/ + +Cool! I've set up a copy here: + + http://bugsweb.bugseverywhere.org/ + +(Since we don't have any open bugs lately, click "Closed" at the top, +or create some, but don't expect them to persist if you do.) + + > anyone has any feedback (on any aspect of it) I'd appreciate it. + +I'm pretty enthusiastic about merging this and then working on it +further. + +Thanks, + +- Chris. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/00c6f4d8-f965-4d2f-a652-17e58c20ab8c/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/00c6f4d8-f965-4d2f-a652-17e58c20ab8c/values new file mode 100644 index 0000000..4a25a25 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/00c6f4d8-f965-4d2f-a652-17e58c20ab8c/values @@ -0,0 +1,14 @@ +Alt-id: <m3eisxtgfx.fsf@pullcord.laptop.org> + + +Author: Chris Ball <cjb@laptop.org> + + +Content-type: text/plain + + +Date: Fri, 03 Jul 2009 20:55:30 -0400 + + +In-reply-to: 2496ccca-130b-4459-bfae-9d9ef0138177 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/16357f68-19c0-4bf9-8220-b88b52b3456d/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/16357f68-19c0-4bf9-8220-b88b52b3456d/body new file mode 100644 index 0000000..5a3508c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/16357f68-19c0-4bf9-8220-b88b52b3456d/body @@ -0,0 +1,26 @@ +Hi everyone. I found Bugs Everywhere and really like the idea of +distributed bug tracking. I felt like practicing building a CherryPy +site so I put together a quick web interface to BE. I know there's +already a TurboGears one in the works, but I needed an excuse to try +out CherryPy again after working with Django for a while. + +Would any of you be willing to take a look at what I've got so far and +tell me what you think I could improve? + +To install and use it: + +* Install CherryPy from http://cherrypy.org/ if you don't have it. +* Install Jinja2 from http://jinja.pocoo.org/2/ if you don't have it. +* Install BugsEverywhere - you probably know how to do this :) +* Download a zip/tar of my project (or hg clone) from http://bitbucket.org/sjl/cherryflavoredbugseverywhere/ +* Unzip my project and put the folder in your Python site-packages +directory. +* Symlink site-packages/cherryflavoredbugseverywhere/cfbe.py to /usr/ +local/bin/cfbe +* Use the "cfbe [project_root]" command to start up the web interface +for that project. +* Visit http://localhost:8080/ in a browser. + +I know that's a lot of steps. I'd like to streamline it quite a bit, +but first I wanted to see if you have any feedback on the system +itself. Thanks! diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/16357f68-19c0-4bf9-8220-b88b52b3456d/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/16357f68-19c0-4bf9-8220-b88b52b3456d/values new file mode 100644 index 0000000..bb88284 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/16357f68-19c0-4bf9-8220-b88b52b3456d/values @@ -0,0 +1,11 @@ +Alt-id: <272FECFE-D16B-47B7-B39A-E2C8A718CCC5@stevelosh.com> + + +Author: Steve Losh <steve@stevelosh.com> + + +Content-type: text/plain + + +Date: Sat, 07 Feb 2009 16:30:33 -0500 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/1f25cba2-03ee-43e1-a042-ef6724938ad8/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/1f25cba2-03ee-43e1-a042-ef6724938ad8/body new file mode 100644 index 0000000..d2ef28c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/1f25cba2-03ee-43e1-a042-ef6724938ad8/body @@ -0,0 +1,77 @@ +Those are beautiful templates -- can you share those? I'd love to +study the HTML and CSS behind them. + +On Sat, Feb 7, 2009 at 5:48 PM, Steve Losh <steve@stevelosh.com> wrote: +> Hey Chris, thanks for the comments. +> +>> +>> My initial impression is that this looks good enough already to merge as +>> a replacement for the turbogears site. What does everyone else think? +>> +> +> I'm not quite sure it's there yet. There are a bunch of bugs I've got +> marked as "beta" that I'd like to see fixed before it's ready for real use. +> Hopefully they shouldn't be too tough to fix. You can point CFBE at itself +> to see them. :) +> +>> Could you explain a little about how you handle authorship of bug +>> changes at the moment, and if it looks plausible to try making it +>> multiuser? (Having it handle more than one "user" logged in at once.) +>> +> +> That's something I need advice on. Right now CFBE is pretty much only +> suitable for local use - you check out whatever you're working on and use it +> as a local interface to the bugs in the repository. Change those, check in, +> etc. It's effectively just a pretty version of the command line be tool. +> +> I haven't used CherryPy's session/authentication support before. This might +> be a good time for me to learn. One way it might be able to handle multiple +> users hitting a central server: +> +> * Each user has to register with the server and be approved by an admin. +> * Each account would be mapped to a contributor string, the same one that +> would show up if you were going to commit to the repository. +> * Once you have an account, you'd login to make any changes. +> +> +> Aside from all that, I'm a little fuzzy on how a centralized interface to a +> distributed bug tracking system should work. A read-only interface to a +> central "main" repository would be easy. Run the server in read-only mode +> pointing at the main repository. People can use it to look at the bugs in +> the tip of that repository. +> +> If it's not read-only, what happens when a user changes/adds/whatevers a +> bug? Should CFBE commit that change to the repository right then and there? +> Should it never commit, just update the bugdir and let the commits happen +> manually? +> +> What happens when you have multiple branches for a repository? Should there +> be one CFBE instance for each branch, or a single one that lets you switch +> between branches (effectively switching between revisions)? +> +> Those are the kind of things that don't really apply when CFBE is just a +> local interface to a single repository. If anyone has any advice on how a +> multi-user interface should work I'd love to hear it! +> +> -- +> Steve Losh +> http://stevelosh.com/ +> +> +> _______________________________________________ +> Be-devel mailing list +> Be-devel@bugseverywhere.org +> http://void.printf.net/cgi-bin/mailman/listinfo/be-devel +> + + + +-- +Matthew Wilson +matt@tplus1.com +http://tplus1.com + +_______________________________________________ +Be-devel mailing list +Be-devel@bugseverywhere.org +http://void.printf.net/cgi-bin/mailman/listinfo/be-devel diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/1f25cba2-03ee-43e1-a042-ef6724938ad8/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/1f25cba2-03ee-43e1-a042-ef6724938ad8/values new file mode 100644 index 0000000..ca3efd0 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/1f25cba2-03ee-43e1-a042-ef6724938ad8/values @@ -0,0 +1,14 @@ +Alt-id: <f6f643a20902071531y6aa3d7a6k7c5a4bd4aa5a04f6@mail.gmail.com> + + +Author: Matthew Wilson <matt@tplus1.com> + + +Content-type: text/plain + + +Date: Sat, 07 Feb 2009 18:31:04 -0500 + + +In-reply-to: 21c90231-d7f2-49bb-97d9-99e16459d799 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/21c90231-d7f2-49bb-97d9-99e16459d799/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/21c90231-d7f2-49bb-97d9-99e16459d799/body new file mode 100644 index 0000000..5fc4981 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/21c90231-d7f2-49bb-97d9-99e16459d799/body @@ -0,0 +1,52 @@ +Hey Chris, thanks for the comments. + +> +> My initial impression is that this looks good enough already to +> merge as +> a replacement for the turbogears site. What does everyone else think? +> + +I'm not quite sure it's there yet. There are a bunch of bugs I've got +marked as "beta" that I'd like to see fixed before it's ready for real +use. Hopefully they shouldn't be too tough to fix. You can point +CFBE at itself to see them. :) + +> Could you explain a little about how you handle authorship of bug +> changes at the moment, and if it looks plausible to try making it +> multiuser? (Having it handle more than one "user" logged in at once.) +> + +That's something I need advice on. Right now CFBE is pretty much only +suitable for local use - you check out whatever you're working on and +use it as a local interface to the bugs in the repository. Change +those, check in, etc. It's effectively just a pretty version of the +command line be tool. + +I haven't used CherryPy's session/authentication support before. This +might be a good time for me to learn. One way it might be able to +handle multiple users hitting a central server: + +* Each user has to register with the server and be approved by an admin. +* Each account would be mapped to a contributor string, the same one +that would show up if you were going to commit to the repository. +* Once you have an account, you'd login to make any changes. + + +Aside from all that, I'm a little fuzzy on how a centralized interface +to a distributed bug tracking system should work. A read-only +interface to a central "main" repository would be easy. Run the +server in read-only mode pointing at the main repository. People can +use it to look at the bugs in the tip of that repository. + +If it's not read-only, what happens when a user changes/adds/whatevers +a bug? Should CFBE commit that change to the repository right then +and there? Should it never commit, just update the bugdir and let the +commits happen manually? + +What happens when you have multiple branches for a repository? Should +there be one CFBE instance for each branch, or a single one that lets +you switch between branches (effectively switching between revisions)? + +Those are the kind of things that don't really apply when CFBE is just +a local interface to a single repository. If anyone has any advice on +how a multi-user interface should work I'd love to hear it! diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/21c90231-d7f2-49bb-97d9-99e16459d799/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/21c90231-d7f2-49bb-97d9-99e16459d799/values new file mode 100644 index 0000000..fbe6c0e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/21c90231-d7f2-49bb-97d9-99e16459d799/values @@ -0,0 +1,14 @@ +Alt-id: <D765386C-4D43-4AE0-83E3-986A1CB4008C@stevelosh.com> + + +Author: Steve Losh <steve@stevelosh.com> + + +Content-type: text/plain + + +Date: Sat, 07 Feb 2009 17:48:06 -0500 + + +In-reply-to: 42d57a41-219f-46db-9fda-21b42351da63 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/2496ccca-130b-4459-bfae-9d9ef0138177/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/2496ccca-130b-4459-bfae-9d9ef0138177/body new file mode 100644 index 0000000..ed4f97d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/2496ccca-130b-4459-bfae-9d9ef0138177/body @@ -0,0 +1,3 @@ +Speaking of that interface, I changed up the look and feel a bit last +weekend. It's still at http://bitbucket.org/sjl/cherryflavoredbugseverywhere/ + -- if anyone has any feedback (on any aspect of it) I'd appreciate it. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/2496ccca-130b-4459-bfae-9d9ef0138177/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/2496ccca-130b-4459-bfae-9d9ef0138177/values new file mode 100644 index 0000000..e602a56 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/2496ccca-130b-4459-bfae-9d9ef0138177/values @@ -0,0 +1,14 @@ +Alt-id: <4701D71B-A14D-4C63-ABCC-E7E5FFE4E4BA@stevelosh.com> + + +Author: Steve Losh <steve@stevelosh.com> + + +Content-type: text/plain + + +Date: Fri, 03 Jul 2009 20:34:51 -0400 + + +In-reply-to: 16357f68-19c0-4bf9-8220-b88b52b3456d + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/42d57a41-219f-46db-9fda-21b42351da63/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/42d57a41-219f-46db-9fda-21b42351da63/body new file mode 100644 index 0000000..a280d8c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/42d57a41-219f-46db-9fda-21b42351da63/body @@ -0,0 +1,25 @@ +Hi Steve, + + > Hi everyone. I found Bugs Everywhere and really like the idea of + > distributed bug tracking. I felt like practicing building a + > CherryPy site so I put together a quick web interface to BE. I + > know there's already a TurboGears one in the works, but I needed an + > excuse to try out CherryPy again after working with Django for a + > while. + +This looks awesome, thanks! I've taken some screenshots for others to +see: + +http://chris.printf.net/cfbe-main.png +http://chris.printf.net/cfbe-detail.png + +My initial impression is that this looks good enough already to merge as +a replacement for the turbogears site. What does everyone else think? + +Could you explain a little about how you handle authorship of bug +changes at the moment, and if it looks plausible to try making it +multiuser? (Having it handle more than one "user" logged in at once.) + +Great work, thanks! + +- Chris. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/42d57a41-219f-46db-9fda-21b42351da63/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/42d57a41-219f-46db-9fda-21b42351da63/values new file mode 100644 index 0000000..e05f99d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/42d57a41-219f-46db-9fda-21b42351da63/values @@ -0,0 +1,14 @@ +Alt-id: <m3zlgxyjo5.fsf@pullcord.laptop.org> + + +Author: Chris Ball <cjb@laptop.org> + + +Content-type: text/plain + + +Date: Sat, 07 Feb 2009 17:19:22 -0500 + + +In-reply-to: 16357f68-19c0-4bf9-8220-b88b52b3456d + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/5e339bac-f4f3-407b-974a-b88795d3573b/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/5e339bac-f4f3-407b-974a-b88795d3573b/body new file mode 100644 index 0000000..593fa83 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/5e339bac-f4f3-407b-974a-b88795d3573b/body @@ -0,0 +1,28 @@ +Hi, + + > Works for me. I've done this now, which closes the last open bug + > in my repo :D. + +Wow. Congrats! I've merged your branch. + + > All the new functionality comes from bug.extra_strings, which + > provides a list for storing arbitrary strings in the bug's + > permanent state. + +That's going to be really useful. + + > Next up: regexp searching for list --extra-strings! ;). + +Awesome. + + > Oh, and obviously there must still be bugs in BE. Please submit + > more ;). + +Perhaps it's a good time to merge Steve Losh's CherryPy web interface? + +http://void.printf.net/pipermail/be-devel/2009-February/000095.html +http://bitbucket.org/sjl/cherryflavoredbugseverywhere/ + +Thanks, + +- Chris. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/5e339bac-f4f3-407b-974a-b88795d3573b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/5e339bac-f4f3-407b-974a-b88795d3573b/values new file mode 100644 index 0000000..a4063be --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/5e339bac-f4f3-407b-974a-b88795d3573b/values @@ -0,0 +1,14 @@ +Alt-id: <m31vp82yyj.fsf@pullcord.laptop.org> + + +Author: Chris Ball <cjb@laptop.org> + + +Content-type: text/plain + + +Date: Thu, 25 Jun 2009 10:02:44 -0400 + + +In-reply-to: 16357f68-19c0-4bf9-8220-b88b52b3456d + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/7fa903a3-f9e6-4e4d-8128-0f26e1ce664b/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/7fa903a3-f9e6-4e4d-8128-0f26e1ce664b/body new file mode 100644 index 0000000..d52f4b9 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/7fa903a3-f9e6-4e4d-8128-0f26e1ce664b/body @@ -0,0 +1,22 @@ +On Jun 25, 2009, at 10:02 AM, Chris Ball <cjb@laptop.org> wrote: +> +>> Oh, and obviously there must still be bugs in BE. Please submit +>> more ;). +> +> Perhaps it's a good time to merge Steve Losh's CherryPy web interface? +> +> http://void.printf.net/pipermail/be-devel/2009-February/000095.html +> http://bitbucket.org/sjl/cherryflavoredbugseverywhere/ +> + +Hey, I haven't touched the web interface in a while, but I should have +some time to fix some stuff up tonight and tomorrow. Hold off on +merging it in until then. + +I'm still curious as to what people think the role of a web interface +like this should be. When I wrote it I meant it as a single-user +interface like the command line one. It could definitely work as a +public, read-only interface too. + +If the goal is to allow more than one person to add issues, how should +commits go? One commit per change? Commit every X minutes if necessary? diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/7fa903a3-f9e6-4e4d-8128-0f26e1ce664b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/7fa903a3-f9e6-4e4d-8128-0f26e1ce664b/values new file mode 100644 index 0000000..eea816a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/7fa903a3-f9e6-4e4d-8128-0f26e1ce664b/values @@ -0,0 +1,14 @@ +Alt-id: <26FBD153-39C5-4641-AF5E-749731964086@stevelosh.com> + + +Author: Steve Losh <steve@stevelosh.com> + + +Content-type: text/plain + + +Date: Thu, 25 Jun 2009 10:23:04 -0400 + + +In-reply-to: 5e339bac-f4f3-407b-974a-b88795d3573b + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/e249e2aa-2029-4a96-bc84-962366e07fd6/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/e249e2aa-2029-4a96-bc84-962366e07fd6/body new file mode 100644 index 0000000..a6dd2f1 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/e249e2aa-2029-4a96-bc84-962366e07fd6/body @@ -0,0 +1,26 @@ +On Sat, Feb 07, 2009 at 05:48:06PM -0500, Steve Losh wrote: +>> My initial impression is that this looks good enough already to merge as +>> a replacement for the turbogears site. What does everyone else think? +> +> I'm not quite sure it's there yet. There are a bunch of bugs I've +> got marked as "beta" that I'd like to see fixed before it's ready +> for real use. Hopefully they shouldn't be too tough to fix. You +> can point CFBE at itself to see them. :) + +Steve's also versioning it with Mercurial. Will he mind changing to +Bazaar? + +Steve, I've touched up CFBE to work with my current BE branch. Some +of the changes apply to the trunk BE, and hopefully the rest will +soon. I think the version-naming issue is what's currently blocking +their adoption ;). I've put up my CFBE branch at + static-http://www.physics.drexel.edu/~wking/code/hg/cfbe +for Mercurial. + +Everyone, should CFBE-specific discussions move off-list? More +generally, I've been sending in lots of what-I'm-working on messages, +but not hearing much back, so let me know if I'm being too obnoxious, +and I'll shut up (or at least move more off-list) ;). + +Cheers, +Trevor diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/e249e2aa-2029-4a96-bc84-962366e07fd6/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/e249e2aa-2029-4a96-bc84-962366e07fd6/values new file mode 100644 index 0000000..3847736 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/e249e2aa-2029-4a96-bc84-962366e07fd6/values @@ -0,0 +1,14 @@ +Alt-id: <20090721135907.GB4469@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 21 Jul 2009 09:59:07 -0400 + + +In-reply-to: 21c90231-d7f2-49bb-97d9-99e16459d799 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/fa60ce1f-a809-4fb3-a2cd-1a2e0bdd0e0a/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/fa60ce1f-a809-4fb3-a2cd-1a2e0bdd0e0a/body new file mode 100644 index 0000000..b1db016 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/fa60ce1f-a809-4fb3-a2cd-1a2e0bdd0e0a/body @@ -0,0 +1,62 @@ +On Thu, Jun 25, 2009 at 10:23:04AM -0400, Steve Losh wrote: +> I'm still curious as to what people think the role of a web interface like +> this should be. When I wrote it I meant it as a single-user interface like +> the command line one. It could definitely work as a public, read-only +> interface too. + +I think the multi-user/public is the way to go. I'd also like to see +a procmail-able script to handle multi-user/public access via email, +which would have all the same issues we're worrying about here. + +On Sat, Feb 07, 2009 at 05:48:06PM -0500, Steve Losh wrote: +> I haven't used CherryPy's session/authentication support before. This +> might be a good time for me to learn. One way it might be able to handle +> multiple users hitting a central server: +> +> * Each user has to register with the server and be approved by an admin. +> * Each account would be mapped to a contributor string, the same one that +> would show up if you were going to commit to the repository. +> * Once you have an account, you'd login to make any changes. + +This sounds good to me. Yay spam reduction ;). + +> If it's not read-only, what happens when a user changes/adds/whatevers a +> bug? Should CFBE commit that change to the repository right then and +> there? Should it never commit, just update the bugdir and let the commits +> happen manually? + +On Thu, Jun 25, 2009 at 10:23:04AM -0400, Steve Losh wrote: +> One commit per change? Commit every X minutes if necessary? + +On Sat, Feb 07, 2009 at 05:48:06PM -0500, Steve Losh wrote: +> What happens when you have multiple branches for a repository? Should +> there be one CFBE instance for each branch, or a single one that lets you +> switch between branches (effectively switching between revisions)? + +There are interesting discussions about the role of distributed +bugtracking here (I'm sure there are others): + http://lwn.net/Articles/281849/ + http://community.livejournal.com/evan_tech/248736.html + +Personally, I've never done much cherry-picking or anything that would +require me to determine exactly which parts of someone's work I want +and which I don't want. I just merge someone's head and edit out the +bits I don't like, a process that also works well for our current +"commit however you want" BE development model ;). Maybe that just +shows that I only work on minor branches of small projects :p. In the +absence of big-project advice, I think we just limit the web front end +to our current model, and let the web interface commit however it +wants as well ;). +1 for adding a <commit> button to the web +interface ;). + +One caveat about a multi-user interface would be that it would allow +the casual users to commit bugs about whatever version they had +installed onto the head of the public-bug branch. In order to figure +out what version of the project they were talking about, the project +should have a way for the user to get a unique version string, ideally +be the bzr-revision-id/git-commit/etc. of the commit for the version +they were using. This would allow developers to determine what branch +to work on with the bug fix, and what branches needed to pull the +eventual fix. If the initially reported buggy version wasn't actually +the root of the bug, oh well :p. Material for a later related bug +report or a reopen. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/fa60ce1f-a809-4fb3-a2cd-1a2e0bdd0e0a/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/fa60ce1f-a809-4fb3-a2cd-1a2e0bdd0e0a/values new file mode 100644 index 0000000..721f9fd --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/fa60ce1f-a809-4fb3-a2cd-1a2e0bdd0e0a/values @@ -0,0 +1,14 @@ +Alt-id: <20090625154734.GA19441@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 25 Jun 2009 11:47:34 -0400 + + +In-reply-to: 16357f68-19c0-4bf9-8220-b88b52b3456d + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/values new file mode 100644 index 0000000..8cf85c9 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/d9959864-ea91-475a-a075-f39aa6760f98/values @@ -0,0 +1,20 @@ +assigned: Steve Losh <steve@stevelosh.com> + + +creator: W. Trevor King <wking@drexel.edu> + + +reporter: Steve Losh <steve@stevelosh.com> + + +severity: wishlist + + +status: assigned + + +summary: CherryPy interface "Cherry-flavored BE" + + +time: Tue, 21 Jul 2009 18:52:44 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/da2b09ff-af24-40f3-9b8d-6ffaa5f41164/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/da2b09ff-af24-40f3-9b8d-6ffaa5f41164/values new file mode 100644 index 0000000..2832bb3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/da2b09ff-af24-40f3-9b8d-6ffaa5f41164/values @@ -0,0 +1,17 @@ +creator: Gianluca Montecchi <gian@grys.it> + + +reporter: Gianluca Montecchi <gian@grys.it> + + +severity: wishlist + + +status: open + + +summary: Add an icon near the status string in "be html" output + + +time: Tue, 04 Aug 2009 21:15:52 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/1182d8e6-5e87-4d0a-b271-c298c36bbc21/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/1182d8e6-5e87-4d0a-b271-c298c36bbc21/body new file mode 100644 index 0000000..2887d2b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/1182d8e6-5e87-4d0a-b271-c298c36bbc21/body @@ -0,0 +1,10 @@ +Problem was due to + open-value-file + write-value-file + add/update-value-file +which should be (and now is) + open-value-file + write-value-file + close-value-file + add/update-value-file +since it was getting added before the changes we'd written were flushed out. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/1182d8e6-5e87-4d0a-b271-c298c36bbc21/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/1182d8e6-5e87-4d0a-b271-c298c36bbc21/values new file mode 100644 index 0000000..5972d7a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/1182d8e6-5e87-4d0a-b271-c298c36bbc21/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@example.com> + + +Content-type: text/plain + + +Date: Wed, 19 Nov 2008 01:12:37 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/8097468f-87a9-4d84-ac20-1772393bb54d/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/8097468f-87a9-4d84-ac20-1772393bb54d/body new file mode 100644 index 0000000..2c49b6b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/8097468f-87a9-4d84-ac20-1772393bb54d/body @@ -0,0 +1,26 @@ +It looks like the mapfiles are not being 'git add'ed after changes. + +$ mkdir BEtest +$ cd BEtest +$ git init +$ be set-root . +$ be new 'new' +$ git status +# On branch master +# +# Initial commit +# +# Changes to be committed: +# (use "git rm --cached <file>..." to unstage) +# +# new file: .be/bugs/8f021d79-44f5-479f-af12-c37e2caf3ce1/values +# new file: .be/settings +# new file: .be/version +# +# Changed but not updated: +# (use "git add <file>..." to update what will be committed) +# +# modified: .be/bugs/8f021d79-44f5-479f-af12-c37e2caf3ce1/values +# modified: .be/settings +# + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/8097468f-87a9-4d84-ac20-1772393bb54d/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/8097468f-87a9-4d84-ac20-1772393bb54d/values new file mode 100644 index 0000000..bb26755 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/8097468f-87a9-4d84-ac20-1772393bb54d/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 17 Nov 2008 15:03:58 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/values new file mode 100644 index 0000000..17288dc --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/values @@ -0,0 +1,14 @@ +creator: wking + + +severity: serious + + +status: fixed + + +summary: BE not notifying git of some changed files + + +time: Mon, 17 Nov 2008 15:02:15 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dba25cfd-aa15-457c-903a-b53ecb5a3b2c/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dba25cfd-aa15-457c-903a-b53ecb5a3b2c/values new file mode 100644 index 0000000..9271b50 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dba25cfd-aa15-457c-903a-b53ecb5a3b2c/values @@ -0,0 +1,15 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: there's a tyop in the wx example gui (in the line that appends ../../ to + sys.path) + + +time: Tue, 27 Dec 2005 16:59:49 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/body new file mode 100644 index 0000000..d29c749 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/body @@ -0,0 +1,43 @@ +BE should not crash when be list|show is used on a git repository that +have not the config variables user.name and user.email defined in the +.git/config file. + +To view the bug, in my opinion shold not be mandatory to have these two options +defined + + +Traceroute: + +galactica:~/Devel/dumb> be show 996 +Traceback (most recent call last): + File "/usr/bin/be", line 62, in <module> + sys.exit(cmdutil.execute(args[0], args[1:])) + File "/usr/lib/python2.5/site-packages/libbe/cmdutil.py", line 76, in execute + ret = cmd.execute([a.decode(enc) for a in args]) + File "/usr/lib/python2.5/site-packages/becommands/show.py", line 60, in execute + bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test) + File "/usr/lib/python2.5/site-packages/libbe/bugdir.py", line 302, in __init__ + self.load() + File "/usr/lib/python2.5/site-packages/libbe/bugdir.py", line 382, in load + self.load_settings() + File "/usr/lib/python2.5/site-packages/libbe/bugdir.py", line 411, in load_settings + self._setup_user_id(self.user_id) + File "/usr/lib/python2.5/site-packages/libbe/properties.py", line 293, in _fget + value = generator(self) + File "/usr/lib/python2.5/site-packages/libbe/bugdir.py", line 177, in _guess_user_id + return self.rcs.get_user_id() + File "/usr/lib/python2.5/site-packages/libbe/rcs.py", line 258, in get_user_id + id = self._rcs_get_user_id() + File "/usr/lib/python2.5/site-packages/libbe/git.py", line 56, in _rcs_get_user_id + status,output,error = self._u_invoke_client("config", "user.name") + File "/usr/lib/python2.5/site-packages/libbe/rcs.py", line 458, in _u_invoke_client + return self._u_invoke(cl_args, stdin=stdin,expect=expect,cwd=directory) + File "/usr/lib/python2.5/site-packages/libbe/rcs.py", line 450, in _u_invoke + raise CommandError(args, status, error) +libbe.rcs.CommandError: Command failed (1): + + +while executing + ['git', 'config', 'user.name'] +galactica:~/Devel/dumb> + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/values new file mode 100644 index 0000000..324b732 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/values @@ -0,0 +1,8 @@ +Author: Gianluca Montecchi <gian@grys.it> + + +Content-type: text/plain + + +Date: Mon, 03 Aug 2009 20:33:30 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/values new file mode 100644 index 0000000..375e44d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/values @@ -0,0 +1,17 @@ +creator: Gianluca Montecchi <gian@grys.it> + + +reporter: Gianluca Montecchi <gian@grys.it> + + +severity: minor + + +status: fixed + + +summary: BE should not crash if user.email and user.name are not defined + + +time: Mon, 03 Aug 2009 20:30:43 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0155831-499f-421a-ad02-cd15fc3fecf1/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0155831-499f-421a-ad02-cd15fc3fecf1/values new file mode 100644 index 0000000..bffaec4 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0155831-499f-421a-ad02-cd15fc3fecf1/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: No way to commit/update from beweb + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/09f950d4-9366-4e7b-98b3-9057999f8f38/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/09f950d4-9366-4e7b-98b3-9057999f8f38/body new file mode 100644 index 0000000..770af86 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/09f950d4-9366-4e7b-98b3-9057999f8f38/body @@ -0,0 +1,19 @@ +On Thu, Jul 16, 2009 at 09:39:30AM -0400, W. Trevor King wrote: +> Disclaimer: I imaging the current implementation will choke on +> non-text/plain content types. Also possibly on non-ascii encodings. + +Non-ascii encodings are now handled (although now the output is +base64-encoded). This is limited by poor unicode handling in the +email module for current pythons, see the log for more details. + +> I should probably allow the "help" command ... ;). + +Added, but it currently shows _all_ commands, not just allowed +commands. + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/09f950d4-9366-4e7b-98b3-9057999f8f38/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/09f950d4-9366-4e7b-98b3-9057999f8f38/values new file mode 100644 index 0000000..78bc87b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/09f950d4-9366-4e7b-98b3-9057999f8f38/values @@ -0,0 +1,14 @@ +Alt-id: <20090718131220.GA31832@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 18 Jul 2009 09:12:20 -0400 + + +In-reply-to: f1cde826-0506-4b4a-92ab-8499e953fa49 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/704b37ab-01bb-43d3-9e9f-f0d354f63c7d/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/704b37ab-01bb-43d3-9e9f-f0d354f63c7d/body new file mode 100644 index 0000000..e008923 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/704b37ab-01bb-43d3-9e9f-f0d354f63c7d/body @@ -0,0 +1,27 @@ +On Sat, Jul 18, 2009 at 06:05:51PM -0400, W. Trevor King wrote: +> My email interface now supports bug creation/comments that look +> like: +> +> $ cat | mail -s "[be-bug] new" "whatever-dev@fancyprojects.com" +> The demuxulizer is broken +> +> <describe bug> +> ^D + +The move towards the DBT interface means this example should now look +like + + $ cat | mail -s "[be-bug:submit] The demuxulizer is broken" whatever-dev@fancyprojects.com + Version: XYZ + + <describe bug> + -- + Ignored text + ^D + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/704b37ab-01bb-43d3-9e9f-f0d354f63c7d/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/704b37ab-01bb-43d3-9e9f-f0d354f63c7d/values new file mode 100644 index 0000000..b640d0b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/704b37ab-01bb-43d3-9e9f-f0d354f63c7d/values @@ -0,0 +1,14 @@ +Alt-id: <20090719130649.GA4164@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 19 Jul 2009 09:06:49 -0400 + + +In-reply-to: 7b904395-86e9-4eb1-8534-69cec63801d4 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/7b904395-86e9-4eb1-8534-69cec63801d4/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/7b904395-86e9-4eb1-8534-69cec63801d4/body new file mode 100644 index 0000000..800609e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/7b904395-86e9-4eb1-8534-69cec63801d4/body @@ -0,0 +1,27 @@ +Ah, it's been a good day :). My email interface now supports bug +creation/comments that look like: + + $ cat | mail -s "[be-bug] new" "whatever-dev@fancyprojects.com" + The demuxulizer is broken + + <describe bug> + ^D + +Which is probably easy enough for just about anybody ;). Also easy +for other projects to wrap into one of their tools: + + $ cat | projectAexecutable --report-bug + The demuxulizer is broken + + <describe bug> + ^D + +Which could do things like automatically add the version string, OS +name, etc. + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/7b904395-86e9-4eb1-8534-69cec63801d4/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/7b904395-86e9-4eb1-8534-69cec63801d4/values new file mode 100644 index 0000000..b70c6e3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/7b904395-86e9-4eb1-8534-69cec63801d4/values @@ -0,0 +1,14 @@ +Alt-id: <20090718220551.GB32230@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 18 Jul 2009 18:05:51 -0400 + + +In-reply-to: 09f950d4-9366-4e7b-98b3-9057999f8f38 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/a0e846ed-1549-4ec3-b94d-391e54610f61/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/a0e846ed-1549-4ec3-b94d-391e54610f61/body new file mode 100644 index 0000000..087d67a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/a0e846ed-1549-4ec3-b94d-391e54610f61/body @@ -0,0 +1,58 @@ +On Sun, Jul 19, 2009 at 09:09:05AM +1000, Ben Finney wrote: +> > The interface is basically "place your be command in the subject line" +> +> I would far prefer an interface of “place as many BE commands as you +> like at the top of the message body, ending with an optional terminator +> command, and they will each be executed in turn”. +> ... + +I think the idea behind my first approach was "you guys have +experience with the command line BE interface, so it will be easier to +test if you don't have to learn the DBT interface." The Debian people +have been doing this for a while though, so I imagine their email +interface is pretty good ;). + +Here's a short primer on my take on the DBT interface. + +The DBT has three main email addresses, each with it's own parsing style. + 1) creating bugs (submit@bugs.debian.org) + 2) commenting on bugs (<bug-number>@bugs.debian.org) + 3) controlling/managing bugs (control@bugs.debian.org) +I'm trying to squeeze these down into a single email address to avoid +having to tinker with the mail delivery system, so I've got everything +at (wking at tremily dot us) with subject tags: + 1) creating bugs + Subject: [be-bug:submit] new bug summary ... + 2) commenting on bugs + Subject: [be-bug:<bug-number>] human-specific subject + 3) control + Subject: [be-bug] human-specific subject + +The parsing styles each follow their DBT counterparts, but currently +have a much restricted breadth. + +The control-style consists of a list of allowed be commands, with one +command per line. Blank lines and lines beginning with '#' are +ignored, as well anything following a line starting with '--'. All the +listed commands are executed in order and their output returned. + +The comment-style interface appends a comment to the bug specified in +the subject tag. The the first non-multipart body is attached with +the appropriate content-type. In the case of "text/plain" contents, +anything following a line starting with '--' is stripped. + +The create-style interface creates a bug whose summary is given by the +email's post-tag subject. The body of the email must begin with a +psuedo-header containing at least the "Version" field. Anything after +the pseudo-header and before a line starting with '--' is, if present, +attached as the bugs first comment. + +Take a look at my interfaces/email/interactive/examples for some +examples. + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/a0e846ed-1549-4ec3-b94d-391e54610f61/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/a0e846ed-1549-4ec3-b94d-391e54610f61/values new file mode 100644 index 0000000..5f323c6 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/a0e846ed-1549-4ec3-b94d-391e54610f61/values @@ -0,0 +1,14 @@ +Alt-id: <20090719130153.GA4036@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 19 Jul 2009 09:01:53 -0400 + + +In-reply-to: cfd7cbc7-27ad-4618-8530-cb4d7323514a + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/cfd7cbc7-27ad-4618-8530-cb4d7323514a/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/cfd7cbc7-27ad-4618-8530-cb4d7323514a/body new file mode 100644 index 0000000..3db2a91 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/cfd7cbc7-27ad-4618-8530-cb4d7323514a/body @@ -0,0 +1,26 @@ +"W. Trevor King" <wking@drexel.edu> writes: + +> The interface is basically "place your be command in the subject line" + +I would far prefer an interface of “place as many BE commands as you +like at the top of the message body, ending with an optional terminator +command, and they will each be executed in turn”. + +This would allow a single message to perform a batch of BE commands that +are related, instead of requiring to send each command in a separate +message. + +It would also leave the subject field free for something more +descriptive. The subject field could also be used as the summary field +of newly-created bug reports. With a terminator command, this would +allow the message to be sent both to BE and to some other recipient +(e.g. a mailing list) explaining the change. + +Have a look at the email interface of the Debian BTS for an example +<URL:http://www.debian.org/Bugs/server-request>. + +-- + \ “Pinky, are you pondering what I'm pondering?” “Wuh, I think | + `\ so, Brain, but will they let the Cranberry Duchess stay in the | +_o__) Lincoln Bedroom?” —_Pinky and The Brain_ | +Ben Finney diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/cfd7cbc7-27ad-4618-8530-cb4d7323514a/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/cfd7cbc7-27ad-4618-8530-cb4d7323514a/values new file mode 100644 index 0000000..057b7fa --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/cfd7cbc7-27ad-4618-8530-cb4d7323514a/values @@ -0,0 +1,14 @@ +Alt-id: <87fxctbnce.fsf@benfinney.id.au> + + +Author: Ben Finney <bignose+hates-spam@benfinney.id.au> + + +Content-type: text/plain + + +Date: Sun, 19 Jul 2009 09:09:05 +1000 + + +In-reply-to: f1cde826-0506-4b4a-92ab-8499e953fa49 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/f1cde826-0506-4b4a-92ab-8499e953fa49/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/f1cde826-0506-4b4a-92ab-8499e953fa49/body new file mode 100644 index 0000000..37b9936 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/f1cde826-0506-4b4a-92ab-8499e953fa49/body @@ -0,0 +1,38 @@ +I finally did something towards a useful interactive email interface +;). As per our new guidelines, I'll develop this feature in it's own +branch: + http://www.physics.drexel.edu/~wking/code/bzr/be-email + +The interface is basically "place your be command in the subject line" +with a few exceptions. Some examples: + Subject: [be-bug] list --status=all + Subject: [be-bug] show --xml ID + Subject: [be-bug] new + Subject: [be-bug] comment ID +In the case of "new", the bug description is extracted from the first +non-blank body line. In the case of "comment", the email body is used +as the comment. Currently only "list", "show", "new", and "comment" +are allowed. + +You should get a reply email with exit status, stdout, and stderr from +your command. + +Send some mail to [wking (at) tremily (dot) us] to try it out! Depending +on spam attraction, this might be a limited time offer ;). + +Hopefully this lowers the entry barrier for bug reporting :). + +Disclaimer: I imaging the current implementation will choke on +non-text/plain content types. Also possibly on non-ascii encodings. +Probably lots of other bugs too... ;). For example, I should probably +allow the "help" command ... ;). + +Cheers, +Trevor + +-- +This email may be signed or encrypted with GPG (http://www.gnupg.org). +The GPG signature (if present) will be attached as 'signature.asc'. +For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy + +My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/f1cde826-0506-4b4a-92ab-8499e953fa49/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/f1cde826-0506-4b4a-92ab-8499e953fa49/values new file mode 100644 index 0000000..727c4ee --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/f1cde826-0506-4b4a-92ab-8499e953fa49/values @@ -0,0 +1,11 @@ +Alt-id: <20090716133930.GC12213@mjolnir.home.net> + + +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 16 Jul 2009 09:39:30 -0400 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/fba8de97-9c61-4a08-b3e7-d8a95d6efe54/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/fba8de97-9c61-4a08-b3e7-d8a95d6efe54/body new file mode 100644 index 0000000..167cfe5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/fba8de97-9c61-4a08-b3e7-d8a95d6efe54/body @@ -0,0 +1,10 @@ +Hi Trevor, + + > I finally did something towards a useful interactive email + > interface ;). + +Wow, nice! That'll be really useful. + +- Chris. +-- +Chris Ball <cjb@laptop.org> diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/fba8de97-9c61-4a08-b3e7-d8a95d6efe54/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/fba8de97-9c61-4a08-b3e7-d8a95d6efe54/values new file mode 100644 index 0000000..3cac90e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/fba8de97-9c61-4a08-b3e7-d8a95d6efe54/values @@ -0,0 +1,14 @@ +Alt-id: <m3fxct5vl6.fsf@pullcord.laptop.org> + + +Author: Chris Ball <cjb@laptop.org> + + +Content-type: text/plain + + +Date: Sat, 18 Jul 2009 21:07:33 -0400 + + +In-reply-to: f1cde826-0506-4b4a-92ab-8499e953fa49 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/values new file mode 100644 index 0000000..da43639 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/values @@ -0,0 +1,20 @@ +assigned: W. Trevor King <wking@drexel.edu> + + +creator: W. Trevor King <wking@drexel.edu> + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: wishlist + + +status: fixed + + +summary: Interactive email interface + + +time: Tue, 21 Jul 2009 18:53:50 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/bcd6e5d4-8d03-43ad-a10d-17619735d077/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/bcd6e5d4-8d03-43ad-a10d-17619735d077/body new file mode 100644 index 0000000..c3b0f20 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/bcd6e5d4-8d03-43ad-a10d-17619735d077/body @@ -0,0 +1,5 @@ +There is definitely a difference between the person who reports a bug and the +person who enters it in the system. For example, you are reporting bugs to me, +and I am entering them in the Bugs Everywhere bug list. + +Perhaps there should be two fields. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/bcd6e5d4-8d03-43ad-a10d-17619735d077/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/bcd6e5d4-8d03-43ad-a10d-17619735d077/values new file mode 100644 index 0000000..ae0ca8f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/bcd6e5d4-8d03-43ad-a10d-17619735d077/values @@ -0,0 +1,11 @@ +Author: abentley + + +Content-type: text/plain + + +Date: Thu, 14 Sep 2006 18:05:48 +0000 + + +In-reply-to: e5decfc6-050b-4283-8776-977bf85b2c99 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/e5decfc6-050b-4283-8776-977bf85b2c99/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/e5decfc6-050b-4283-8776-977bf85b2c99/body new file mode 100644 index 0000000..23cb999 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/e5decfc6-050b-4283-8776-977bf85b2c99/body @@ -0,0 +1,3 @@ +Jens Mueller: +Referring to the fields describing a bug, I suggest the following: +The field 'Creator' should be named 'Reporter' (minor issue). diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/e5decfc6-050b-4283-8776-977bf85b2c99/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/e5decfc6-050b-4283-8776-977bf85b2c99/values new file mode 100644 index 0000000..72d84b7 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/e5decfc6-050b-4283-8776-977bf85b2c99/values @@ -0,0 +1,8 @@ +Author: abentley + + +Content-type: text/plain + + +Date: Thu, 14 Sep 2006 18:03:41 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/values new file mode 100644 index 0000000..7b5e6e6 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: Add a reporter field + + +time: Thu, 14 Sep 2006 16:47:57 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e30e2b6b-acc9-4b93-88c6-b63b6e30b593/comments/2cd562f5-fcb9-4cc5-bf8c-ad5c9d960761/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e30e2b6b-acc9-4b93-88c6-b63b6e30b593/comments/2cd562f5-fcb9-4cc5-bf8c-ad5c9d960761/body new file mode 100644 index 0000000..30286d3 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e30e2b6b-acc9-4b93-88c6-b63b6e30b593/comments/2cd562f5-fcb9-4cc5-bf8c-ad5c9d960761/body @@ -0,0 +1,15 @@ +Before Bugs Everywhere Directory v1.4 we kept + "encoding" + "vcs_name" +and other bugdir-wide configuration options in ./be/settings + +Now we don't store them anymore, but we should keep some. For +example, the encoding setting is useful when running `be html` in a +cron job. The settings are repository wide, so they should _still_ go +in ./be/settings (since there may, eventually, be several bugdirs in a +repo), but who's job is it to read that file? + +The user interface takes care of encoding, but the storage object +would be checking for a bug repository and reading the settings file. +How/when does it notify the UI? + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e30e2b6b-acc9-4b93-88c6-b63b6e30b593/comments/2cd562f5-fcb9-4cc5-bf8c-ad5c9d960761/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e30e2b6b-acc9-4b93-88c6-b63b6e30b593/comments/2cd562f5-fcb9-4cc5-bf8c-ad5c9d960761/values new file mode 100644 index 0000000..a4a84af --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e30e2b6b-acc9-4b93-88c6-b63b6e30b593/comments/2cd562f5-fcb9-4cc5-bf8c-ad5c9d960761/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 01 Feb 2010 14:34:10 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e30e2b6b-acc9-4b93-88c6-b63b6e30b593/comments/68ec74b9-d2c7-421f-ac70-602b43bbd263/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e30e2b6b-acc9-4b93-88c6-b63b6e30b593/comments/68ec74b9-d2c7-421f-ac70-602b43bbd263/body new file mode 100644 index 0000000..fafa132 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e30e2b6b-acc9-4b93-88c6-b63b6e30b593/comments/68ec74b9-d2c7-421f-ac70-602b43bbd263/body @@ -0,0 +1,5 @@ +On the other hand, since encoding decisions seem to be locale-driven, +so you can just setup the appropriate locale environmental variables +in your cron job: + export LANG=en_US.utf8 +and that should do it... diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e30e2b6b-acc9-4b93-88c6-b63b6e30b593/comments/68ec74b9-d2c7-421f-ac70-602b43bbd263/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e30e2b6b-acc9-4b93-88c6-b63b6e30b593/comments/68ec74b9-d2c7-421f-ac70-602b43bbd263/values new file mode 100644 index 0000000..4bb296a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e30e2b6b-acc9-4b93-88c6-b63b6e30b593/comments/68ec74b9-d2c7-421f-ac70-602b43bbd263/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Mon, 01 Feb 2010 15:35:57 +0000 + + +In-reply-to: 2cd562f5-fcb9-4cc5-bf8c-ad5c9d960761 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e30e2b6b-acc9-4b93-88c6-b63b6e30b593/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e30e2b6b-acc9-4b93-88c6-b63b6e30b593/values new file mode 100644 index 0000000..4d48446 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e30e2b6b-acc9-4b93-88c6-b63b6e30b593/values @@ -0,0 +1,17 @@ +creator: W. Trevor King <wking@drexel.edu> + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: open + + +summary: Where should the vcs-name and encoding configuration options live? + + +time: Mon, 01 Feb 2010 14:28:13 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/body new file mode 100644 index 0000000..0598d70 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/body @@ -0,0 +1,8 @@ +<type 'unicode'> <body>�</body> +Traceback (most recent call last): + File "<string>", line 1, in <module> + File "/usr/lib/python2.5/xml/etree/ElementTree.py", line 963, in XML + parser.feed(text) + File "/usr/lib/python2.5/xml/etree/ElementTree.py", line 1245, in feed + self._parser.Parse(data, 0) +UnicodeEncodeError: 'ascii' codec can't encode character u'\u1234' in position 6: ordinal not in range(128) diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/values new file mode 100644 index 0000000..8a5060e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 12 Jul 2009 11:34:22 +0000 + + +In-reply-to: faa686bf-c0eb-48bf-8a0b-d9a2e02bd132 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/body new file mode 100644 index 0000000..397d4b6 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/body @@ -0,0 +1 @@ +It looks like etree wants a byte string, not unicode input diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/values new file mode 100644 index 0000000..55642ec --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 12 Jul 2009 11:42:16 +0000 + + +In-reply-to: faa686bf-c0eb-48bf-8a0b-d9a2e02bd132 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/body new file mode 100644 index 0000000..ce2bb8d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/body @@ -0,0 +1,5 @@ +For example, this works: + +python -c 'from xml.etree import ElementTree; a=u"<body>\u1234</body>"; print type(a), a; b=ElementTree.XML(a.encode("unicode_escape")); print type(b.text), unicode(b.text).decode("unicode_escape");' + +Ugly though :p. Ah well. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/values new file mode 100644 index 0000000..705ce8d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 12 Jul 2009 11:46:57 +0000 + + +In-reply-to: 520a9829-8d90-43ce-be64-868b8321e5b0 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/body new file mode 100644 index 0000000..89a8f8d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/body @@ -0,0 +1 @@ +That's with Python 2.5.2 and ElementTree "2326 2005-03-17 07:45:21Z fredrik" diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/values new file mode 100644 index 0000000..3a0c6ff --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 12 Jul 2009 11:37:55 +0000 + + +In-reply-to: 07fc448f-c42e-4846-929a-8924de485766 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/body new file mode 100644 index 0000000..57e050d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/body @@ -0,0 +1,5 @@ +Isolated problem to: + +python -c 'from xml.etree import ElementTree; a=u"<body>\u1234</body>"; print type(a), a; b=ElementTree.XML(a);' + +Output attached below diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/values new file mode 100644 index 0000000..8591aa5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 12 Jul 2009 11:31:13 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/values new file mode 100644 index 0000000..4bc81f5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/values @@ -0,0 +1,17 @@ +creator: W. Trevor King <wking@drexel.edu> + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: fixed + + +summary: utf8 problems in xml parsing + + +time: Sat, 11 Jul 2009 15:48:32 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ecc91b94-7f3f-44a7-af58-03191d327a7f/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ecc91b94-7f3f-44a7-af58-03191d327a7f/values new file mode 100644 index 0000000..a82beb8 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ecc91b94-7f3f-44a7-af58-03191d327a7f/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: fixed + + +summary: no tests for missing $EDITOR + + +time: Tue, 17 May 2005 13:27:33 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/9525e3f3-a044-4fa9-b311-56336267b8b5/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/9525e3f3-a044-4fa9-b311-56336267b8b5/body new file mode 100644 index 0000000..ae7a57f --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/9525e3f3-a044-4fa9-b311-56336267b8b5/body @@ -0,0 +1,21 @@ +> I think a good solution would run along the lines of the currently +> commented out code in duplicate_bugdir(), where a +> VersionedStorage.changed_since(revision) +> call would give you a list of changed files. diff could work off of +> that directly, without the need to generate a whole duplicate bugdir. + +This is definately the way to go. Rough approach for the VCS family: + +1) Parse `bzr diff` or such to get a list of new,changed,moved,removed + paths. +2) Convert those paths to ids. +3) Return a list of ids to duplicate_bugdir(). +4) Provide Storage.parent(id, revision), so duplicate_bugdir() could + figure out what type of id we were dealing with (bugdir, bug, + comment, other?), and construct the appropriate difference tree. + +There could be a DupBugDir class which stored that diff tree and a +link to the current bugdir, which would make diffs much easier (work +already done, just copy the diff tree), and provide faster access to +unchanged files (just use the current version). + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/9525e3f3-a044-4fa9-b311-56336267b8b5/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/9525e3f3-a044-4fa9-b311-56336267b8b5/values new file mode 100644 index 0000000..3dfe992 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/9525e3f3-a044-4fa9-b311-56336267b8b5/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 03 Jan 2010 12:25:03 +0000 + + +In-reply-to: 9c4b8921-7b43-4bb6-b650-34144b414dc0 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/9c4b8921-7b43-4bb6-b650-34144b414dc0/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/9c4b8921-7b43-4bb6-b650-34144b414dc0/body new file mode 100644 index 0000000..e43a951 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/9c4b8921-7b43-4bb6-b650-34144b414dc0/body @@ -0,0 +1,23 @@ +Ok, time to fix the issue I mentioned in this commit message: + +revno: 473.1.63 +revision-id: wking@drexel.edu-20091215114420-sbdnvm5jlx0ampbg + +... +duplicate_bugdir() works, but for the vcs backends, it could require +shelling out for _every_ file read. This could, and probably will, be +horribly slow. Still it works ;). + +I'm not sure what a better implementation would be. The old +implementation checked out the entire earlier state into a temporary +directory + pros: single shell out, simple upgrade implementation + cons: wouldn't work well for HTTP backens + +I think a good solution would run along the lines of the currently +commented out code in duplicate_bugdir(), where a + VersionedStorage.changed_since(revision) +call would give you a list of changed files. diff could work off of +that directly, without the need to generate a whole duplicate bugdir. +I'm stuck on how to handle upgrades though... +... diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/9c4b8921-7b43-4bb6-b650-34144b414dc0/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/9c4b8921-7b43-4bb6-b650-34144b414dc0/values new file mode 100644 index 0000000..a02a32b --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/9c4b8921-7b43-4bb6-b650-34144b414dc0/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 02 Jan 2010 22:58:31 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/c664b7be-ded5-42dd-a16a-82b2bdb52e36/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/c664b7be-ded5-42dd-a16a-82b2bdb52e36/body new file mode 100644 index 0000000..b1b9b1a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/c664b7be-ded5-42dd-a16a-82b2bdb52e36/body @@ -0,0 +1,7 @@ +> I'm stuck on how to handle upgrades though... + +I've satisfied myself with the solution mentioned in #bea86499-824e-4e77-b085-2d581fa9ccab/1100c966-9671-4bc6-8b68-6d408a910da1/bd1207ef-f97e-4078-8c5d-046072012082#, +namely, upgrading on disk the way we've always done, and not +supporting on-the-fly upgrading at all. This isn't important for this +bug, but I didn't want to just ignore that part of the commit message. + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/c664b7be-ded5-42dd-a16a-82b2bdb52e36/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/c664b7be-ded5-42dd-a16a-82b2bdb52e36/values new file mode 100644 index 0000000..1d01491 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/comments/c664b7be-ded5-42dd-a16a-82b2bdb52e36/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 02 Jan 2010 23:04:01 +0000 + + +In-reply-to: 9c4b8921-7b43-4bb6-b650-34144b414dc0 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/values new file mode 100644 index 0000000..7b2813d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ed5eac05-80ed-411d-88a4-d2261b879713/values @@ -0,0 +1,14 @@ +creator: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: fixed + + +summary: Slow and ugly diff implementation + + +time: Sat, 02 Jan 2010 22:56:08 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ee681951-f254-43d3-a53a-1b36ae415d5c/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ee681951-f254-43d3-a53a-1b36ae415d5c/values new file mode 100644 index 0000000..2c8543e --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/ee681951-f254-43d3-a53a-1b36ae415d5c/values @@ -0,0 +1,15 @@ +creator: abentley + + +extra_strings: +- BLOCKS:4fc71206-4285-417f-8a3c-ed6fb31bbbda + + +severity: minor + + +status: closed + + +summary: Support rcs configuration + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f51dc5a7-37b7-4ce1-859a-b7cb58be6494/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f51dc5a7-37b7-4ce1-859a-b7cb58be6494/values new file mode 100644 index 0000000..bb6b222 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f51dc5a7-37b7-4ce1-859a-b7cb58be6494/values @@ -0,0 +1,15 @@ +creator: Aaron Bentley + + +extra_strings: +- BLOCKS:47c8fd5f-1f5a-4048-bef7-bb4c9a37c411 + + +severity: fatal + + +status: fixed + + +summary: Can't create bugs + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f5c06914-dc64-4658-8ec7-32a026a53f55/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f5c06914-dc64-4658-8ec7-32a026a53f55/values new file mode 100644 index 0000000..3c7b8d1 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f5c06914-dc64-4658-8ec7-32a026a53f55/values @@ -0,0 +1,15 @@ +creator: abentley + + +extra_strings: +- BLOCKS:bd0ebb56-fb46-45bc-af08-1e4a94e8ef3c + + +severity: minor + + +status: fixed + + +summary: Implement bug tree diff + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f65b680b-4309-43a2-ae2d-e65811c9d107/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f65b680b-4309-43a2-ae2d-e65811c9d107/values new file mode 100644 index 0000000..a2b042c --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f65b680b-4309-43a2-ae2d-e65811c9d107/values @@ -0,0 +1,11 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: friendly name is created, but not used + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f70dd5df-805b-49f3-a9ce-12e0fae63365/comments/24903c62-f441-496e-9dcf-17e7a581df33/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f70dd5df-805b-49f3-a9ce-12e0fae63365/comments/24903c62-f441-496e-9dcf-17e7a581df33/body new file mode 100644 index 0000000..dd40bfa --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f70dd5df-805b-49f3-a9ce-12e0fae63365/comments/24903c62-f441-496e-9dcf-17e7a581df33/body @@ -0,0 +1 @@ +Aaron said this was closeable in Nov. 24th email to the BE list. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f70dd5df-805b-49f3-a9ce-12e0fae63365/comments/24903c62-f441-496e-9dcf-17e7a581df33/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f70dd5df-805b-49f3-a9ce-12e0fae63365/comments/24903c62-f441-496e-9dcf-17e7a581df33/values new file mode 100644 index 0000000..9825ae8 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f70dd5df-805b-49f3-a9ce-12e0fae63365/comments/24903c62-f441-496e-9dcf-17e7a581df33/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Thu, 04 Dec 2008 17:20:20 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f70dd5df-805b-49f3-a9ce-12e0fae63365/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f70dd5df-805b-49f3-a9ce-12e0fae63365/values new file mode 100644 index 0000000..dde51b9 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f70dd5df-805b-49f3-a9ce-12e0fae63365/values @@ -0,0 +1,14 @@ +creator: abentley + + +severity: minor + + +status: closed + + +summary: Allow different sorts + + +time: Wed, 25 Jan 2006 15:43:19 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f77fc673-c852-4c81-bfa2-1d59de2661c8/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f77fc673-c852-4c81-bfa2-1d59de2661c8/values new file mode 100644 index 0000000..72c2839 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f77fc673-c852-4c81-bfa2-1d59de2661c8/values @@ -0,0 +1,17 @@ +creator: Gianluca Montecchi <gian@grys.it> + + +reporter: Gianluca Montecchi <gian@grys.it> + + +severity: minor + + +status: fixed + + +summary: Comment should be threaded in the "be html" output + + +time: Tue, 21 Jul 2009 21:39:52 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/body new file mode 100644 index 0000000..02bbe3a --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/body @@ -0,0 +1,5 @@ +Wrote/borrowed libbe/encoding.py. +Now the following works: + +python -c 'import libbe.encoding as e; print e.get_encoding(); e.set_IO_stream_encodings(e.get_encoding()) ;print u"\u2019"' | cat + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/values new file mode 100644 index 0000000..1e12a53 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 25 Nov 2008 19:41:02 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/body new file mode 100644 index 0000000..d97791d --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/body @@ -0,0 +1,14 @@ +$ be show 31cd490d-a1c2-4ab3-8284-d80395e34dd2 + +works as expected, but + +$ be show 31cd490d-a1c2-4ab3-8284-d80395e34dd2 | grep something +Traceback (most recent call last): + File "/home/wking/bin/be", line 30, in <module> + sys.exit(cmdutil.execute(sys.argv[1], sys.argv[2:])) + File "/home/wking/src/fun/be-bugfix/libbe/cmdutil.py", line 57, in execute + File "/home/wking/src/fun/be/be.wtk/becommands/show.py", line 44, in execute + print bug.string(show_comments=True) +UnicodeEncodeError: 'ascii' codec can't encode character u'\u2019' in position 2100: ordinal not in range(128) + +By the way, u2019 is a fancy apostrophe. diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/values new file mode 100644 index 0000000..95751fd --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 25 Nov 2008 02:36:16 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/body new file mode 100644 index 0000000..7bb09ff --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/body @@ -0,0 +1,9 @@ +Solution here +http://www.amk.ca/python/howto/unicode + +You need to encode before printing. + +This is unfortunate, because we're currently very glib about just +printing info to the terminal. This makes it much more important to +have a single bugdir-wide encoding specification... + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/values new file mode 100644 index 0000000..1e4f9c5 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Tue, 25 Nov 2008 03:02:59 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/body new file mode 100644 index 0000000..b441da9 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/body @@ -0,0 +1 @@ +Test unicode �quotes� diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/values new file mode 100644 index 0000000..86cfb90 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/values @@ -0,0 +1,8 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sat, 11 Jul 2009 18:28:57 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/values new file mode 100644 index 0000000..93ad759 --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/values @@ -0,0 +1,14 @@ +creator: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: fixed + + +summary: UTF-8 encoding trouble with pipes in becommands/show + + +time: Tue, 25 Nov 2008 02:30:35 +0000 + diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/settings b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/settings new file mode 100644 index 0000000..8ecc0cd --- /dev/null +++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/settings @@ -0,0 +1,14 @@ +extra_strings: +- "SUBSCRIBE:W. Trevor King <wking@drexel.edu>\tall\t*" + + +inactive_status: +- - closed + - The bug is no longer relevant. +- - fixed + - The bug should no longer occur. +- - wontfix + - It's not a bug, it's a feature. +- - disabled + - Unknown meaning. For backwards compatibility with old BE bugs. + diff --git a/.be/version b/.be/version index 990837e..e7aade4 100644 --- a/.be/version +++ b/.be/version @@ -1 +1 @@ -Bugs Everywhere Tree 1 0 +Bugs Everywhere Directory v1.4 diff --git a/.bzrignore b/.bzrignore new file mode 100644 index 0000000..7f19077 --- /dev/null +++ b/.bzrignore @@ -0,0 +1,12 @@ +./build +libbe/_version.py +./doc/*.1 +*.pyc +*.sw[pon] +fte.dsk +*~ +./.shelf +Bugs-Everywhere-Web/devdata.sqlite +Bugs-Everywhere-Web/beweb/config.py +Bugs-Everywhere-Web/beweb/database.sqlite +Bugs-Everywhere-Web/beweb/catwalk-session @@ -0,0 +1,15 @@ +Bugs Everywhere was written by: +Aaron Bentley +Alex Miller +Alexander Belchenko +Ben Finney +Chris Ball +Gianluca Montecchi +James Rowe +Jelmer Vernooij +Marien Zwart +Moritz Barsnick +Oleg Romanyshyn +Thomas Gerigk +Thomas Habets +W. Trevor King @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e9e1748 --- /dev/null +++ b/Makefile @@ -0,0 +1,81 @@ +#! /usr/bin/make -f +# :vim: filetype=make : -*- makefile; coding: utf-8; -*- + +# Makefile +# Part of Bugs Everywhere, a distributed bug tracking system. +# +# Copyright (C) 2008-2010 Ben Finney <benf@cybersource.com.au> +# Chris Ball <cjb@laptop.org> +# Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +SHELL = /bin/bash +RM = rm +#PATH = /usr/bin:/bin # must include sphinx-build for 'sphinx' target. + +#PREFIX = /usr/local +PREFIX = ${HOME} +INSTALL_OPTIONS = "--prefix=${PREFIX}" + +# Directories with semantic meaning +DOC_DIR := doc +MAN_DIR := ${DOC_DIR}/man + +MANPAGES = be.1 +GENERATED_FILES := build libbe/_version.py + +MANPAGE_FILES = $(patsubst %,${MAN_DIR}/%,${MANPAGES}) +GENERATED_FILES += ${MANPAGE_FILES} + + +.PHONY: all +all: build + + +.PHONY: build +build: libbe/_version.py + python setup.py build + +.PHONY: doc +doc: sphinx man + +.PHONY: install +install: build doc + python setup.py install ${INSTALL_OPTIONS} + +test: build + python test.py + +.PHONY: clean +clean: + $(RM) -rf ${GENERATED_FILES} + $(MAKE) -C ${DOC_DIR} clean + + +.PHONY: libbe/_version.py +libbe/_version.py: + bzr version-info --format python > $@ + +.PHONY: man +man: ${MANPAGE_FILES} + +%.1: %.1.sgml + docbook-to-man $< > $@ + +.PHONY: sphinx +sphinx: + $(MAKE) -C ${DOC_DIR} html @@ -0,0 +1,152 @@ +February 20, 2010 + * `be html` uses truncated IDs in comment and bug URLs and anchors. + +January 27, 2010 + * `be html` links (<a href="...) #-delimited references in text/* + comment bodies. + +January 25, 2010 + * Added --ssl to `be serve` using cherrypy.wsgiserver. + +January 23, 2010 + * Added 'Created comment with ID .../.../...' output to `be comment`. + * Added --important and --mine to `be list`. + +January 20, 2010 + * Renamed 'be-mbox-to-xml' -> 'be-mail-to-xml' and added support for + several mailbox formats. + +January 3, 2010 + * Changed `be list --uuids` -> `be list --ids` + Instead of UUIDs, it now outputs user ids: BUGDIR/BUG + +January 1, 2010 + * Added HTTP storage backend and server + Serve a local repo on http://localhost:8000 + be --repo REPO serve + Then connect from other be calls, for example + be --repo http://localhost:8000 list + +December 31, 2009 + * New bugdir/bug/comment ID format replaces old bug:comment format. + * Deprecated support for `be diff` on Arch and Darcs <= 2.3.1. A new + backend abstraction (Storage) makes the former implementation + ungainly. + * Improved command completion. + * Removed commands close, open, email_bugs, + * Flipped some arguments + `be assign BUG-ID [ASSIGNEE]` -> `be status ASSIGNED BUG-ID ...` + `be severity BUG-ID SEVERITY` -> `be severity SEVERITY BUG-ID ...` + `be status BUG-ID STATUS` -> `be status STATUS BUG-ID ...` + +December 7, 2009 + * added --paginate and --no-pager to be. + * be --dir DIR COMMAND now roots the bugdir in DIR _without_ changing + directories. + * `be init --root DIR` should now be `be --dir DIR init`. + +December 5, 2009 + * targets are now a special type of bug (severity 'target'), so you + can do all the things you do with normal bugs to them as well + (e.g. comment on them, link them into dependency trees, etc.) + * new command `be due` to get/set bug due dates. + * changes to `be diff` + * exits with an error if required revision control is not possible. + Previously it printed a message, but exitted with status 0. + * removed options --new, --removed, --modified, --all + * added options --uuids, --subscribe + Replace: + '--new' with '--uuids --subscribe DIR:new' + '--removed' with '--uuids --subscribe DIR:rem' + '--modified' with '--uuids --subscribe DIR:mod' + '--all' with '--uuids' + * changes to `be depend` + * added options --status, --severity + * changes to `be list` + * added blacklist capability to --status, --severity, --assigned + * removed options --target, --cur-target + Replace: + 'be list --target TARGET' with + 'be depend --status -closed,fixed,wontfix --severity -target \ + $(be target --resolve TARGET)' + 'be list --cur-target' with + 'be depend --status -closed,fixed,wontfix --severity -target \ + $(be target --resolve)' + * changes to `be target` + * added option --resolve + * removed option --list + Replace: + 'be target --list' with 'be list --status all --severity target' + * assorted cleanups and bugfixes + +December 4, 2009 + * new commands: + email-bugs + * broke `be comment --xml` out and extended into `be import-xml` + * added --dir option to `be diff' + * new XML format <be-xml> + * interfaces/email/interactive: + * added support for [be-bug:xml] interface + * improved security with restrict_file_access + * assorted cleanups, bugfixes, and optimizations + +November 17, 2009 + * new becommands: + commit + depend + html + merge + remove + status + subscribe + tag + * renamed becommands: + set_root => init + * removed becommands: + inprogress + upgrade + * new interfaces: + email: + interactive + catmutt + xml: + be-mbox-to-xml + be-xml-to-mbox + * deprecated interfaces: + gui: + beg + wxbe + web: + Bugs-Everywhere-Web + * lots of bugfixes and cleanups, see `be diff 200` for details. + +April 10, 2006 + * Updated BeWeb to TurboGear 0.9 + +April 6, 2006 + * Better diagnostics from Marien Zwart + * Fixed installation from Marien Zwart + * Support ReST comments + +April 3, 2006 + * Handle replying to comments + * Better help handling (Thomas Gerigk) + +March 31, 2006 + * Changes to comments are shown in bzr diff + +March 3, 2006 + * Better bzr compatibility + * Auto-commit support + +Feb 3, 2006 + * BeWeb can merge, commit, etc. + +Jan 30, 2006 + * Creator support (Alexander Belchenko) + +Jan 26, 2006 + * Unicode support + +December 3, 2005 +* Added new "beweb" web interface @@ -1,20 +1,71 @@ --*- markdown -*- +Bugs Everywhere +=============== -Cherry Flavored Bugs Everywhere -=============================== +This is Bugs Everywhere (BE), a bugtracker built on distributed version +control. It works with Arch, Bazaar, Darcs, Git, and Mercurial at the +moment, but is easily extensible. It can also function with no VCS at +all. -CFBE is a quick web interface to [BugsEverywhere](http://bugseverywhere.org/). It's still very much a work-in-progress. +The idea is to package the bug information with the source code, so that +bugs can be marked "fixed" in the branches that fix them. So, instead of +numbers, bugs have globally unique ids. -Installing ----------- -I intend to streamline the installation once I'm satisfied with the interface itself. For now, the install process goes something like this: +Getting BE +========== -* Install [CherryPy](http://cherrypy.org/) if you don't have it. -* Install [Jinja2](http://jinja.pocoo.org/2/) if you don't have it. -* Install [BugsEverywhere](http://bugseverywhere.org/) if you don't have it. -* Download a zip/tar of CFBE (or hg clone) from the [Mercurial repository](http://bitbucket.org/sjl/cherryflavoredbugseverywhere/). -* Unzip (if you grabbed a zip) and put the folder in your Python site-packages directory (or put it anywhere and symlink it to site-packages). -* Symlink `site-packages/cherryflavoredbugseverywhere/cfbe.py` to `/usr/local/bin/cfbe` -* Use `cfbe [project_root]` to start up the web interface for that project. -* Visit http://localhost:8080/ in a browser. +BE is available as a bzr repository:: + + $ bzr branch http://bzr.bugseverywhere.org/be + +See the homepage_ for details. If you do branch the bzr repo, you'll +need to run:: + + $ make + +to build some auto-generated files (e.g. ``libbe/_version.py``), and:: + + $ make install + +to install BE. By default BE will install into your home directory, +but you can tweak the ``PREFIX`` variable in ``Makefile`` to install +to another location. + +.. _homepage: http://bugseverywhere.org/ + + +Getting started +=============== + +To get started, you must set the bugtracker root. Typically, you will want to +set the bug root to your project root, so that Bugs Everywhere works in any +part of your project tree.:: + + $ be init -r $PROJECT_ROOT + +To create bugs, use ``be new $DESCRIPTION``. To comment on bugs, you +can can use ``be comment $BUG_ID``. To close a bug, use +``be close $BUG_ID`` or ``be status $BUG_ID fixed``. For more +commands, see ``be help``. You can also look at the usage examples in +``test_usage.sh``. + + +Documentation +============= + +If ``be help`` isn't scratching your itch, the full documentation is +available in the doc directory as reStructuredText_ . You can build +the full documentation with Sphinx_ , convert single files with +docutils_ , or browse through the doc directory by hand. +doc/index.txt is a good place to start. If you do use Sphinx, you'll +need to install numpydoc_ for automatically generating API +documentation. See the ``NumPy/SciPy documentation guide``_ for an +introduction to the syntax. + +.. _reStructuredText: + http://docutils.sourceforge.net/docs/user/rst/quickref.html +.. _Sphinx: http://sphinx.pocoo.org/ +.. _docutils: http://docutils.sourceforge.net/ +.. _numpydoc: http://pypi.python.org/pypi/numpydoc +.. _NumPy/SciPy documentation guide: + http://projects.scipy.org/numpy/wiki/CodingStyleGuidelines @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import sys +import libbe.ui.command_line + +sys.exit(libbe.ui.command_line.main()) diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..6d7bbc5 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,94 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = .build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest + +help: + @echo "Please use \`make <target>' where <target> is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " libbe to autogenerate files for all libbe modules" + +clean: + -rm -rf $(BUILDDIR) libbe + +html: libbe + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: libbe + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +pickle: libbe + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: libbe + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: libbe + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: libbe + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/bugs-everywhere.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/bugs-everywhere.qhc" + +latex: libbe + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: libbe + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: libbe + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: libbe + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY : libbe +libbe : + python generate-libbe-txt.py diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..1090a28 --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- +# +# bugs-everywhere documentation build configuration file, created by +# sphinx-quickstart on Fri Feb 5 20:02:21 2010. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.append(os.path.abspath('.')) +sys.path.insert(0, os.path.abspath('..')) + +import libbe.version + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.coverage', + 'numpydoc'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['.templates'] + +# The suffix of source filenames. +source_suffix = '.txt' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'bugs-everywhere' +copyright = u'2010, W. Trevor King' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = libbe.version.version() +# The full version, including alpha/beta/rc tags. +release = libbe.version.version() + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['.build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['.static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'bugs-everywheredoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'bugs-everywhere.tex', u'bugs-everywhere Documentation', + u'W. Trevor King', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/doc/distributed_bugtracking.txt b/doc/distributed_bugtracking.txt new file mode 100644 index 0000000..5ca42a6 --- /dev/null +++ b/doc/distributed_bugtracking.txt @@ -0,0 +1,57 @@ +*********************** +Distributed Bugtracking +*********************** + +Usage Cases +=========== + +Case 1: Tracking the status of bugs in remote repo branches +----------------------------------------------------------- + +See the discussion in +#bea86499-824e-4e77-b085-2d581fa9ccab/12c986be-d19a-4b8b-b1b5-68248ff4d331#. +Here, it doesn't matter whether the remote repository is a branch of +the local repository, or a completely separate project +(e.g. upstream, ...). So long as the remote project provides access +via some REPO format, you can use:: + + $ be --repo REPO ... + +to run your query, or:: + + $ be diff REPO + +to see the changes between the local and remote repositories. + + +Case 2: Importing bugs from other repositories +---------------------------------------------- + +Case 2.1: If the remote repository is a branch of the local repository:: + + $ <VCS> merge <REPO> + +Case 2.2: If the remote repository is not a branch of the local repository +(Hypothetical command):: + + $ be import <REPO> <ID> + + +Notes +===== + +Providing public repositories +----------------------------- + +e.g. for non-dev users. These are just branches that expose a public +interface (HTML, email, ...). Merge and query like any other +development branch. + + +Managing permissions +-------------------- + +Many bugtrackers implement some sort of permissions system, and they +are certainly required for a central system with diverse user roles. +However DVCSs also support the "pull my changes" workflow, where +permissions are irrelevant. diff --git a/doc/doc.txt b/doc/doc.txt new file mode 100644 index 0000000..e1b7a3a --- /dev/null +++ b/doc/doc.txt @@ -0,0 +1,23 @@ +**************************** +Producing this documentation +**************************** + +This documentation is written in reStructuredText_, and produced +using Sphinx_ and the numpydoc_ extension. The documentation source +should be fairly readable without processing, but you can compile the +documentation, you'll need to install Sphinx and numpydoc:: + + $ easy_install Sphinx + $ easy_install numpydoc + +.. _Sphinx: http://sphinx.pocoo.org/ +.. _numpydoc: http://pypi.python.org/pypi/numpydoc + +See the reStructuredText quick reference and the `NumPy/SciPy +documentation guide`_ for an introduction to the documentation +syntax. + +.. _reStructuredText: + http://docutils.sourceforge.net/docs/user/rst/quickref.html +.. _NumPy/SciPy documentation guide: + http://projects.scipy.org/numpy/wiki/CodingStyleGuidelines diff --git a/doc/email.txt b/doc/email.txt new file mode 120000 index 0000000..b25875f --- /dev/null +++ b/doc/email.txt @@ -0,0 +1 @@ +../interfaces/email/interactive/README
\ No newline at end of file diff --git a/doc/generate-libbe-txt.py b/doc/generate-libbe-txt.py new file mode 100644 index 0000000..35eb5c4 --- /dev/null +++ b/doc/generate-libbe-txt.py @@ -0,0 +1,52 @@ +#!/usr/bin/python +# +# Copyright + +"""Auto-generate reStructuredText of the libbe module tree for Sphinx. +""" + +import sys +import os, os.path + +sys.path.insert(0, os.path.abspath('..')) +from test import python_tree + +def title(modname): + t = ':mod:`%s`' % modname + delim = '*'*len(t) + return '\n'.join([delim, t, delim, '', '']) + +def automodule(modname): + return '\n'.join([ + '.. automodule:: %s' % modname, + ' :members:', + ' :undoc-members:', + '', '']) + +def toctree(children): + if len(children) == 0: + return '' + return '\n'.join([ + '.. toctree::', + ' :maxdepth: 2', + '', + ] + [ + ' %s.txt' % c for c in sorted(children) + ] + ['', '']) + +def make_module_txt(modname, children): + filename = os.path.join('libbe', '%s.txt' % modname) + if not os.path.exists('libbe'): + os.mkdir('libbe') + if os.path.exists(filename): + return None # don't overwrite potentially hand-written files. + f = file(filename, 'w') + f.write(title(modname)) + f.write(automodule(modname)) + f.write(toctree(children)) + f.close() + +if __name__ == '__main__': + pt = python_tree(root_path='../libbe', root_modname='libbe') + for node in pt.traverse(): + make_module_txt(node.modname, [c.modname for c in node]) diff --git a/doc/hacking.txt b/doc/hacking.txt new file mode 100644 index 0000000..5b075f9 --- /dev/null +++ b/doc/hacking.txt @@ -0,0 +1,75 @@ +********** +Hacking BE +********** + +Adding commands +=============== + +To write a plugin, you simply create a new file in the +``libbe/commands/`` directory. Take a look at one of the simpler +plugins (e.g. ``remove.py``) for an example of how that looks, and to +start getting a feel for the libbe interface. + +See ``libbe/commands/base.py`` for the definition of the important +classes ``Option``, ``Argument``, ``Command``, ``InputOutput``, +``StorageCallbacks``, and ``UserInterface`` classes. You'll be +subclassing ``Command`` for your command, but all those classes will +be important. + + +Command completion +------------------ + +BE implements a general framework to make it easy to support command +completion for arbitrary plugins. In order to support this system, +any of your completable ``Argument()`` instances (in your command's +``.options`` or ``.args``) should be initialized with some valid +completion_callback function. Some common cases are defined in +``libbe.command.util``. If you need more flexibility, see +``libbe.command.list``'s ``--sort`` option for an example of +extensions via ``libbe.command.util.Completer``, or write a custom +completion function from scratch. + + +Adding user interfaces +====================== + +Take a look at ``libbe/ui/command_line.py`` for an example. Basically +you'll need to setup a ``UserInterface`` instance for running commands. +More details to come after I write an HTML UI... + + +Testing +======= + +Run any tests in your module with:: + + be$ python test.py <python.module.name> + +for example: + + be$ python test.py libbe.command.merge + +For a definition of "any tests", see ``test.py``'s +``add_module_tests()`` function. + +Note that you will need to run ``make`` before testing a clean BE +branch to auto-generate required files like ``libbe/_version.py``. + + +Profiling +========= + +Find out which 20 calls take the most cumulative time (time of +execution + childrens' times):: + + $ python -m cProfile -o profile be [command] [args] + $ python -c "import pstats; p=pstats.Stats('profile'); p.sort_stats('cumulative').print_stats(20)" + +It's often useful to toss:: + + import sys, traceback + print >> sys.stderr, '-'*60, '\n', '\n'.join(traceback.format_stack()[-10:]) + +into expensive functions (e.g. ``libbe.util.subproc.invoke()``) if +you're not sure why they're being called. diff --git a/doc/html.txt b/doc/html.txt new file mode 100644 index 0000000..9e7f114 --- /dev/null +++ b/doc/html.txt @@ -0,0 +1,7 @@ +************** +HTML Interface +************** + +There's an interactive HTML interface in the works +(http://bitbucket.org/sjl/cherryflavoredbugseverywhere/), but it's not +ready for use as a public interface yet. diff --git a/doc/index.txt b/doc/index.txt new file mode 100644 index 0000000..30b0318 --- /dev/null +++ b/doc/index.txt @@ -0,0 +1,39 @@ +Welcome to the bugs-everywhere documentation! +============================================= + +Bugs Everywhere (BE) is a bugtracker built on distributed version +control. It works with Arch_, Bazaar_, Darcs_, Git_, and Mercurial_ +at the moment, but is easily extensible. It can also function with no +VCS at all. + +.. _Arch: http://www.gnu.org/software/gnu-arch/ +.. _Bazaar: http://bazaar.canonical.com/ +.. _Darcs: http://darcs.net/ +.. _Git: http://git-scm.com/ +.. _Mercurial: http://mercurial.selenic.com/ + +The idea is to package the bug information with the source code, so +that bugs can be marked "fixed" in the branches that fix them. + + +Contents: + +.. toctree:: + :maxdepth: 2 + + install.txt + tutorial.txt + email.txt + html.txt + distributed_bugtracking.txt + hacking.txt + spam.txt + libbe/libbe.txt + doc.txt + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/doc/install.txt b/doc/install.txt new file mode 100644 index 0000000..b1d153e --- /dev/null +++ b/doc/install.txt @@ -0,0 +1,55 @@ +************* +Installing BE +************* + +Bazaar repository +================= + +BE is available as a bzr repository:: + + $ bzr branch http://bzr.bugseverywhere.org/be + +See the homepage_ for details. If you do branch the bzr repo, you'll +need to run:: + + $ make + +to build some auto-generated files (e.g. ``libbe/_version.py``), and:: + + $ make install + +to install BE. By default BE will install into your home directory, +but you can tweak the ``PREFIX`` variable in ``Makefile`` to install +to another location. + +.. _homepage: http://bugseverywhere.org/ + + +Release tarballs +================ + +For those not interested in the cutting edge, or those who don't want +to worry about installing Bazaar, we'll post release tarballs somewhere +(once we actually make a release). After you've downloaded the release +tarball, unpack it with:: + + $ tar -xzvf be-<VERSION>.tar.gz + +And install it with::: + + $ cd be-<VERSION> + $ make install + + +Distribution packages +===================== + +Some distributions (Debian_ , Ubuntu_ , others?) package BE. If +you're running one of those distributions, you can install the package +with your regular package manager. For Debian, Ubuntu, and related +distros, that's:: + + $ apt-get install bugs-everywhere + +.. _Debian: http://packages.debian.org/sid/bugs-everywhere +.. _Ubuntu: http://packages.ubuntu.com/lucid/bugs-everywhere diff --git a/doc/man/be.1.sgml b/doc/man/be.1.sgml new file mode 100644 index 0000000..a2df21f --- /dev/null +++ b/doc/man/be.1.sgml @@ -0,0 +1,134 @@ +<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [ + +<!-- Process this file with docbook-to-man to generate an nroff manual + page: `docbook-to-man manpage.sgml > manpage.1'. You may view + the manual page with: `docbook-to-man manpage.sgml | nroff -man | + less'. A typical entry in a Makefile or Makefile.am is: + +be.1: be.1.sgml + docbook-to-man $< > $@ + + The docbook-to-man binary is found in the docbook-to-man package. + Please remember that if you create the nroff version in one of the + debian/rules file targets (such as build), you will need to include + docbook-to-man in your Build-Depends control field. + + --> + + <!ENTITY dhfirstname "<firstname>Ben</firstname>"> + <!ENTITY dhsurname "<surname>Finney</surname>"> + <!-- Please adjust the date whenever revising the manpage. --> + <!ENTITY dhdate "<date>2009-06-25</date>"> + <!-- SECTION should be 1-8, maybe w/ subsection other parameters are + allowed: see man(7), man(1). --> + <!ENTITY dhsection "<manvolnum>1</manvolnum>"> + <!ENTITY dhemail "<email>ben+debian@benfinney.id.au</email>"> + <!ENTITY dhusername "Ben Finney"> + <!ENTITY dhucpackage "<refentrytitle>BUGS-EVERYWHERE</refentrytitle>"> + <!ENTITY dhpackage "bugs-everywhere"> + <!ENTITY pkgfullname "Bugs Everywhere"> + <!ENTITY uccmdname "<refentrytitle>BE</refentrytitle>"> + <!ENTITY cmdname "be"> + + <!ENTITY debian "<productname>Debian</productname>"> + <!ENTITY gnu "<acronym>GNU</acronym>"> + <!ENTITY gpl "&gnu; <acronym>GPL</acronym>"> +]> + +<refentry> + <refentryinfo> + <address> + &dhemail; + </address> + <author> + &dhfirstname; + &dhsurname; + </author> + <copyright> + <year>2009</year> + <holder>&dhusername;</holder> + </copyright> + &dhdate; + </refentryinfo> + <refmeta> + &uccmdname; + + &dhsection; + </refmeta> + <refnamediv> + <refname>&cmdname;</refname> + + <refpurpose>distributed bug tracker</refpurpose> + </refnamediv> + <refsynopsisdiv> + <cmdsynopsis> + <command>&cmdname;</command> + <arg><replaceable>command</replaceable></arg> + <arg><replaceable>command_options ...</replaceable></arg> + <arg><replaceable>command_args ...</replaceable></arg> + </cmdsynopsis> + <cmdsynopsis> + <command>&cmdname; help</command> + </cmdsynopsis> + <cmdsynopsis> + <command>&cmdname; help</command> + <arg><replaceable>command</replaceable></arg> + </cmdsynopsis> + </refsynopsisdiv> + <refsect1> + <title>DESCRIPTION</title> + + <para>This manual page documents briefly the + <command>&cmdname;</command> command, part of the + &pkgfullname; package.</para> + + <para><command>&cmdname;</command> allows commandline interaction + with the &pkgfullname; database in a project tree.</para> + + </refsect1> + <refsect1> + <title>COMMANDS</title> + <variablelist> + <varlistentry> + <term><command>help</command> + </term> + <listitem> + <para>Print help for be and a list of all available commands. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + <refsect1> + <title>AUTHOR</title> + + <para>This manual page was written by &dhusername; <&dhemail;> for + the &debian; system (but may be used by others). Permission is + granted to copy, distribute and/or modify this document under + the terms of the &gnu; General Public License, Version 2 or any + later version published by the Free Software Foundation. + </para> + <para> + On Debian systems, the complete text of the GNU General Public + License can be found in /usr/share/common-licenses/GPL. + </para> + + </refsect1> +</refentry> + +<!-- Keep this comment at the end of the file +Local variables: +mode: sgml +sgml-omittag:t +sgml-shorttag:t +sgml-minimize-attributes:nil +sgml-always-quote-attributes:t +sgml-indent-step:2 +sgml-indent-data:t +sgml-parent-document:nil +sgml-default-dtd-file:nil +sgml-exposed-tags:nil +sgml-local-catalogs:nil +sgml-local-ecat-files:nil +End: +--> diff --git a/doc/spam.txt b/doc/spam.txt new file mode 100644 index 0000000..39e7a86 --- /dev/null +++ b/doc/spam.txt @@ -0,0 +1,60 @@ +***************** +Dealing with spam +***************** + +In the case that some spam or inappropriate comment makes its way +through you interface, you can (sometimes) remove the offending commit +``XYZ``. + + +If the offending commit is the last commit +========================================== + ++-------+----------------------------+ +| arch | | ++-------+----------------------------+ +| bzr | bzr uncommit && bzr revert | ++-------+----------------------------+ +| darcs | darcs obliterate --last=1 | ++-------+----------------------------+ +| git | git reset --hard HEAD^ | ++-------+----------------------------+ +| hg | hg rollback && hg revert | ++-------+----------------------------+ + +If the offending commit is not the last commit +============================================== + ++----------+-----------------------------------------------+ +| arch | | ++----------+-----------------------------------------------+ +| bzr [#]_ | bzr rebase -r <XYZ+1>..-1 --onto before:XYZ . | ++----------+-----------------------------------------------+ +| darcs | darcs obliterate --matches 'name XYZ' | ++----------+-----------------------------------------------+ +| git | git rebase --onto XYZ~1 XYZ | ++----------+-----------------------------------------------+ +| hg [#]_ | | ++----------+-----------------------------------------------+ + +.. [#] Requires the ```bzr-rebase`` plugin`_. Note, you have to + increment ``XYZ`` by hand for ``<XYZ+1>``, because ``bzr`` does not + support ``after:XYZ``. + +.. [#] From `Mercurial: The Definitive Guide`: + + "Mercurial also does not provide a way to make a file or + changeset completely disappear from history, because there is no + way to enforce its disappearance" + +.. _bzr-rebase plugin: http://wiki.bazaar.canonical.com/Rebase +.. _Mercurial: The Definitive Guide: + http://hgbook.red-bean.com/read/finding-and-fixing-mistakes.html#id394667 + +Warnings about changing history +=============================== + +Note that all of these *change the repo history* , so only do this on +your interface-specific repo before it interacts with any other repo. +Otherwise, you'll have to survive by cherry-picking only the good +commits. diff --git a/doc/tutorial.txt b/doc/tutorial.txt new file mode 100644 index 0000000..7932c9c --- /dev/null +++ b/doc/tutorial.txt @@ -0,0 +1,393 @@ +******** +Tutorial +******** + +Introduction +============ + +Bugs Everywhere (BE) is a bugtracker built on distributed revision +control. The idea is to package the bug information with the source +code, so that developers working on the code can make appropriate +changes to the bug repository as they go. For example, by marking a +bug as "fixed" and applying the fixing changes in the same commit. +This makes it easy to see what's been going on in a particular branch +and helps keep the bug repository in sync with the code. + +However, there are some differences compared to centralized +bugtrackers. Because bugs and comments can be created by several +users in parallel, they have globally unique IDs_ rather than numbers. +There is also a developer-friendly command-line_ interface to +compliment the user-friendly web_ and email_ interfaces. This +tutorial will focus on the command-line interface as the most +powerful, and leave the web and email interfaces to other documents. + +.. _command-line: `Command-line interface`_ +.. _web: tutorial-html.txt +.. _email: tutorial-email.txt +.. _IDs: libbe/libbe.util.id.txt + +Installation +============ + +If your distribution packages BE, it will be easiest to use their package. +For example, most Debian-based distributions support:: + + $ apt-get install bugs-everywhere + +Bugs +==== + +If you have any problems with BE, you can look for matching bugs:: + + $ be --repo http://bugseverywhere.org/bugs list + +If your bug isn't listed, please open a new bug:: + + $ be --repo http://bugseverywhere.org/bugs new 'bug' + Created bug with ID bea/abc + $ be --repo http://bugseverywhere.org/bugs comment bea/def + <editor spawned for comments> + + +Command-line interface +====================== + +Help +---- + +All of the following information elaborates on the command help text, +which is stored in the code itself, and therefore more likely to be up +to date. You can get a list of commands and topics with:: + + $ be help + +Or see specific help on ``COMMAND`` with + + $ be help COMMAND + +for example:: + + $ be help init + +will give help on the ``init`` command. + +Initialization +-------------- + +You're happily coding in your Arch_ / Bazaar_ / Darcs_ / Git_ / +Mercurial_ versioned project and you discover a bug. "Hmm, I'll need +a simple way to track these things", you think. This is where BE +comes in. One of the benefits of distributed versioning systems is +the ease of repository creation, and BE follows this trend. Just +type:: + + $ be init + Using <VCS> for revision control. + BE repository initialized. + +in your project's root directory. This will create a ``.be`` +directory containing the bug repository and notify your VCS so it will +be versioned starting with your next commit. See:: + + $ be help init + +for specific details about where the ``.be`` directory will end up +if you call it from a directory besides your project's root. + +.. _Arch: http://www.gnu.org/software/gnu-arch/ +.. _Bazaar: http://bazaar.canonical.com/ +.. _Darcs: http://darcs.net/ +.. _Git: http://git-scm.com/ +.. _Mercurial: http://mercurial.selenic.com/ + +Inside the ``.be`` directory (among other things) there will be a long +UUID_ directory. This is your bug directory. The idea is that you +could keep several bug directories in the same repository, using one +to track bugs, another to track roadmap issues, etc. See IDs_ for +details. For BE itself, the bug directory is +``bea86499-824e-4e77-b085-2d581fa9ccab``, which is why all the bug and +comment IDs in this tutorial will start with ``bea/``. + +.. _UUID: http://en.wikipedia.org/wiki/Universally_Unique_Identifier + + +Creating bugs +------------- + +Create new bugs with:: + + $ be new <SUMMARY> + +For example:: + + $ be new 'Missing demuxalizer functionality' + Created bug with ID bea/28f + +If you are entering a bug reported by another person, take advantage +of the ``--reporter`` option to give them credit:: + + $ be new --reporter 'John Doe <jdoe@example.com>' 'Missing whatsit...' + Created bug with ID bea/81a + +See ``be help new`` for more details. + +While the bug summary should include the appropriate keywords, it +should also be brief. You should probably add a comment immediately +giving a more elaborate explanation of the problem so that the +developer understands what you want and when the bug can be considered +fixed. + +Commenting on bugs +------------------ + +Bugs are like little mailing lists, and you can comment on the bug +itself or previous comments, attach files, etc. For example + + $ be comment abc/28f "Thoughts about demuxalizers..." + Created comment with ID abc/28f/97a + $ be comment abc/def/012 "Oops, I forgot to mention..." + Created comment with ID abc/28f/e88 + +Usually comments will be long enough that you'll want to compose them +in a text editor, not on the command line itself. Running ``be +comment`` without providing a ``COMMENT`` argument will try to spawn +an editor automatically (using your environment's ``VISUAL`` or +``EDITOR``, see `Guide to Unix, Environmental Variables`_). + +.. _Guide to Unix, Environmental Variables: + http://en.wikibooks.org/wiki/Guide_to_Unix/Environment_Variables + +You can also pipe the comment body in on stdin, which is especially +useful for binary attachments, etc.:: + + $ cat screenshot.png | be comment --content-type image/png bea/28f + Created comment with ID bea/28f/35d + +It's polite to insert binary attachments under comments that explain +the content and why you're attaching it, so the above should have been + + $ be comment bea/28f "Whosit dissapears when you mouse-over whatsit." + Created comment with ID bea/28f/41d + $ cat screenshot.png | be comment --content-type image/png bea/28f/41d + Created comment with ID bea/28f/35d + +For more details, see ``be help comment``. + +Showing bugs +------------ + +Ok, you understand how to enter bugs, but how do you get that +information back out? If you know the ID of the item you're +interested in (e.g. bug bea/28f), try:: + + $ be show bea/28f + ID : 28fb711c-5124-4128-88fe-a88a995fc519 + Short name : bea/28f + Severity : minor + Status : open + Assigned : + Reporter : + Creator : ... + Created : ... + Missing demuxalizer functionality + --------- Comment --------- + Name: bea/28f/97a + From: ... + Date: ... + + Thoughts about demuxalizers... + --------- Comment --------- + Name: bea/28f/e88 + From: ... + Date: ... + + Thoughts about demuxalizers... + --------- Comment --------- + Name: bea/28f/41d + From: ... + Date: ... + + Whosit dissapears when you mouse-over whatsit. + --------- Comment --------- + Name: bea/28f/35d + From: ... + Date: ... + + Content type image/png not printable. Try XML output instead + +You can also get a single comment body, which is useful for extracting +binary attachments:: + + $ be show --only-raw-body bea/28f/35d > screenshot.png + +There is also an XML output format, which can be useful for emailing +entries around, scripting BE, etc. + + $ be show --xml bea/35d + <?xml version="1.0" encoding="UTF-8" ?> + <be-xml> + ... + +Listing bugs +------------ + +If you *don't* know which bug you're interested in, you can query +the whole bug directory:: + + $ be list + bea/28f:om: Missing demuxalizer functionality + bea/81a:om: Missing whatsit... + +There are a whole slew of options for filtering the list of bugs. See +``be help list`` for details. + +Showing changes +--------------- + +Often you will want to see what's going on in another dev's branch or +remind yourself what you've been working on recently. All VCSs have +some sort of ``diff`` command that shows what's changed since revision +``XYZ``. BE has it's own command that formats the bug-repository +portion of those changes in an easy-to-understand summary format. To +compare your working tree with the last commit:: + + $ be diff + New bugs: + bea/01c:om: Need command output abstraction for flexible UIs + Modified bugs: + bea/343:om: Attach tests to bugs + Changed bug settings: + creator: None -> W. Trevor King <wking@drexel.edu> + +Compare with a previous revision ``480``:: + + $ be diff 480 + ... + +Compare your BE branch with the trunk:: + + $ be diff --repo http://bugseverywhere.org/bugs/ + +Manipulating bugs +----------------- + +There are several commands that allow to to set bug properties. They +are all fairly straightforward, so we will merely point them out here, +and refer you to ``be help COMMAND`` for more details. + +* ``assign``, Assign an individual or group to fix a bug +* ``depend``, Add/remove bug dependencies +* ``due``, Set bug due dates +* ``status``, Change a bug's status level +* ``severity``, Change a bug's severity level +* ``tag``, Tag a bug, or search bugs for tags +* ``target``, Assorted bug target manipulations and queries + +You can also remove bugs you feel are no longer useful with +``be remove``, and merge duplicate bugs with ``be merge``. + +Subscriptions +------------- + +Since BE bugs act as mini mailing lists, we provide ``be subscribe`` +as a way to manage change notification. You can subscribe to all +the changes with:: + + $ be subscribe --types all DIR + +Subscribe only to bug creaton on bugseverywhere.org with:: + + $ be subscribe --server bugseverywhere.org --types new DIR + +Subscribe to get all the details about bug ``bea/28f``:: + + $ be subscribe --types new bea/28f + +To unsubscribe, simply repeat the subscription command adding the +``--unsubscribe`` option, but be aware that it may take some time for +these changes to propogate between distributed repositories. If you +don't feel confident in your ability to filter email, it's best to +only subscribe to the repository for which you have direct write +access. + +Managing bug directories +------------------------ + +``be set`` lets you configure a bug directory. You can set + +* ``active_status`` + The allowed active bug states and their descriptions. +* ``inactive_status`` + The allowed inactive bug states and their descriptions. +* ``severities`` + The allowed bug severities and their descriptions. +* ``target`` + The current project development target (bug UUID). +* ``extra_strings`` + Space for an array of extra strings. You usually won't bother with + this directly. + +For example, to set the current target to '1.2.3':: + + $ be set target $(be target --resolve '1.2.3') + +Import XML +---------- + +For serializing bug information (e.g. to email to a mailing list), use:: + + $ be show --xml bea/28f > bug.xml + +This information can be imported into (another) bug directory via + + $ be import-xml bug.xml + +Also distributed with BE are some utilities to convert mailboxes +into BE-XML (``be-mail-to-xml``) and convert BE-XML into mbox_ +format for reading in your mail client. + +.. _mbox: http://en.wikipedia.org/wiki/Mbox + +Export HTML +----------- + +To create a static dump of your bug directory, use:: + + $ be html + +This is a fairly flexible command, see ``be help html`` for details. +It works pretty well as the browsable part of a public interface using +the email_ interface for interactive access. + +BE over HTTP +------------ + +Besides using BE to work directly with local VCS-based repositories, +you can use:: + + $ be serve + +To serve a repository over HTTP. For example:: + + $ be serve > server.log 2>&1 & + $ be --repo http://localhost:8000 list + +Of course, be careful about serving over insecure networks, since +malicous users could fill your disk with endless bugs, etc. You can +dissable write access by using the ``--read-only`` option, which would +make serving on a public network safer. + +Driving the VCS through BE +-------------------------- + +Since BE uses internal storage drivers for its various backends, it +seemed useful to provide a uniform interface to some of the common +functionality. These commands are not intended to replace the usually +much more powerful native VCS commands, but to provide an easy means +of simple VCS-agnostic scripting for BE user interfaces, etc. + +Commit +~~~~~~ + +Currently, we only expose ``be commit``, which commits all currently +pending changes. diff --git a/interfaces/email/interactive/README b/interfaces/email/interactive/README new file mode 100644 index 0000000..48bccdd --- /dev/null +++ b/interfaces/email/interactive/README @@ -0,0 +1,160 @@ +*************** +Email Interface +*************** + +Overview +======== + +The interactive email interface to Bugs Everywhere (BE) attempts to +provide a `Debian-bug-tracking-system-style`_ interface to a BE +repository. Users can mail in bug reports, comments, or control +requests, which will be committed to the served repository. +Developers can then pull the changes they approve of from the served +repository into their other repositories and push updates back onto +the served repository. + +.. _Debian-bug-tracking-system-style: http://www.debian.org/Bugs + +Architecture +============ + +In order to reduce setup costs, the entire interface can piggyback on +an existing email address, although from a security standpoint it's +probably best to create a dedicated user. Incoming email is filtered +by procmail, with matching emails being piped into ``be-handle-mail`` +for execution. + +Once ``be-handle-mail`` receives the email, the parsing method is +selected according to the subject tag that procmail used grab the +email in the first place. There are four parsing styles: + + +--------------------+----------------------------------+ + | Style | Subject | + +====================+==================================+ + | creating bugs | [be-bug:submit] new bug summary | + +--------------------+----------------------------------+ + | commenting on bugs | [be-bug:<bug-id>] commit message | + +--------------------+----------------------------------+ + | control | [be-bug] commit message | + +--------------------+----------------------------------+ + +These are analogous to ``submit@bugs.debian.org``, +``nnn@bugs.debian.org``, and ``control@bugs.debian.org`` respectively. + +Creating bugs +============= + +This interface creates a bug whose summary is given by the email's +post-tag subject. The body of the email must begin with a +pseudo-header containing at least the ``Version`` field. Anything after +the pseudo-header and before a line starting with ``--`` is, if present, +attached as the bug's first comment.:: + + From jdoe@example.com Fri Apr 18 12:00:00 2008 + From: John Doe <jdoe@example.com> + Date: Fri, 18 Apr 2008 12:00:00 +0000 + Content-Type: text/plain; charset=UTF-8 + Content-Transfer-Encoding: 8bit + Subject: [be-bug:submit] Need tests for the email interface. + + Version: XYZ + Severity: minor + + Someone should write up a series of test emails to send into + be-handle-mail so we can test changes quickly without having to + use procmail. + + -- + Goofy tagline not included. + +Available pseudo-headers are ``Version``, ``Reporter``, ``Assign``, +``Depend``, ``Severity``, ``Status``, ``Tag``, and ``Target``. + +Commenting on bugs +================== + +This interface appends a comment to the bug specified in the subject +tag. The the first non-multipart body is attached with the +appropriate content-type. In the case of ``text/plain`` contents, +anything following a line starting with ``--`` is stripped.:: + + From jdoe@example.com Fri Apr 18 12:00:00 2008 + From: John Doe <jdoe@example.com> + Date: Fri, 18 Apr 2008 12:00:00 +0000 + Content-Type: text/plain; charset=UTF-8 + Content-Transfer-Encoding: 8bit + Subject: [be-bug:XYZ] Isolated problem in baz() + + Finally tracked it down to the bar() call. Some sort of + string<->unicode conversion problem. Solution ideas? + + -- + Goofy tagline not included. + +Controlling bugs +================ + +This interface consists of a list of allowed be commands, with one +command per line. Blank lines and lines beginning with ``#`` are +ignored, as well anything following a line starting with ``--``. All +the listed commands are executed in order and their output returned. +The commands are split into arguments with the POSIX-compliant +shlex.split().:: + + From jdoe@example.com Fri Apr 18 12:00:00 2008 + From: John Doe <jdoe@example.com> + Date: Fri, 18 Apr 2008 12:00:00 +0000 + Content-Type: text/plain; charset=UTF-8 + Content-Transfer-Encoding: 8bit + Subject: [be-bug] I'll handle XYZ by release 1.2.3 + + assign XYZ "John Doe <jdoe@example.com>" + status XYZ assigned + severity XYZ critical + target XYZ 1.2.3 + + -- + Goofy tagline ignored. + +Example emails +============== + +Take a look at ``interfaces/email/interactive/examples`` for some +more examples. + +Procmail rules +============== + +The file ``_procmailrc`` as it stands is fairly appropriate for as a +dedicated user's ``~/.procmailrc``. It forwards matching mail to +``be-handle-mail``, which should be installed somewhere in the user's +path. All non-matching mail is dumped into ``/dev/null``. Everything +procmail does will be logged to ``~/be-mail/procmail.log``. + +If you're piggybacking the interface on top of an existing account, +you probably only need to add the ``be-handle-mail`` stanza to your +existing ``~/.procmailrc``, since you will still want to receive +non-bug emails. + +Note that you will probably have to add a:: + + --repo /path/to/served/repository + +option to the ``be-handle-mail`` invocation so it knows what repository to +serve. + +Multiple repositories may be served by the same email address by adding +multiple ``be-handle-mail`` stanzas, each matching a different tag, for +example the ``[be-bug`` portion of the stanza could be ``[projectX-bug``, +``[projectY-bug``, etc. If you change the base tag, be sure to add a:: + + --tag-base "projectX-bug" + +or equivalent to your ``be-handle-mail`` invocation. + +Testing +======= + +Send test emails in to ``be-handle-mail`` with something like:: + + cat examples/blank | ./be-handle-mail -o -l - -a diff --git a/interfaces/email/interactive/_procmailrc b/interfaces/email/interactive/_procmailrc new file mode 100644 index 0000000..d42c0cf --- /dev/null +++ b/interfaces/email/interactive/_procmailrc @@ -0,0 +1,22 @@ +# .procmailrc +# +# see man procmail, procmailrc, and procmailex +# +# If you already have a ~/.procmailrc file, you probably only need to +# insert the bug-email grabbing stanza in your ~/.procmailrc. +# +# This file is released to the Public Domain. + +MAILDIR=$HOME/be-mail +LOGFILE=$MAILDIR/procmail.log + +# Grab all incoming bug emails (but not replies). This rule eats +# matching emails (i.e. no further procmail processing). +:0 +* ^Subject: \[be-bug +* !^Subject:.*\[be-bug].*Re: +| be-handle-mail + +# Drop everything else +:0 +/dev/null diff --git a/interfaces/email/interactive/be-handle-mail b/interfaces/email/interactive/be-handle-mail new file mode 100755 index 0000000..c8343fc --- /dev/null +++ b/interfaces/email/interactive/be-handle-mail @@ -0,0 +1,909 @@ +#!/usr/bin/env python +# +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" +Provide and email interface to the distributed bugtracker Bugs +Everywhere. Recieves incoming email via procmail. Provides an +interface similar to the Debian Bug Tracker. There are currently +three distinct email types: submits, comments, and controls. The +email types are differentiated by tags in the email subject. See +SUBJECT_TAG* for the current values. + +Submit emails create a bug (and optionally add some intitial +comments). The post-tag subject is used as the bug summary, and the +email body is parsed for a pseudo-header. Any text after the +psuedo-header but before a possible line starting with BREAK is added +as the initial bug comment. + +Comment emails add comments to a bug. The first non-multipart portion +of the email is used as the comment body. If that portion has a +"text/plain" type, any text after and including a possible line +starting with BREAK is stripped to avoid lots of taglines cluttering +up the repository. + +Control emails preform any allowed BE commands. The first +non-multipart portion of the email is used as the comment body. If +that portion has a "text/plain" type, any text after and including a +possible line starting with BREAK is stripped. Each pre-BREAK line of +the portion should be a valid BE command, with the initial "be" +omitted, e.g. "be status XYZ fixed" --> "status XYZ fixed". + +Any changes made to the repository are commited after the email is +executed, with the email's post-tag subject as the commit message. +""" + +import codecs +import StringIO as StringIO +import email +from email.mime.multipart import MIMEMultipart +import email.utils +import os +import os.path +import re +import shlex +import sys +import time +import traceback +import types +import doctest +import unittest + +import libbe.bugdir +import libbe.bug +import libbe.comment +import libbe.diff +import libbe.command +import libbe.command.subscribe as subscribe +import libbe.storage +import libbe.ui.command_line +import libbe.util.encoding +import libbe.util.utility +import send_pgp_mime + +THIS_SERVER = u'thor.physics.drexel.edu' +THIS_ADDRESS = u'BE Bugs <wking@thor.physics.drexel.edu>' +UI = None +_THIS_DIR = os.path.abspath(os.path.dirname(__file__)) +LOGPATH = os.path.join(_THIS_DIR, u'be-handle-mail.log') +LOGFILE = None + +# Tag strings generated by generate_global_tags() +SUBJECT_TAG_BASE = u'be-bug' +SUBJECT_TAG_RESPONSE = None +SUBJECT_TAG_START = None +SUBJECT_TAG_NEW = None +SUBJECT_TAG_COMMENT = None +SUBJECT_TAG_CONTROL = None + +BREAK = u'--' +NEW_REQUIRED_PSEUDOHEADERS = [u'Version'] +NEW_OPTIONAL_PSEUDOHEADERS = [u'Reporter', u'Assign', u'Depend', u'Severity', + u'Status', u'Tag', u'Target', + u'Confirm', u'Subscribe'] +CONTROL_COMMENT = u'#' +ALLOWED_COMMANDS = [u'assign', u'comment', u'commit', u'depend', u'diff', + u'due', u'help', u'list', u'merge', u'new', u'severity', + u'show', u'status', u'subscribe', u'tag', u'target'] + +AUTOCOMMIT = True + +ENCODING = u'utf-8' +libbe.util.encoding.ENCODING = ENCODING # force default encoding + +class InvalidEmail (ValueError): + def __init__(self, msg, message): + ValueError.__init__(self, message) + self.msg = msg + def response(self): + header = self.msg.response_header + body = [u'Error processing email:\n', + self.response_body(), u''] + response_generator = \ + send_pgp_mime.PGPMimeMessageFactory(u'\n'.join(body)) + response = MIMEMultipart() + response.attach(response_generator.plain()) + response.attach(self.msg.msg) + ret = send_pgp_mime.attach_root(header, response) + return ret + def response_body(self): + err_text = [unicode(self)] + return u'\n'.join(err_text) + +class InvalidSubject (InvalidEmail): + def __init__(self, msg, message=None): + if message == None: + message = u'Invalid subject' + InvalidEmail.__init__(self, msg, message) + def response_body(self): + err_text = u'\n'.join([unicode(self), u'', + u'full subject was:', + self.msg.subject()]) + return err_text + +class InvalidPseudoHeader (InvalidEmail): + def response_body(self): + err_text = [u'Invalid pseudo-header:\n', + unicode(self)] + return u'\n'.join(err_text) + +class InvalidCommand (InvalidEmail): + def __init__(self, msg, command, message=None): + bigmessage = u'Invalid execution command "%s"' % command + if message != None: + bigmessage += u'\n%s' % message + InvalidEmail.__init__(self, msg, bigmessage) + self.command = command + +class InvalidOption (InvalidCommand): + def __init__(self, msg, option, message=None): + bigmessage = u'Invalid option "%s"' % (option) + if message != None: + bigmessage += u'\n%s' % message + InvalidCommand.__init__(self, msg, info, command, bigmessage) + self.option = option + +class NotificationFailed (Exception): + def __init__(self, msg): + bigmessage = 'Notification failed: %s' % msg + Exception.__init__(self, bigmessage) + self.short_msg = msg + +class ID (object): + """ + Sometimes you want to reference the output of a command that + hasn't been executed yet. ID is there for situations like + > a = Command(msg, "new", ["create a bug"]) + > b = Command(msg, "comment", [ID(a), "and comment on it"]) + """ + def __init__(self, command): + self.command = command + def extract_id(self): + if hasattr(self, 'cached_id'): + return self._cached_id + assert self.command.ret == 0, self.command.ret + if self.command.command.name == u'new': + regexp = re.compile(u'Created bug with ID (.*)') + else: + raise NotImplementedError, self.command.command + match = regexp.match(self.command.stdout) + assert len(match.groups()) == 1, str(match.groups()) + self._cached_id = match.group(1) + return self._cached_id + def __str__(self): + if self.command.ret != 0: + return '<id for %s>' % repr(self.command) + return '<id %s>' % self.extract_id() + +class Command (object): + """ + A libbe.command.Command handler. + + Initialize with + Command(msg, command, args=None, stdin=None) + where + msg: the Message instance prompting this command + command: name of becommand to execute, e.g. "new" + args: list of arguments to pass to the command + stdin: if non-null, a string to pipe into the command's stdin + """ + def __init__(self, msg, command, args=None, stdin=None): + self.msg = msg + if args == None: + self.args = [] + else: + self.args = args + self.command = libbe.command.get_command_class(command_name=command)() + self.command._setup_io = lambda i_enc,o_enc : None + self.ret = None + self.stdin = stdin + self.stdout = None + def __str__(self): + return '<command: %s %s>' % (self.command, ' '.join([str(s) for s in self.args])) + def normalize_args(self): + """ + Expand any ID placeholders in self.args. + """ + for i,arg in enumerate(self.args): + if isinstance(arg, ID): + self.args[i] = arg.extract_id() + def run(self): + """ + Attempt to execute the command whose info is given in the dictionary + info. Returns the exit code, stdout, and stderr produced by the + command. + """ + if self.command.name in [None, u'']: # don't accept blank commands + raise InvalidCommand(self.msg, self, 'Blank') + elif self.command.name not in ALLOWED_COMMANDS: + raise InvalidCommand(self.msg, self, 'Not allowed') + assert self.ret == None, u'running %s twice!' % unicode(self) + self.normalize_args() + UI.io.set_stdin(self.stdin) + self.ret = libbe.ui.command_line.dispatch(UI, self.command, self.args) + self.stdout = UI.io.get_stdout() + return (self.ret, self.stdout) + def response_msg(self): + if self.ret == None: self.ret = -1 + response_body = [u'Results of running: (exit code %d)' % self.ret, + u' %s %s' % (self.command.name,u' '.join(self.args))] + if self.stdout != None and len(self.stdout) > 0: + response_body.extend([u'', u'output:', u'', self.stdout]) + response_body.append(u'') # trailing endline + response_generator = \ + send_pgp_mime.PGPMimeMessageFactory(u'\n'.join(response_body)) + return response_generator.plain() + +class DiffTree (libbe.diff.DiffTree): + """ + In order to avoid tons of tiny MIMEText attachments, bug-level + nodes set .add_child_text=True (in .join()), which is propogated + on to their descendents. Instead of creating their own + attachement, each of these descendents appends his data_part to + the end of the bug-level MIMEText attachment. + + For the example tree in the libbe.diff.Diff unittests: + bugdir + bugdir/settings + bugdir/bugs + bugdir/bugs/new + bugdir/bugs/new/c <- sets .add_child_text + bugdir/bugs/rem + bugdir/bugs/rem/b <- sets .add_child_text + bugdir/bugs/mod + bugdir/bugs/mod/a <- sets .add_child_text + bugdir/bugs/mod/a/settings + bugdir/bugs/mod/a/comments + bugdir/bugs/mod/a/comments/new + bugdir/bugs/mod/a/comments/new/acom + bugdir/bugs/mod/a/comments/rem + bugdir/bugs/mod/a/comments/mod + """ + def report_or_none(self): + report = self.report() + if report == None: + return None + payload = report.get_payload() + if payload == None or len(payload) == 0: + return None + return report + def report_string(self): + report = self.report_or_none() + if report == None: + return 'No changes' + else: + return send_pgp_mime.flatten(report, to_unicode=True) + def make_root(self): + return MIMEMultipart() + def join(self, root, parent, data_part): + if hasattr(parent, 'attach_child_text'): + self.attach_child_text = True + if data_part != None: + send_pgp_mime.append_text(parent.data_mime_part, u'\n\n%s' % (data_part)) + self.data_mime_part = parent.data_mime_part + else: + self.data_mime_part = None + if data_part != None: + self.data_mime_part = send_pgp_mime.encodedMIMEText(data_part) + if parent != None and parent.name in [u'new', u'rem', u'mod']: + self.attach_child_text = True + if data_part == None: # make blank data_mime_part for children's appends + self.data_mime_part = send_pgp_mime.encodedMIMEText(u'') + if self.data_mime_part != None: + self.data_mime_part[u'Content-Description'] = self.name + root.attach(self.data_mime_part) + def data_part(self, depth, indent=False): + return libbe.diff.DiffTree.data_part(self, depth, indent=indent) + +class Diff (libbe.diff.Diff): + def bug_add_string(self, bug): + return bug.string(show_comments=True) + def _comment_summary_string(self, comment): + return comment.string() + def comment_add_string(self, comment): + return self._comment_summary_string(comment) + def comment_rem_string(self, comment): + return self._comment_summary_string(comment) + +class Message (object): + def __init__(self, email_text=None, disable_parsing=False): + if disable_parsing == False: + self.text = email_text + p=email.Parser.Parser() + self.msg=p.parsestr(self.text) + if LOGFILE != None: + LOGFILE.write(u'handling %s\n' % self.author_addr()) + LOGFILE.write(u'\n%s\n\n' % self.text) + self.confirm = True # enable/disable confirmation email + def _yes_no(self, boolean): + if boolean == True: + return 'yes' + return 'no' + def author_tuple(self): + """ + Extract and normalize the sender's email address. Returns a + (name, email) tuple. + """ + if not hasattr(self, 'author_tuple_cache'): + self._author_tuple_cache = \ + send_pgp_mime.source_email(self.msg, return_realname=True) + return self._author_tuple_cache + def author_addr(self): + return email.utils.formataddr(self.author_tuple()) + def author_name(self): + return self.author_tuple()[0] + def author_email(self): + return self.author_tuple()[1] + def default_msg_attribute_access(self, attr_name, default=None): + if attr_name in self.msg: + return self.msg[attr_name] + return default + def message_id(self, default=None): + return self.default_msg_attribute_access('message-id', default=default) + def subject(self): + if 'subject' not in self.msg: + raise InvalidSubject(self, u'Email must contain a subject') + return self.msg['subject'] + def _split_subject(self): + """ + Returns (tag, subject), with missing values replaced by None. + """ + if hasattr(self, '_split_subject_cache'): + return self._split_subject_cache + args = self.subject().split(u']',1) + if len(args) < 1: + self._split_subject_cache = (None, None) + elif len(args) < 2: + self._split_subject_cache = (args[0]+u']', None) + else: + self._split_subject_cache = (args[0]+u']', args[1].strip()) + return self._split_subject_cache + def _subject_tag_type(self): + """ + Parse subject tag, return (type, value), where type is one of + None, "new", "comment", or "control"; and value is None except + in the case of "comment", in which case it's the bug + ID/shortname. + """ + tag,subject = self._split_subject() + type = None + value = None + if tag == SUBJECT_TAG_NEW: + type = u'new' + elif tag == SUBJECT_TAG_CONTROL: + type = u'control' + else: + match = SUBJECT_TAG_COMMENT.match(tag) + if len(match.groups()) == 1: + type = u'comment' + value = match.group(1) + return (type, value) + def validate_subject(self): + """ + Validate the subject line. + """ + tag,subject = self._split_subject() + if not tag.startswith(SUBJECT_TAG_START): + raise InvalidSubject( + self, u'Subject must start with "%s"' % SUBJECT_TAG_START) + tag_type,value = self._subject_tag_type() + if tag_type == None: + raise InvalidSubject(self, u'Invalid tag "%s"' % tag) + elif tag_type == u'new' and len(subject) == 0: + raise InvalidSubject(self, u'Cannot create a bug with blank title') + elif tag_type == u'comment' and len(value) == 0: + raise InvalidSubject(self, u'Must specify a bug ID to comment') + def _get_bodies_and_mime_types(self): + """ + Traverse the email message returning (body, mime_type) for + each non-mulitpart portion of the message. + """ + msg_charset = self.msg.get_content_charset(ENCODING).lower() + for part in self.msg.walk(): + if part.is_multipart(): + continue + body,mime_type=(part.get_payload(decode=True),part.get_content_type()) + charset = part.get_content_charset(msg_charset).lower() + if mime_type.startswith('text/'): + body = unicode(body, charset) # convert text types to unicode + yield (body, mime_type) + def _parse_body_pseudoheaders(self, body, required, optional, + dictionary=None): + """ + Grab any pseudo-headers from the beginning of body. Raise + InvalidPseudoHeader on errors. Returns the body text after + the pseudo-header and a dictionary of set options. If you + like, you can initialize the dictionary with some defaults + and pass your initialized dict in as dictionary. + """ + if dictionary == None: + dictionary = {} + body_lines = body.splitlines() + all = required+optional + for i,line in enumerate(body_lines): + line = line.strip() + if len(line) == 0: + break + if ':' not in line: + raise InvalidPseudoheader(self, line) + key,value = line.split(':', 1) + value = value.strip() + if key not in all: + raise InvalidPseudoHeader(self, key) + if len(value) == 0: + raise InvalidEmail( + self, u'Blank value for: %s' % key) + dictionary[key] = value + missing = [] + for key in required: + if key not in dictionary: + missing.append(key) + if len(missing) > 0: + raise InvalidPseudoHeader(self, + u'Missing required pseudo-headers:\n%s' + % u', '.join(missing)) + remaining_body = u'\n'.join(body_lines[i:]).strip() + return (remaining_body, dictionary) + def _strip_footer(self, body): + body_lines = body.splitlines() + for i,line in enumerate(body_lines): + if line.startswith(BREAK): + break + i += 1 # increment past the current valid line. + return u'\n'.join(body_lines[:i]).strip() + def parse(self): + """ + Parse the commands given in the email. Raises assorted + subclasses of InvalidEmail in the case of invalid messages, + otherwise returns a list of suggested commands to run. + """ + self.validate_subject() + tag_type,value = self._subject_tag_type() + if tag_type == u'new': + commands = self.parse_new() + elif tag_type == u'comment': + commands = self.parse_comment(value) + elif tag_type == u'control': + commands = self.parse_control() + else: + raise Exception, u'Unrecognized tag type "%s"' % tag_type + return commands + def parse_new(self): + command = u'new' + tag,subject = self._split_subject() + summary = subject + options = {u'Reporter': self.author_addr(), + u'Confirm': self._yes_no(self.confirm), + u'Subscribe': 'no', + } + body,mime_type = list(self._get_bodies_and_mime_types())[0] + comment_body,options = \ + self._parse_body_pseudoheaders(body, + NEW_REQUIRED_PSEUDOHEADERS, + NEW_OPTIONAL_PSEUDOHEADERS, + options) + if options[u'Confirm'].lower() == 'no': + self.confirm = False + if options[u'Subscribe'].lower() == 'yes' and self.confirm == True: + # respond with the subscription format rather than the + # normal command-output format, because the subscription + # format is more user-friendly. + self.confirm = False + args = [u'--reporter', options[u'Reporter']] + args.append(summary) + commands = [Command(self, command, args)] + id = ID(commands[0]) + comment_body = self._strip_footer(comment_body) + if len(comment_body) > 0: + command = u'comment' + comment = u'Version: %s\n\n'%options[u'Version'] + comment_body + args = [u'--author', self.author_addr(), + u'--alt-id', self.message_id(), + u'--content-type', mime_type] + args.append(id) + args.append(u'-') + commands.append(Command(self, u'comment', args, stdin=comment)) + for key,value in options.items(): + if key in [u'Version', u'Reporter', u'Confirm']: + continue # we've already handled these options + command = key.lower() + if key in [u'Depend', u'Tag', u'Target', u'Subscribe']: + args = [id, value] + else: + args = [value, id] + if key == u'Subscribe': + if value.lower() != 'yes': + continue + args = ['--subscriber', self.author_addr(), id] + commands.append(Command(self, command, args)) + return commands + def parse_comment(self, bug_uuid): + command = u'comment' + bug_id = bug_uuid + author = self.author_addr() + alt_id = self.message_id() + body,mime_type = list(self._get_bodies_and_mime_types())[0] + if mime_type == 'text/plain': + body = self._strip_footer(body) + content_type = mime_type + args = [u'--author', author] + if alt_id != None: + args.extend([u'--alt-id', alt_id]) + args.extend([u'--content-type', content_type, bug_id, u'-']) + commands = [Command(self, command, args, stdin=body)] + return commands + def parse_control(self): + body,mime_type = list(self._get_bodies_and_mime_types())[0] + commands = [] + for line in body.splitlines(): + line = line.strip() + if line.startswith(CONTROL_COMMENT) or len(line) == 0: + continue + if line.startswith(BREAK): + break + if type(line) == types.UnicodeType: + # work around http://bugs.python.org/issue1170 + line = line.encode('unicode escape') + fields = shlex.split(line) + if type(line) == types.UnicodeType: + # work around http://bugs.python.org/issue1170 + for field in fields: + field = unicode(field, 'unicode escape') + command,args = (fields[0], fields[1:]) + commands.append(Command(self, command, args)) + if len(commands) == 0: + raise InvalidEmail(self, u'No commands in control email.') + return commands + def run(self, repo='.'): + self._begin_response() + commands = self.parse() + try: + for i,command in enumerate(commands): + command.run() + self._add_response(command.response_msg()) + finally: + if AUTOCOMMIT == True: + tag,subject = self._split_subject() + self.commit_command = Command(self, 'commit', [subject]) + self.commit_command.run() + if LOGFILE != None: + LOGFILE.write(u'Autocommit:\n%s\n\n' % + send_pgp_mime.flatten(self.commit_command.response_msg(), + to_unicode=True)) + def _begin_response(self): + tag,subject = self._split_subject() + response_header = [u'From: %s' % THIS_ADDRESS, + u'To: %s' % self.author_addr(), + u'Date: %s' % libbe.util.utility.time_to_str(time.time()), + u'Subject: %s Re: %s'%(SUBJECT_TAG_RESPONSE,subject) + ] + if self.message_id() != None: + response_header.append(u'In-reply-to: %s' % self.message_id()) + self.response_header = \ + send_pgp_mime.header_from_text(text=u'\n'.join(response_header)) + self._response_messages = [] + def _add_response(self, response_message): + self._response_messages.append(response_message) + def response_email(self): + assert len(self._response_messages) > 0 + if len(self._response_messages) == 1: + response_body = self._response_messages[0] + else: + response_body = MIMEMultipart() + for message in self._response_messages: + response_body.attach(message) + return send_pgp_mime.attach_root(self.response_header, response_body) + def subscriber_emails(self, previous_revision=None): + if previous_revision == None: + if AUTOCOMMIT != True: # no way to tell what's changed + raise NotificationFailed('Autocommit dissabled') + if len(self._response_messages) == 0: + raise NotificationFailed('Initial email failed.') + if self.commit_command.ret != 0: + # commit failed. Error already logged. + raise NotificationFailed('Commit failed') + + bd = UI.storage_callbacks.get_bugdir() + writeable = bd.storage.writeable + bd.storage.writeable = False + if bd.storage.versioned == False: # no way to tell what's changed + bd.storage.writeable = writeable + raise NotificationFailed('Not versioned') + + bd.load_all_bugs() + subscribers = subscribe.get_bugdir_subscribers(bd, THIS_SERVER) + if len(subscribers) == 0: + bd.storage.writeable = writeable + return [] + for subscriber,subscriptions in subscribers.items(): + subscribers[subscriber] = [] + for id,types in subscriptions.items(): + for type in types: + subscribers[subscriber].append( + libbe.diff.Subscription(id,type)) + + before_bd, after_bd = self._get_before_and_after_bugdirs(bd, previous_revision) + diff = Diff(before_bd, after_bd) + diff.full_report(diff_tree=DiffTree) + header = self._subscriber_header(bd, previous_revision) + + emails = [] + for subscriber,subscriptions in subscribers.items(): + header.replace_header('to', subscriber) + report = diff.report_tree(subscriptions, diff_tree=DiffTree) + root = report.report_or_none() + if root != None: + emails.append(send_pgp_mime.attach_root(header, root)) + if LOGFILE != None: + LOGFILE.write(u'Preparing to notify %s of changes\n' % subscriber) + bd.storage.writeable = writeable + return emails + def _get_before_and_after_bugdirs(self, bd, previous_revision=None): + if previous_revision == None: + commit_msg = self.commit_command.stdout + assert commit_msg.startswith('Committed '), commit_msg + after_revision = commit_msg[len('Committed '):] + before_revision = bd.storage.revision_id(-2) + else: + before_revision = previous_revision + if before_revision == None: + # this commit was the initial commit + before_bd = libbe.bugdir.BugDir(from_disk=False, + manipulate_encodings=False) + else: + before_bd = libbe.bugdir.RevisionedBugDir(bd, before_revision) + #after_bd = bd.duplicate_bugdir(after_revision) + after_bd = bd # assume no changes since commit a few cycles ago + return (before_bd, after_bd) + def _subscriber_header(self, bd, previous_revision=None): + root_dir = os.path.basename(bd.storage.repo) + if previous_revision == None: + subject = 'Changes to %s on %s by %s' \ + % (root_dir, THIS_SERVER, self.author_addr()) + else: + subject = 'Changes to %s on %s since revision %s' \ + % (root_dir, THIS_SERVER, previous_revision) + header = [u'From: %s' % THIS_ADDRESS, + u'To: %s' % u'DUMMY-AUTHOR', + u'Date: %s' % libbe.util.utility.time_to_str(time.time()), + u'Subject: %s Re: %s' % (SUBJECT_TAG_RESPONSE, subject) + ] + return send_pgp_mime.header_from_text(text=u'\n'.join(header)) + +def generate_global_tags(tag_base=u'be-bug'): + """ + Generate a series of tags from a base tag string. + """ + global SUBJECT_TAG_BASE, SUBJECT_TAG_START, SUBJECT_TAG_RESPONSE, \ + SUBJECT_TAG_NEW, SUBJECT_TAG_COMMENT, SUBJECT_TAG_CONTROL + SUBJECT_TAG_BASE = tag_base + SUBJECT_TAG_START = u'[%s' % tag_base + SUBJECT_TAG_RESPONSE = u'[%s]' % tag_base + SUBJECT_TAG_NEW = u'[%s:submit]' % tag_base + SUBJECT_TAG_COMMENT = re.compile(u'\[%s:([\-0-9a-z/]*)]' % tag_base) + SUBJECT_TAG_CONTROL = SUBJECT_TAG_RESPONSE + +def open_logfile(logpath=None): + """ + If logpath=None, default to global LOGPATH. + Special logpath strings: + "-" set LOGFILE to sys.stderr + "none" disable logging + Relative logpaths are expanded relative to _THIS_DIR + """ + global LOGPATH, LOGFILE + if logpath != None: + if logpath == u'-': + LOGPATH = u'stderr' + LOGFILE = sys.stderr + elif logpath == u'none': + LOGPATH = u'none' + LOGFILE = None + elif os.path.isabs(logpath): + LOGPATH = logpath + else: + LOGPATH = os.path.join(_THIS_DIR, logpath) + if LOGFILE == None and LOGPATH != u'none': + LOGFILE = codecs.open(LOGPATH, u'a+', + libbe.util.encoding.get_filesystem_encoding()) + +def close_logfile(): + if LOGFILE != None and LOGPATH not in [u'stderr', u'none']: + LOGFILE.close() + +def test(): + result = unittest.TextTestRunner(verbosity=2).run(suite) + num_errors = len(result.errors) + num_failures = len(result.failures) + num_bad = num_errors + num_failures + return num_bad + +def main(args): + from optparse import OptionParser + global AUTOCOMMIT, UI + + usage='be-handle-mail [options]\n\n%s' % (__doc__) + parser = OptionParser(usage=usage) + parser.add_option('-r', '--repo', dest='repo', default=_THIS_DIR, + metavar='REPO', + help='Select the BE repository to serve (%default).') + parser.add_option('-t', '--tag-base', dest='tag_base', + default=SUBJECT_TAG_BASE, metavar='TAG', + help='Set the subject tag base (%default).') + parser.add_option('-o', '--output', dest='output', action='store_true', + help="Don't mail the generated message, print it to stdout instead. Useful for testing be-handle-mail functionality without the whole mail transfer agent and procmail setup.") + parser.add_option('-l', '--logfile', dest='logfile', metavar='LOGFILE', + help='Set the logfile to LOGFILE. Relative paths are relative to the location of this be-handle-mail file (%s). The special value of "-" directs the log output to stderr, and "none" disables logging.' % _THIS_DIR) + parser.add_option('-a', '--disable-autocommit', dest='autocommit', + default=True, action='store_false', + help='Disable the autocommit after parsing the email.') + parser.add_option('-s', '--disable-subscribers', dest='subscribers', + default=True, action='store_false', + help='Disable subscriber notification emails.') + parser.add_option('--notify-since', dest='notify_since', metavar='REVISION', + help='Notify subscribers of all changes since REVISION. When this option is set, no input email parsing is done.') + parser.add_option('--test', dest='test', action='store_true', + help='Run internal unit-tests and exit.') + + pargs = args + options,args = parser.parse_args(args[1:]) + + if options.test == True: + num_bad = test() + if num_bad > 126: + num_bad = 1 + sys.exit(num_bad) + + AUTOCOMMIT = options.autocommit + + if options.notify_since == None: + msg_text = sys.stdin.read() + + open_logfile(options.logfile) + generate_global_tags(options.tag_base) + + io = libbe.command.StringInputOutput() + UI = libbe.command.UserInterface(io, location=options.repo) + + if options.notify_since != None: + if options.subscribers == True: + if LOGFILE != None: + LOGFILE.write(u'Checking for subscribers to notify since revision %s\n' + % options.notify_since) + try: + m = Message(disable_parsing=True) + emails = m.subscriber_emails(options.notify_since) + except NotificationFailed, e: + if LOGFILE != None: + LOGFILE.write(unicode(e) + u'\n') + else: + for msg in emails: + if options.output == True: + print send_pgp_mime.flatten(msg, to_unicode=True) + else: + send_pgp_mime.mail(msg, send_pgp_mime.sendmail) + close_logfile() + UI.cleanup() + sys.exit(0) + + if len(msg_text.strip()) == 0: # blank email!? + if LOGFILE != None: + LOGFILE.write(u'Blank email!\n') + close_logfile() + UI.cleanup() + sys.exit(1) + try: + m = Message(msg_text) + m.run() + except InvalidEmail, e: + response = e.response() + except Exception, e: + if LOGFILE != None: + LOGFILE.write(u'Uncaught exception:\n%s\n' % (e,)) + traceback.print_tb(sys.exc_traceback, file=LOGFILE) + close_logfile() + m.commit_command.cleanup() + UI.cleanup() + sys.exit(1) + else: + response = m.response_email() + if options.output == True: + print send_pgp_mime.flatten(response, to_unicode=True) + elif m.confirm == True: + if LOGFILE != None: + LOGFILE.write(u'Sending response to %s\n' % m.author_addr()) + LOGFILE.write(u'\n%s\n\n' % send_pgp_mime.flatten(response, + to_unicode=True)) + send_pgp_mime.mail(response, send_pgp_mime.sendmail) + else: + if LOGFILE != None: + LOGFILE.write(u'Response declined by %s\n' % m.author_addr()) + if options.subscribers == True: + if LOGFILE != None: + LOGFILE.write(u'Checking for subscribers\n') + try: + emails = m.subscriber_emails() + except NotificationFailed, e: + if LOGFILE != None: + LOGFILE.write(unicode(e) + u'\n') + else: + for msg in emails: + if options.output == True: + print send_pgp_mime.flatten(msg, to_unicode=True) + else: + send_pgp_mime.mail(msg, send_pgp_mime.sendmail) + + close_logfile() + m.commit_command.cleanup() + UI.cleanup() + +class GenerateGlobalTagsTestCase (unittest.TestCase): + def setUp(self): + super(GenerateGlobalTagsTestCase, self).setUp() + self.save_global_tags() + def tearDown(self): + self.restore_global_tags() + super(GenerateGlobalTagsTestCase, self).tearDown() + def save_global_tags(self): + self.saved_globals = [SUBJECT_TAG_BASE, SUBJECT_TAG_START, + SUBJECT_TAG_RESPONSE, SUBJECT_TAG_NEW, + SUBJECT_TAG_COMMENT, SUBJECT_TAG_CONTROL] + def restore_global_tags(self): + global SUBJECT_TAG_BASE, SUBJECT_TAG_START, SUBJECT_TAG_RESPONSE, \ + SUBJECT_TAG_NEW, SUBJECT_TAG_COMMENT, SUBJECT_TAG_CONTROL + SUBJECT_TAG_BASE, SUBJECT_TAG_START, SUBJECT_TAG_RESPONSE, \ + SUBJECT_TAG_NEW, SUBJECT_TAG_COMMENT, SUBJECT_TAG_CONTROL = \ + self.saved_globals + def test_restore_global_tags(self): + "Test global tag restoration by teardown function." + global SUBJECT_TAG_BASE + self.failUnlessEqual(SUBJECT_TAG_BASE, u'be-bug') + SUBJECT_TAG_BASE = 'projectX-bug' + self.failUnlessEqual(SUBJECT_TAG_BASE, u'projectX-bug') + self.restore_global_tags() + self.failUnlessEqual(SUBJECT_TAG_BASE, u'be-bug') + def test_subject_tag_base(self): + "Should set SUBJECT_TAG_BASE global correctly" + generate_global_tags(u'projectX-bug') + self.failUnlessEqual(SUBJECT_TAG_BASE, u'projectX-bug') + def test_subject_tag_start(self): + "Should set SUBJECT_TAG_START global correctly" + generate_global_tags(u'projectX-bug') + self.failUnlessEqual(SUBJECT_TAG_START, u'[projectX-bug') + def test_subject_tag_response(self): + "Should set SUBJECT_TAG_RESPONSE global correctly" + generate_global_tags(u'projectX-bug') + self.failUnlessEqual(SUBJECT_TAG_RESPONSE, u'[projectX-bug]') + def test_subject_tag_new(self): + "Should set SUBJECT_TAG_NEW global correctly" + generate_global_tags(u'projectX-bug') + self.failUnlessEqual(SUBJECT_TAG_NEW, u'[projectX-bug:submit]') + def test_subject_tag_control(self): + "Should set SUBJECT_TAG_CONTROL global correctly" + generate_global_tags(u'projectX-bug') + self.failUnlessEqual(SUBJECT_TAG_CONTROL, u'[projectX-bug]') + def test_subject_tag_comment(self): + "Should set SUBJECT_TAG_COMMENT global correctly" + generate_global_tags(u'projectX-bug') + m = SUBJECT_TAG_COMMENT.match('[projectX-bug:abc/xyz-123]') + self.failUnlessEqual(len(m.groups()), 1) + self.failUnlessEqual(m.group(1), u'abc/xyz-123') + +unitsuite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) +suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) + +if __name__ == "__main__": + main(sys.argv) diff --git a/__init__.py b/interfaces/email/interactive/examples/blank index e69de29..e69de29 100644 --- a/__init__.py +++ b/interfaces/email/interactive/examples/blank diff --git a/interfaces/email/interactive/examples/comment b/interfaces/email/interactive/examples/comment new file mode 100644 index 0000000..f22e4b2 --- /dev/null +++ b/interfaces/email/interactive/examples/comment @@ -0,0 +1,11 @@ +From jdoe@example.com Fri Apr 18 11:18:58 2008 +Message-ID: <xyz@example.com> +Date: Fri, 18 Apr 2008 12:00:00 +0000 +From: John Doe <jdoe@example.com> +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Subject: [be-bug:a1d] Subject ignored + +We sure do. +-- +Goofy tagline ignored diff --git a/interfaces/email/interactive/examples/email_bugs b/interfaces/email/interactive/examples/email_bugs new file mode 100644 index 0000000..949e1c1 --- /dev/null +++ b/interfaces/email/interactive/examples/email_bugs @@ -0,0 +1,37 @@ +From jdoe@example.com Fri Apr 18 12:00:00 2008 +Content-Type: text/xml; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: quoted-printable +From: jdoe@example.com +To: a@b.com +Date: Fri, 18 Apr 2008 12:00:00 +0000 +Subject: [be-bug:xml] Updates to a, b + +<?xml version="1.0" encoding="utf-8" ?> +<be-xml> + <version> + <tag>1.0.0</tag> + <branch-nick>be</branch-nick> + <revno>446</revno> + <revision-id>wking@drexel.edu-20091119214553-iqyw2cpqluww3zna</revision-id> + </version> + <bug> + <uuid>a</uuid> + <short-name>a</short-name> + <severity>minor</severity> + <status>open</status> + <creator>John Doe <jdoe@example.com></creator> + <created>Thu, 01 Jan 1970 00:00:00 +0000</created> + <summary>Bug A</summary> + </bug> + <bug> + <uuid>b</uuid> + <short-name>b</short-name> + <severity>minor</severity> + <status>closed</status> + <creator>Jane Doe <jdoe@example.com></creator> + <created>Thu, 01 Jan 1970 00:00:00 +0000</created> + <summary>Bug B</summary> + </bug> +</be-xml> + diff --git a/interfaces/email/interactive/examples/failing_multiples b/interfaces/email/interactive/examples/failing_multiples new file mode 100644 index 0000000..cf50211 --- /dev/null +++ b/interfaces/email/interactive/examples/failing_multiples @@ -0,0 +1,16 @@ +From jdoe@example.com Fri Apr 18 12:00:00 2008 +Message-ID: <abcd@example.com> +Date: Fri, 18 Apr 2008 12:00:00 +0000 +From: John Doe <jdoe@example.com> +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Subject: [be-bug] Commit message... + +new "test bug" +new "test bug 2" +failing-command +new "test bug 3" + +-- +This message fails partway through, but the partial changes should be +recorded in a commit... diff --git a/interfaces/email/interactive/examples/invalid_command b/interfaces/email/interactive/examples/invalid_command new file mode 100644 index 0000000..f2963c7 --- /dev/null +++ b/interfaces/email/interactive/examples/invalid_command @@ -0,0 +1,11 @@ +From jdoe@example.com Fri Apr 18 11:18:58 2008 +Message-ID: <abcd@example.com> +Date: Fri, 18 Apr 2008 12:00:00 +0000 +From: John Doe <jdoe@example.com> +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Subject: [be-bug] + +close +-- +Close is currently disabled for the email interface. diff --git a/interfaces/email/interactive/examples/invalid_subject b/interfaces/email/interactive/examples/invalid_subject new file mode 100644 index 0000000..1e2eb88 --- /dev/null +++ b/interfaces/email/interactive/examples/invalid_subject @@ -0,0 +1,9 @@ +From jdoe@example.com Fri Apr 18 11:18:58 2008 +Message-ID: <abcd@example.com> +Date: Fri, 18 Apr 2008 12:00:00 +0000 +From: John Doe <jdoe@example.com> +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Subject: Spam! + +This should elicit an "invalid subject" response email. diff --git a/interfaces/email/interactive/examples/list b/interfaces/email/interactive/examples/list new file mode 100644 index 0000000..acba424 --- /dev/null +++ b/interfaces/email/interactive/examples/list @@ -0,0 +1,11 @@ +From jdoe@example.com Fri Apr 18 11:18:58 2008 +Message-ID: <abcd@example.com> +Date: Fri, 18 Apr 2008 12:00:00 +0000 +From: John Doe <jdoe@example.com> +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Subject: [be-bug] Subject ignored + +list --status all +-- +Dummy content diff --git a/interfaces/email/interactive/examples/missing_command b/interfaces/email/interactive/examples/missing_command new file mode 100644 index 0000000..bb390fc --- /dev/null +++ b/interfaces/email/interactive/examples/missing_command @@ -0,0 +1,11 @@ +From jdoe@example.com Fri Apr 18 11:18:58 2008 +Message-ID: <abcd@example.com> +Date: Fri, 18 Apr 2008 12:00:00 +0000 +From: John Doe <jdoe@example.com> +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Subject: [be-bug] Subject ignored + +abcde +-- +This should elicit a "invalid command 'abcde'" response email. diff --git a/interfaces/email/interactive/examples/multiple_commands b/interfaces/email/interactive/examples/multiple_commands new file mode 100644 index 0000000..41ef730 --- /dev/null +++ b/interfaces/email/interactive/examples/multiple_commands @@ -0,0 +1,14 @@ +From jdoe@example.com Fri Apr 18 11:18:58 2008 +Message-ID: <abcd@example.com> +Date: Fri, 18 Apr 2008 12:00:00 +0000 +From: John Doe <jdoe@example.com> +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Subject: [be-bug] Subject ignored + +help +list --status=all +list --status=fixed +show --xml 361 +-- +Goofy tagline ignored. diff --git a/interfaces/email/interactive/examples/new b/interfaces/email/interactive/examples/new new file mode 100644 index 0000000..c64db93 --- /dev/null +++ b/interfaces/email/interactive/examples/new @@ -0,0 +1,19 @@ +From jdoe@example.com Fri Apr 18 12:00:00 2008 +Message-ID: <abcd@example.com> +Date: Fri, 18 Apr 2008 12:00:00 +0000 +From: John Doe <jdoe@example.com> +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Subject: [be-bug:submit] Need tests for the email interface. + +Version: XYZ +Reporter: Jane Doe +Assign: Dick Tracy +Depend: 00f +Severity: critical +Status: assigned +Tag: topsecret +Target: Law&Order + +-- +Goofy tagline not included, and no comment added. diff --git a/interfaces/email/interactive/examples/new_with_comment b/interfaces/email/interactive/examples/new_with_comment new file mode 100644 index 0000000..1077f0f --- /dev/null +++ b/interfaces/email/interactive/examples/new_with_comment @@ -0,0 +1,13 @@ +From jdoe@example.com Fri Apr 18 11:18:58 2008 +Message-ID: <abcd@example.com> +Date: Fri, 18 Apr 2008 12:00:00 +0000 +From: John Doe <jdoe@example.com> +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Subject: [be-bug:submit] Need tests for the email interface. + +Version: XYZ + +I think so anyway. +-- +Goofy tagline not included. diff --git a/interfaces/email/interactive/examples/show b/interfaces/email/interactive/examples/show new file mode 100644 index 0000000..c5f8a4d --- /dev/null +++ b/interfaces/email/interactive/examples/show @@ -0,0 +1,11 @@ +From jdoe@example.com Fri Apr 18 11:18:58 2008 +Message-ID: <abcd@example.com> +Date: Fri, 18 Apr 2008 12:00:00 +0000 +From: John Doe <jdoe@example.com> +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Subject: [be-bug] Subject ignored + +show --xml 361 +-- +Can we show a bug? diff --git a/interfaces/email/interactive/examples/unicode b/interfaces/email/interactive/examples/unicode new file mode 100644 index 0000000..f0e8001 --- /dev/null +++ b/interfaces/email/interactive/examples/unicode @@ -0,0 +1,11 @@ +From jdoe@example.com Fri Apr 18 11:18:58 2008 +Message-ID: <abcd@example.com> +Date: Fri, 18 Apr 2008 12:00:00 +0000 +From: John Doe <jdoe@example.com> +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Subject: [be-bug] Subject ignored + +show --xml f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a +-- +Can we handle unicode output? diff --git a/interfaces/email/interactive/libbe b/interfaces/email/interactive/libbe new file mode 120000 index 0000000..7d18612 --- /dev/null +++ b/interfaces/email/interactive/libbe @@ -0,0 +1 @@ +../../../libbe
\ No newline at end of file diff --git a/interfaces/email/interactive/send_pgp_mime.py b/interfaces/email/interactive/send_pgp_mime.py new file mode 100644 index 0000000..517b1f0 --- /dev/null +++ b/interfaces/email/interactive/send_pgp_mime.py @@ -0,0 +1,611 @@ +#!/usr/bin/python +# +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" +Python module and command line tool for sending pgp/mime email. + +Mostly uses subprocess to call gpg and a sendmail-compatible mailer. +If you lack gpg, either don't use the encryption functions or adjust +the pgp_* commands. You may need to adjust the sendmail command to +point to whichever sendmail-compatible mailer you have on your system. +""" + +from cStringIO import StringIO +import os +import re +#import GnuPGInterface # Maybe should use this instead of subprocess +import smtplib +import subprocess +import sys +import tempfile +import types + +try: + from email import Message + from email.mime.text import MIMEText + from email.mime.multipart import MIMEMultipart + from email.mime.application import MIMEApplication + from email.encoders import encode_7or8bit + from email.generator import Generator + from email.parser import Parser + from email.utils import getaddress +except ImportError: + # adjust to old python 2.4 + from email import Message + from email.MIMEText import MIMEText + from email.MIMEMultipart import MIMEMultipart + from email.MIMENonMultipart import MIMENonMultipart + from email.Encoders import encode_7or8bit + from email.Generator import Generator + from email.Parser import Parser + from email.Utils import getaddresses + + getaddress = getaddresses + class MIMEApplication (MIMENonMultipart): + def __init__(self, _data, _subtype, _encoder, **params): + MIMENonMultipart.__init__(self, 'application', _subtype, **params) + self.set_payload(_data) + _encoder(self) + +usage="""usage: %prog [options] + +Scriptable PGP MIME email using gpg. + +You can use gpg-agent for passphrase caching if your key requires a +passphrase (it better!). Example usage would be to install gpg-agent, +and then run + export GPG_TTY=`tty` + eval $(gpg-agent --daemon) +in your shell before invoking this script. See gpg-agent(1) for more +details. Alternatively, you can send your passphrase in on stdin + echo 'passphrase' | %prog [options] +or use the --passphrase-file option + %prog [options] --passphrase-file FILE [more options] +Both of these alternatives are much less secure than gpg-agent. You +have been warned. +""" + +verboseInvoke = False +PGP_SIGN_AS = None +PASSPHRASE = None + +# The following commands are adapted from my .mutt/pgp configuration +# +# Printf-like sequences: +# %a The value of PGP_SIGN_AS. +# %f Expands to the name of a file with text to be signed/encrypted. +# %p Expands to the passphrase argument. +# %R A string with some number (0 on up) of pgp_reciepient_arg +# strings. +# %r One key ID (e.g. recipient email address) to build a +# pgp_reciepient_arg string. +# +# The above sequences can be used to optionally print a string if +# their length is nonzero. For example, you may only want to pass the +# -u/--local-user argument to gpg if PGP_SIGN_AS is defined. To +# optionally print a string based upon one of the above sequences, the +# following construct is used +# %?<sequence_char>?<optional_string>? +# where sequence_char is a character from the table above, and +# optional_string is the string you would like printed if status_char +# is nonzero. optional_string may contain other sequence as well as +# normal text, but it may not contain any question marks. +# +# see http://codesorcery.net/old/mutt/mutt-gnupg-howto +# http://www.mutt.org/doc/manual/manual-6.html#pgp_autosign +# http://tldp.org/HOWTO/Mutt-GnuPG-PGP-HOWTO-8.html +# for more details + +pgp_recipient_arg='-r "%r"' +pgp_stdin_passphrase_arg='--passphrase-fd 0' +pgp_sign_command='/usr/bin/gpg --no-verbose --quiet --batch %p --output - --detach-sign --armor --textmode %?a?-u "%a"? %f' +pgp_encrypt_only_command='/usr/bin/gpg --no-verbose --quiet --batch --output - --encrypt --armor --textmode --always-trust --encrypt-to "%a" %R -- %f' +pgp_encrypt_sign_command='/usr/bin/gpg --no-verbose --quiet --batch %p --output - --encrypt --sign %?a?-u "%a"? --armor --textmode --always-trust --encrypt-to "%a" %R -- %f' +sendmail='/usr/sbin/sendmail -t' + +def mail(msg, sendmail=None): + """ + Send an email Message instance on its merry way. + + We can shell out to the user specified sendmail in case + the local host doesn't have an SMTP server set up + for easy smtplib usage. + """ + if sendmail != None: + execute(sendmail, stdin=flatten(msg)) + return None + s = smtplib.SMTP() + s.connect() + s.sendmail(from_addr=source_email(msg), + to_addrs=target_emails(msg), + msg=flatten(msg)) + s.close() + +def header_from_text(text, encoding="us-ascii"): + """ + Simple wrapper for instantiating an email.Message from text. + >>> header = header_from_text('\\n'.join(['From: me@big.edu','To: you@big.edu','Subject: testing'])) + >>> print flatten(header) + From: me@big.edu + To: you@big.edu + Subject: testing + <BLANKLINE> + <BLANKLINE> + """ + text = text.strip() + if type(text) == types.UnicodeType: + text = text.encode(encoding) + # assume StringType arguments are already encoded + p = Parser() + return p.parsestr(text, headersonly=True) + +def guess_encoding(text): + if type(text) == types.StringType: + encoding = "us-ascii" + elif type(text) == types.UnicodeType: + for encoding in ["us-ascii", "iso-8859-1", "utf-8"]: + try: + text.encode(encoding) + except UnicodeError: + pass + else: + break + assert encoding != None + return encoding + +def encodedMIMEText(body, encoding=None): + if encoding == None: + encoding = guess_encoding(body) + if encoding == "us-ascii": + return MIMEText(body) + else: + # Create the message ('plain' stands for Content-Type: text/plain) + return MIMEText(body.encode(encoding), 'plain', encoding) + +def append_text(text_part, new_text): + original_payload = text_part.get_payload(decode=True) + new_payload = u"%s%s" % (original_payload, new_text) + new_encoding = guess_encoding(new_payload) + text_part.set_payload(new_payload.encode(new_encoding), new_encoding) + +def attach_root(header, root_part): + """ + Attach the email.Message root_part to the email.Message header + without generating a multi-part message. + """ + for k,v in header.items(): + root_part[k] = v + return root_part + +def execute(args, stdin=None, expect=(0,)): + """ + Execute a command (allows us to drive gpg). + """ + if verboseInvoke == True: + print >> sys.stderr, '$ '+args + try: + p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, close_fds=True) + except OSError, e: + strerror = '%s\nwhile executing %s' % (e.args[1], args) + raise Exception, strerror + output, error = p.communicate(input=stdin) + status = p.wait() + if verboseInvoke == True: + print >> sys.stderr, '(status: %d)\n%s%s' % (status, output, error) + if status not in expect: + strerror = '%s\nwhile executing %s\n%s\n%d' % (args[1], args, error, status) + raise Exception, strerror + return status, output, error + +def replace(template, format_char, replacement_text): + """ + >>> replace('--textmode %?a?-u %a? %f', 'f', 'file.in') + '--textmode %?a?-u %a? file.in' + >>> replace('--textmode %?a?-u %a? %f', 'a', '0xHEXKEY') + '--textmode -u 0xHEXKEY %f' + >>> replace('--textmode %?a?-u %a? %f', 'a', '') + '--textmode %f' + """ + if replacement_text == None: + replacement_text = "" + regexp = re.compile('%[?]'+format_char+'[?]([^?]*)[?]') + if len(replacement_text) > 0: + str = regexp.sub('\g<1>', template) + else: + str = regexp.sub('', template) + regexp = re.compile('%'+format_char) + str = regexp.sub(replacement_text, str) + return str + +def flatten(msg, to_unicode=False): + """ + Produce flat text output from an email Message instance. + """ + assert msg != None + fp = StringIO() + g = Generator(fp, mangle_from_=False) + g.flatten(msg) + text = fp.getvalue() + if to_unicode == True: + encoding = msg.get_content_charset() or "utf-8" + text = unicode(text, encoding=encoding) + return text + +def source_email(msg, return_realname=False): + """ + Search the header of an email Message instance to find the + sender's email address. + """ + froms = msg.get_all('from', []) + from_tuples = getaddresses(froms) # [(realname, email_address), ...] + assert len(from_tuples) == 1 + if return_realname == True: + return from_tuples[0] # (realname, email_address) + return from_tuples[0][1] # email_address + +def target_emails(msg): + """ + Search the header of an email Message instance to find a + list of recipient's email addresses. + """ + tos = msg.get_all('to', []) + ccs = msg.get_all('cc', []) + bccs = msg.get_all('bcc', []) + resent_tos = msg.get_all('resent-to', []) + resent_ccs = msg.get_all('resent-cc', []) + resent_bccs = msg.get_all('resent-bcc', []) + all_recipients = getaddresses(tos + ccs + bccs + resent_tos + + resent_ccs + resent_bccs) + return [addr[1] for addr in all_recipients] + +class PGPMimeMessageFactory (object): + """ + See http://www.ietf.org/rfc/rfc3156.txt for specification details. + >>> from_addr = "me@big.edu" + >>> to_addr = "you@you.edu" + >>> header = header_from_text('\\n'.join(['From: %s'%from_addr,'To: %s'%to_addr,'Subject: testing'])) + >>> source_email(header) == from_addr + True + >>> target_emails(header) == [to_addr] + True + >>> m = PGPMimeMessageFactory('check 1 2\\ncheck 1 2\\n') + >>> print flatten(m.clearBodyPart()) + Content-Type: text/plain; charset="us-ascii" + MIME-Version: 1.0 + Content-Transfer-Encoding: 7bit + Content-Disposition: inline + <BLANKLINE> + check 1 2 + check 1 2 + <BLANKLINE> + >>> print flatten(m.plain()) + Content-Type: text/plain; charset="us-ascii" + MIME-Version: 1.0 + Content-Transfer-Encoding: 7bit + <BLANKLINE> + check 1 2 + check 1 2 + <BLANKLINE> + >>> signed = m.sign(header) + >>> signed.set_boundary('boundsep') + >>> print flatten(signed).replace('\\t', ' '*4) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE + Content-Type: multipart/signed; protocol="application/pgp-signature"; + micalg="pgp-sha1"; boundary="boundsep" + MIME-Version: 1.0 + Content-Disposition: inline + <BLANKLINE> + --boundsep + Content-Type: text/plain; charset="us-ascii" + MIME-Version: 1.0 + Content-Transfer-Encoding: 7bit + Content-Disposition: inline + <BLANKLINE> + check 1 2 + check 1 2 + <BLANKLINE> + --boundsep + MIME-Version: 1.0 + Content-Transfer-Encoding: 7bit + Content-Description: signature + Content-Type: application/pgp-signature; name="signature.asc"; + charset="us-ascii" + <BLANKLINE> + -----BEGIN PGP SIGNATURE----- + ... + -----END PGP SIGNATURE----- + <BLANKLINE> + --boundsep-- + >>> encrypted = m.encrypt(header) + >>> encrypted.set_boundary('boundsep') + >>> print flatten(encrypted).replace('\\t', ' '*4) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE + Content-Type: multipart/encrypted; + protocol="application/pgp-encrypted"; + micalg="pgp-sha1"; boundary="boundsep" + MIME-Version: 1.0 + Content-Disposition: inline + <BLANKLINE> + --boundsep + Content-Type: application/pgp-encrypted + MIME-Version: 1.0 + Content-Transfer-Encoding: 7bit + <BLANKLINE> + Version: 1 + <BLANKLINE> + --boundsep + MIME-Version: 1.0 + Content-Transfer-Encoding: 7bit + Content-Type: application/octet-stream; charset="us-ascii" + <BLANKLINE> + -----BEGIN PGP MESSAGE----- + ... + -----END PGP MESSAGE----- + <BLANKLINE> + --boundsep-- + >>> signedAndEncrypted = m.signAndEncrypt(header) + >>> signedAndEncrypted.set_boundary('boundsep') + >>> print flatten(signedAndEncrypted).replace('\\t', ' '*4) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE + Content-Type: multipart/encrypted; + protocol="application/pgp-encrypted"; + micalg="pgp-sha1"; boundary="boundsep" + MIME-Version: 1.0 + Content-Disposition: inline + <BLANKLINE> + --boundsep + Content-Type: application/pgp-encrypted + MIME-Version: 1.0 + Content-Transfer-Encoding: 7bit + <BLANKLINE> + Version: 1 + <BLANKLINE> + --boundsep + MIME-Version: 1.0 + Content-Transfer-Encoding: 7bit + Content-Type: application/octet-stream; charset="us-ascii" + <BLANKLINE> + -----BEGIN PGP MESSAGE----- + ... + -----END PGP MESSAGE----- + <BLANKLINE> + --boundsep-- + """ + def __init__(self, body): + self.body = body + def clearBodyPart(self): + body = encodedMIMEText(self.body) + body.add_header('Content-Disposition', 'inline') + return body + def passphrase_arg(self, passphrase=None): + if passphrase == None and PASSPHRASE != None: + passphrase = PASSPHRASE + if passphrase == None: + return (None,'') + return (passphrase, pgp_stdin_passphrase_arg) + def plain(self): + """ + text/plain + """ + return encodedMIMEText(self.body) + def sign(self, header, passphrase=None): + """ + multipart/signed + +-> text/plain (body) + +-> application/pgp-signature (signature) + """ + passphrase,pass_arg = self.passphrase_arg(passphrase) + body = self.clearBodyPart() + bfile = tempfile.NamedTemporaryFile() + bfile.write(flatten(body)) + bfile.flush() + + args = replace(pgp_sign_command, 'f', bfile.name) + if PGP_SIGN_AS == None: + pgp_sign_as = '<%s>' % source_email(header) + else: + pgp_sign_as = PGP_SIGN_AS + args = replace(args, 'a', pgp_sign_as) + args = replace(args, 'p', pass_arg) + status,output,error = execute(args, stdin=passphrase) + signature = output + + sig = MIMEApplication(_data=signature, + _subtype='pgp-signature; name="signature.asc"', + _encoder=encode_7or8bit) + sig['Content-Description'] = 'signature' + sig.set_charset('us-ascii') + + msg = MIMEMultipart('signed', micalg='pgp-sha1', + protocol='application/pgp-signature') + msg.attach(body) + msg.attach(sig) + + msg['Content-Disposition'] = 'inline' + return msg + def encrypt(self, header, passphrase=None): + """ + multipart/encrypted + +-> application/pgp-encrypted (control information) + +-> application/octet-stream (body) + """ + body = self.clearBodyPart() + bfile = tempfile.NamedTemporaryFile() + bfile.write(flatten(body)) + bfile.flush() + + recipients = [replace(pgp_recipient_arg, 'r', recipient) + for recipient in target_emails(header)] + recipient_string = ' '.join(recipients) + args = replace(pgp_encrypt_only_command, 'R', recipient_string) + args = replace(args, 'f', bfile.name) + if PGP_SIGN_AS == None: + pgp_sign_as = '<%s>' % source_email(header) + else: + pgp_sign_as = PGP_SIGN_AS + args = replace(args, 'a', pgp_sign_as) + status,output,error = execute(args) + encrypted = output + + enc = MIMEApplication(_data=encrypted, _subtype='octet-stream', + _encoder=encode_7or8bit) + enc.set_charset('us-ascii') + + control = MIMEApplication(_data='Version: 1\n', _subtype='pgp-encrypted', + _encoder=encode_7or8bit) + + msg = MIMEMultipart('encrypted', micalg='pgp-sha1', + protocol='application/pgp-encrypted') + msg.attach(control) + msg.attach(enc) + + msg['Content-Disposition'] = 'inline' + return msg + def signAndEncrypt(self, header, passphrase=None): + """ + multipart/encrypted + +-> application/pgp-encrypted (control information) + +-> application/octet-stream (body) + """ + passphrase,pass_arg = self.passphrase_arg(passphrase) + body = self.sign(header, passphrase) + body.__delitem__('Bcc') + bfile = tempfile.NamedTemporaryFile() + bfile.write(flatten(body)) + bfile.flush() + + recipients = [replace(pgp_recipient_arg, 'r', recipient) + for recipient in target_emails(header)] + recipient_string = ' '.join(recipients) + args = replace(pgp_encrypt_only_command, 'R', recipient_string) + args = replace(args, 'f', bfile.name) + if PGP_SIGN_AS == None: + pgp_sign_as = '<%s>' % source_email(header) + else: + pgp_sign_as = PGP_SIGN_AS + args = replace(args, 'a', pgp_sign_as) + args = replace(args, 'p', pass_arg) + status,output,error = execute(args, stdin=passphrase) + encrypted = output + + enc = MIMEApplication(_data=encrypted, _subtype='octet-stream', + _encoder=encode_7or8bit) + enc.set_charset('us-ascii') + + control = MIMEApplication(_data='Version: 1\n', + _subtype='pgp-encrypted', + _encoder=encode_7or8bit) + + msg = MIMEMultipart('encrypted', micalg='pgp-sha1', + protocol='application/pgp-encrypted') + msg.attach(control) + msg.attach(enc) + + msg['Content-Disposition'] = 'inline' + return msg + +def test(): + import doctest + doctest.testmod() + + +if __name__ == '__main__': + from optparse import OptionParser + + parser = OptionParser(usage=usage) + parser.add_option('-t', '--test', dest='test', action='store_true', + help='Run doctests and exit') + + parser.add_option('-H', '--header-file', dest='header_filename', + help='file containing email header', metavar='FILE') + parser.add_option('-B', '--body-file', dest='body_filename', + help='file containing email body', metavar='FILE') + + parser.add_option('-P', '--passphrase-file', dest='passphrase_file', + help='file containing gpg passphrase', metavar='FILE') + parser.add_option('-p', '--passphrase-fd', dest='passphrase_fd', + help='file descriptor from which to read gpg passphrase (0 for stdin)', + type="int", metavar='DESCRIPTOR') + + parser.add_option('--mode', dest='mode', default='sign', + help="One of 'sign', 'encrypt', 'sign-encrypt', or 'plain'. Defaults to %default.", + metavar='MODE') + + parser.add_option('-a', '--sign-as', dest='sign_as', + help="The gpg key to sign with (gpg's -u/--local-user)", + metavar='KEY') + + parser.add_option('--output', dest='output', action='store_true', + help="Don't mail the generated message, print it to stdout instead.") + + (options, args) = parser.parse_args() + + stdin_used = False + + if options.passphrase_file != None: + PASSPHRASE = file(options.passphrase_file, 'r').read() + elif options.passphrase_fd != None: + if options.passphrase_fd == 0: + stdin_used = True + PASSPHRASE = sys.stdin.read() + else: + PASSPHRASE = os.read(options.passphrase_fd) + + if options.sign_as: + PGP_SIGN_AS = options.sign_as + + if options.test == True: + test() + sys.exit(0) + + header = None + if options.header_filename != None: + if options.header_filename == '-': + assert stdin_used == False + stdin_used = True + header = sys.stdin.read() + else: + header = file(options.header_filename, 'r').read() + if header == None: + raise Exception, "missing header" + headermsg = header_from_text(header) + body = None + if options.body_filename != None: + if options.body_filename == '-': + assert stdin_used == False + stdin_used = True + body = sys.stdin.read() + else: + body = file(options.body_filename, 'r').read() + if body == None: + raise Exception, "missing body" + + m = PGPMimeMessageFactory(body) + if options.mode == "sign": + bodymsg = m.sign(header) + elif options.mode == "encrypt": + bodymsg = m.encrypt(header) + elif options.mode == "sign-encrypt": + bodymsg = m.signAndEncrypt(header) + elif options.mode == "plain": + bodymsg = m.plain() + else: + print "Unrecognized mode '%s'" % options.mode + + message = attach_root(headermsg, bodymsg) + if options.output == True: + message = flatten(message) + print message + else: + mail(message, sendmail) diff --git a/.be/bugs/04edb940-06dd-4ded-8697-156d54a1d875/comments/e3389187-1e84-43d5-b40b-26f53090edff/body b/interfaces/web/.be/bugs/04edb940-06dd-4ded-8697-156d54a1d875/comments/e3389187-1e84-43d5-b40b-26f53090edff/body index 49a1e50..49a1e50 100644 --- a/.be/bugs/04edb940-06dd-4ded-8697-156d54a1d875/comments/e3389187-1e84-43d5-b40b-26f53090edff/body +++ b/interfaces/web/.be/bugs/04edb940-06dd-4ded-8697-156d54a1d875/comments/e3389187-1e84-43d5-b40b-26f53090edff/body diff --git a/.be/bugs/04edb940-06dd-4ded-8697-156d54a1d875/comments/e3389187-1e84-43d5-b40b-26f53090edff/values b/interfaces/web/.be/bugs/04edb940-06dd-4ded-8697-156d54a1d875/comments/e3389187-1e84-43d5-b40b-26f53090edff/values index bf58725..bf58725 100644 --- a/.be/bugs/04edb940-06dd-4ded-8697-156d54a1d875/comments/e3389187-1e84-43d5-b40b-26f53090edff/values +++ b/interfaces/web/.be/bugs/04edb940-06dd-4ded-8697-156d54a1d875/comments/e3389187-1e84-43d5-b40b-26f53090edff/values diff --git a/.be/bugs/04edb940-06dd-4ded-8697-156d54a1d875/values b/interfaces/web/.be/bugs/04edb940-06dd-4ded-8697-156d54a1d875/values index 256574b..256574b 100644 --- a/.be/bugs/04edb940-06dd-4ded-8697-156d54a1d875/values +++ b/interfaces/web/.be/bugs/04edb940-06dd-4ded-8697-156d54a1d875/values diff --git a/.be/bugs/0a234f51-2fdf-4001-a04f-b7e02c2fa47b/values b/interfaces/web/.be/bugs/0a234f51-2fdf-4001-a04f-b7e02c2fa47b/values index b911874..b911874 100644 --- a/.be/bugs/0a234f51-2fdf-4001-a04f-b7e02c2fa47b/values +++ b/interfaces/web/.be/bugs/0a234f51-2fdf-4001-a04f-b7e02c2fa47b/values diff --git a/.be/bugs/0be47243-c172-4de9-b71b-d5dea60f91d5/values b/interfaces/web/.be/bugs/0be47243-c172-4de9-b71b-d5dea60f91d5/values index 0626932..0626932 100644 --- a/.be/bugs/0be47243-c172-4de9-b71b-d5dea60f91d5/values +++ b/interfaces/web/.be/bugs/0be47243-c172-4de9-b71b-d5dea60f91d5/values diff --git a/.be/bugs/171819aa-c092-4ddf-ace3-797635fa2572/values b/interfaces/web/.be/bugs/171819aa-c092-4ddf-ace3-797635fa2572/values index 361cfa7..361cfa7 100644 --- a/.be/bugs/171819aa-c092-4ddf-ace3-797635fa2572/values +++ b/interfaces/web/.be/bugs/171819aa-c092-4ddf-ace3-797635fa2572/values diff --git a/.be/bugs/24555ea1-76b5-40a8-918f-115a28f5f36a/values b/interfaces/web/.be/bugs/24555ea1-76b5-40a8-918f-115a28f5f36a/values index 72629bc..72629bc 100644 --- a/.be/bugs/24555ea1-76b5-40a8-918f-115a28f5f36a/values +++ b/interfaces/web/.be/bugs/24555ea1-76b5-40a8-918f-115a28f5f36a/values diff --git a/.be/bugs/312fb152-0155-45c1-9d4d-f49dd5816fbb/values b/interfaces/web/.be/bugs/312fb152-0155-45c1-9d4d-f49dd5816fbb/values index 2795a3a..2795a3a 100644 --- a/.be/bugs/312fb152-0155-45c1-9d4d-f49dd5816fbb/values +++ b/interfaces/web/.be/bugs/312fb152-0155-45c1-9d4d-f49dd5816fbb/values diff --git a/.be/bugs/35b962a0-a64a-4b5c-82c5-ea740e8a6322/values b/interfaces/web/.be/bugs/35b962a0-a64a-4b5c-82c5-ea740e8a6322/values index 134df9b..134df9b 100644 --- a/.be/bugs/35b962a0-a64a-4b5c-82c5-ea740e8a6322/values +++ b/interfaces/web/.be/bugs/35b962a0-a64a-4b5c-82c5-ea740e8a6322/values diff --git a/.be/bugs/42716dc2-6201-4537-b5fd-e1280812a53d/values b/interfaces/web/.be/bugs/42716dc2-6201-4537-b5fd-e1280812a53d/values index c193f8f..c193f8f 100644 --- a/.be/bugs/42716dc2-6201-4537-b5fd-e1280812a53d/values +++ b/interfaces/web/.be/bugs/42716dc2-6201-4537-b5fd-e1280812a53d/values diff --git a/.be/bugs/4286c0f8-5703-4bc1-b256-414dc408f067/values b/interfaces/web/.be/bugs/4286c0f8-5703-4bc1-b256-414dc408f067/values index bc901f9..bc901f9 100644 --- a/.be/bugs/4286c0f8-5703-4bc1-b256-414dc408f067/values +++ b/interfaces/web/.be/bugs/4286c0f8-5703-4bc1-b256-414dc408f067/values diff --git a/.be/bugs/528b2e84-a944-4628-a18f-cc1def1c7e16/values b/interfaces/web/.be/bugs/528b2e84-a944-4628-a18f-cc1def1c7e16/values index 19aafd2..19aafd2 100644 --- a/.be/bugs/528b2e84-a944-4628-a18f-cc1def1c7e16/values +++ b/interfaces/web/.be/bugs/528b2e84-a944-4628-a18f-cc1def1c7e16/values diff --git a/.be/bugs/52a15454-196c-4990-b55d-be2e37d575c3/values b/interfaces/web/.be/bugs/52a15454-196c-4990-b55d-be2e37d575c3/values index a3cb0aa..a3cb0aa 100644 --- a/.be/bugs/52a15454-196c-4990-b55d-be2e37d575c3/values +++ b/interfaces/web/.be/bugs/52a15454-196c-4990-b55d-be2e37d575c3/values diff --git a/.be/bugs/545311df-8c88-4504-9f83-11d7c5d8aa50/comments/88d54d29-7312-4bb3-bc50-1970bdb2bb0e/body b/interfaces/web/.be/bugs/545311df-8c88-4504-9f83-11d7c5d8aa50/comments/88d54d29-7312-4bb3-bc50-1970bdb2bb0e/body index 5becd48..5becd48 100644 --- a/.be/bugs/545311df-8c88-4504-9f83-11d7c5d8aa50/comments/88d54d29-7312-4bb3-bc50-1970bdb2bb0e/body +++ b/interfaces/web/.be/bugs/545311df-8c88-4504-9f83-11d7c5d8aa50/comments/88d54d29-7312-4bb3-bc50-1970bdb2bb0e/body diff --git a/.be/bugs/545311df-8c88-4504-9f83-11d7c5d8aa50/comments/88d54d29-7312-4bb3-bc50-1970bdb2bb0e/values b/interfaces/web/.be/bugs/545311df-8c88-4504-9f83-11d7c5d8aa50/comments/88d54d29-7312-4bb3-bc50-1970bdb2bb0e/values index 41f53c6..41f53c6 100644 --- a/.be/bugs/545311df-8c88-4504-9f83-11d7c5d8aa50/comments/88d54d29-7312-4bb3-bc50-1970bdb2bb0e/values +++ b/interfaces/web/.be/bugs/545311df-8c88-4504-9f83-11d7c5d8aa50/comments/88d54d29-7312-4bb3-bc50-1970bdb2bb0e/values diff --git a/.be/bugs/545311df-8c88-4504-9f83-11d7c5d8aa50/values b/interfaces/web/.be/bugs/545311df-8c88-4504-9f83-11d7c5d8aa50/values index 851021e..851021e 100644 --- a/.be/bugs/545311df-8c88-4504-9f83-11d7c5d8aa50/values +++ b/interfaces/web/.be/bugs/545311df-8c88-4504-9f83-11d7c5d8aa50/values diff --git a/.be/bugs/55e76f74-37fb-4254-8498-54b703ba54f6/values b/interfaces/web/.be/bugs/55e76f74-37fb-4254-8498-54b703ba54f6/values index cded1dc..cded1dc 100644 --- a/.be/bugs/55e76f74-37fb-4254-8498-54b703ba54f6/values +++ b/interfaces/web/.be/bugs/55e76f74-37fb-4254-8498-54b703ba54f6/values diff --git a/.be/bugs/615ad650-9fb9-4026-9779-58d42b4e528e/values b/interfaces/web/.be/bugs/615ad650-9fb9-4026-9779-58d42b4e528e/values index 56ae9a1..56ae9a1 100644 --- a/.be/bugs/615ad650-9fb9-4026-9779-58d42b4e528e/values +++ b/interfaces/web/.be/bugs/615ad650-9fb9-4026-9779-58d42b4e528e/values diff --git a/.be/bugs/63619cf7-89eb-4e64-91e9-b8a73d2a6c72/values b/interfaces/web/.be/bugs/63619cf7-89eb-4e64-91e9-b8a73d2a6c72/values index cb7a38e..cb7a38e 100644 --- a/.be/bugs/63619cf7-89eb-4e64-91e9-b8a73d2a6c72/values +++ b/interfaces/web/.be/bugs/63619cf7-89eb-4e64-91e9-b8a73d2a6c72/values diff --git a/.be/bugs/700cd3f1-70b6-4887-89a2-c1d039732add/values b/interfaces/web/.be/bugs/700cd3f1-70b6-4887-89a2-c1d039732add/values index 71ab0a3..71ab0a3 100644 --- a/.be/bugs/700cd3f1-70b6-4887-89a2-c1d039732add/values +++ b/interfaces/web/.be/bugs/700cd3f1-70b6-4887-89a2-c1d039732add/values diff --git a/.be/bugs/81f69fbd-1ca5-4f89-a6e1-79ea1e6bf4d9/values b/interfaces/web/.be/bugs/81f69fbd-1ca5-4f89-a6e1-79ea1e6bf4d9/values index dcaa6b3..dcaa6b3 100644 --- a/.be/bugs/81f69fbd-1ca5-4f89-a6e1-79ea1e6bf4d9/values +++ b/interfaces/web/.be/bugs/81f69fbd-1ca5-4f89-a6e1-79ea1e6bf4d9/values diff --git a/.be/bugs/866cba32-4347-4f51-9b1d-69454638ca78/comments/738f9826-57b6-43d6-a0cb-0dfeeb185b96/body b/interfaces/web/.be/bugs/866cba32-4347-4f51-9b1d-69454638ca78/comments/738f9826-57b6-43d6-a0cb-0dfeeb185b96/body index f75f8b7..f75f8b7 100644 --- a/.be/bugs/866cba32-4347-4f51-9b1d-69454638ca78/comments/738f9826-57b6-43d6-a0cb-0dfeeb185b96/body +++ b/interfaces/web/.be/bugs/866cba32-4347-4f51-9b1d-69454638ca78/comments/738f9826-57b6-43d6-a0cb-0dfeeb185b96/body diff --git a/.be/bugs/866cba32-4347-4f51-9b1d-69454638ca78/comments/738f9826-57b6-43d6-a0cb-0dfeeb185b96/values b/interfaces/web/.be/bugs/866cba32-4347-4f51-9b1d-69454638ca78/comments/738f9826-57b6-43d6-a0cb-0dfeeb185b96/values index 7927b05..7927b05 100644 --- a/.be/bugs/866cba32-4347-4f51-9b1d-69454638ca78/comments/738f9826-57b6-43d6-a0cb-0dfeeb185b96/values +++ b/interfaces/web/.be/bugs/866cba32-4347-4f51-9b1d-69454638ca78/comments/738f9826-57b6-43d6-a0cb-0dfeeb185b96/values diff --git a/.be/bugs/866cba32-4347-4f51-9b1d-69454638ca78/values b/interfaces/web/.be/bugs/866cba32-4347-4f51-9b1d-69454638ca78/values index f9e0a64..f9e0a64 100644 --- a/.be/bugs/866cba32-4347-4f51-9b1d-69454638ca78/values +++ b/interfaces/web/.be/bugs/866cba32-4347-4f51-9b1d-69454638ca78/values diff --git a/.be/bugs/870d5dbe-6449-4ec4-ae6f-e84bebadbce0/values b/interfaces/web/.be/bugs/870d5dbe-6449-4ec4-ae6f-e84bebadbce0/values index e91a4cf..e91a4cf 100644 --- a/.be/bugs/870d5dbe-6449-4ec4-ae6f-e84bebadbce0/values +++ b/interfaces/web/.be/bugs/870d5dbe-6449-4ec4-ae6f-e84bebadbce0/values diff --git a/.be/bugs/8cb9045c-7266-4c40-9a76-65f3c5d5bb60/values b/interfaces/web/.be/bugs/8cb9045c-7266-4c40-9a76-65f3c5d5bb60/values index b8403eb..b8403eb 100644 --- a/.be/bugs/8cb9045c-7266-4c40-9a76-65f3c5d5bb60/values +++ b/interfaces/web/.be/bugs/8cb9045c-7266-4c40-9a76-65f3c5d5bb60/values diff --git a/.be/bugs/984472f6-98f5-48fc-b521-70a1e5f60614/values b/interfaces/web/.be/bugs/984472f6-98f5-48fc-b521-70a1e5f60614/values index 21d3cef..21d3cef 100644 --- a/.be/bugs/984472f6-98f5-48fc-b521-70a1e5f60614/values +++ b/interfaces/web/.be/bugs/984472f6-98f5-48fc-b521-70a1e5f60614/values diff --git a/.be/bugs/9bc14860-b2bb-4442-85ea-0b8e7083457b/values b/interfaces/web/.be/bugs/9bc14860-b2bb-4442-85ea-0b8e7083457b/values index b01cd70..b01cd70 100644 --- a/.be/bugs/9bc14860-b2bb-4442-85ea-0b8e7083457b/values +++ b/interfaces/web/.be/bugs/9bc14860-b2bb-4442-85ea-0b8e7083457b/values diff --git a/.be/bugs/ac72991a-72e5-4b14-b53c-0fa38d0f31bb/values b/interfaces/web/.be/bugs/ac72991a-72e5-4b14-b53c-0fa38d0f31bb/values index b4de064..b4de064 100644 --- a/.be/bugs/ac72991a-72e5-4b14-b53c-0fa38d0f31bb/values +++ b/interfaces/web/.be/bugs/ac72991a-72e5-4b14-b53c-0fa38d0f31bb/values diff --git a/.be/bugs/bef126a0-27be-402f-84fa-85f6342c97c0/values b/interfaces/web/.be/bugs/bef126a0-27be-402f-84fa-85f6342c97c0/values index 94d96d7..94d96d7 100644 --- a/.be/bugs/bef126a0-27be-402f-84fa-85f6342c97c0/values +++ b/interfaces/web/.be/bugs/bef126a0-27be-402f-84fa-85f6342c97c0/values diff --git a/.be/bugs/c7251ff9-24e4-402d-8d4e-605a78b9a91d/values b/interfaces/web/.be/bugs/c7251ff9-24e4-402d-8d4e-605a78b9a91d/values index 4c1c8cb..4c1c8cb 100644 --- a/.be/bugs/c7251ff9-24e4-402d-8d4e-605a78b9a91d/values +++ b/interfaces/web/.be/bugs/c7251ff9-24e4-402d-8d4e-605a78b9a91d/values diff --git a/.be/bugs/cfb52b6c-d1a6-4018-a255-27cc1c878193/values b/interfaces/web/.be/bugs/cfb52b6c-d1a6-4018-a255-27cc1c878193/values index 49fa830..49fa830 100644 --- a/.be/bugs/cfb52b6c-d1a6-4018-a255-27cc1c878193/values +++ b/interfaces/web/.be/bugs/cfb52b6c-d1a6-4018-a255-27cc1c878193/values diff --git a/.be/bugs/d63d0bdd-e025-4f7c-9fcf-47a71de6d4d4/values b/interfaces/web/.be/bugs/d63d0bdd-e025-4f7c-9fcf-47a71de6d4d4/values index c100da5..c100da5 100644 --- a/.be/bugs/d63d0bdd-e025-4f7c-9fcf-47a71de6d4d4/values +++ b/interfaces/web/.be/bugs/d63d0bdd-e025-4f7c-9fcf-47a71de6d4d4/values diff --git a/.be/bugs/dd7aa57c-f184-495a-8520-2676c1066fb4/comments/24aab4bf-b525-48d6-9666-626e3ddcecf7/body b/interfaces/web/.be/bugs/dd7aa57c-f184-495a-8520-2676c1066fb4/comments/24aab4bf-b525-48d6-9666-626e3ddcecf7/body index 6447d18..6447d18 100644 --- a/.be/bugs/dd7aa57c-f184-495a-8520-2676c1066fb4/comments/24aab4bf-b525-48d6-9666-626e3ddcecf7/body +++ b/interfaces/web/.be/bugs/dd7aa57c-f184-495a-8520-2676c1066fb4/comments/24aab4bf-b525-48d6-9666-626e3ddcecf7/body diff --git a/.be/bugs/dd7aa57c-f184-495a-8520-2676c1066fb4/comments/24aab4bf-b525-48d6-9666-626e3ddcecf7/values b/interfaces/web/.be/bugs/dd7aa57c-f184-495a-8520-2676c1066fb4/comments/24aab4bf-b525-48d6-9666-626e3ddcecf7/values index c68151d..c68151d 100644 --- a/.be/bugs/dd7aa57c-f184-495a-8520-2676c1066fb4/comments/24aab4bf-b525-48d6-9666-626e3ddcecf7/values +++ b/interfaces/web/.be/bugs/dd7aa57c-f184-495a-8520-2676c1066fb4/comments/24aab4bf-b525-48d6-9666-626e3ddcecf7/values diff --git a/.be/bugs/dd7aa57c-f184-495a-8520-2676c1066fb4/values b/interfaces/web/.be/bugs/dd7aa57c-f184-495a-8520-2676c1066fb4/values index 18dd9a3..18dd9a3 100644 --- a/.be/bugs/dd7aa57c-f184-495a-8520-2676c1066fb4/values +++ b/interfaces/web/.be/bugs/dd7aa57c-f184-495a-8520-2676c1066fb4/values diff --git a/.be/bugs/decc6e78-a3db-4cd3-ad23-2bf8ed77cb0d/values b/interfaces/web/.be/bugs/decc6e78-a3db-4cd3-ad23-2bf8ed77cb0d/values index 96f52d3..96f52d3 100644 --- a/.be/bugs/decc6e78-a3db-4cd3-ad23-2bf8ed77cb0d/values +++ b/interfaces/web/.be/bugs/decc6e78-a3db-4cd3-ad23-2bf8ed77cb0d/values diff --git a/.be/bugs/e22a9048-9a97-41b1-91a2-d4178c674b37/values b/interfaces/web/.be/bugs/e22a9048-9a97-41b1-91a2-d4178c674b37/values index 63e9e8c..63e9e8c 100644 --- a/.be/bugs/e22a9048-9a97-41b1-91a2-d4178c674b37/values +++ b/interfaces/web/.be/bugs/e22a9048-9a97-41b1-91a2-d4178c674b37/values diff --git a/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/aea21508-69c2-4d6b-ada1-4fbadac14c56/body b/interfaces/web/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/aea21508-69c2-4d6b-ada1-4fbadac14c56/body index d13b1b7..d13b1b7 100644 --- a/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/aea21508-69c2-4d6b-ada1-4fbadac14c56/body +++ b/interfaces/web/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/aea21508-69c2-4d6b-ada1-4fbadac14c56/body diff --git a/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/aea21508-69c2-4d6b-ada1-4fbadac14c56/values b/interfaces/web/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/aea21508-69c2-4d6b-ada1-4fbadac14c56/values index 4f055dd..4f055dd 100644 --- a/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/aea21508-69c2-4d6b-ada1-4fbadac14c56/values +++ b/interfaces/web/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/aea21508-69c2-4d6b-ada1-4fbadac14c56/values diff --git a/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/d5ffa1c4-f435-4a9a-99f3-2a7bc3072051/body b/interfaces/web/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/d5ffa1c4-f435-4a9a-99f3-2a7bc3072051/body index 8598e67..8598e67 100644 --- a/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/d5ffa1c4-f435-4a9a-99f3-2a7bc3072051/body +++ b/interfaces/web/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/d5ffa1c4-f435-4a9a-99f3-2a7bc3072051/body diff --git a/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/d5ffa1c4-f435-4a9a-99f3-2a7bc3072051/values b/interfaces/web/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/d5ffa1c4-f435-4a9a-99f3-2a7bc3072051/values index f541cad..f541cad 100644 --- a/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/d5ffa1c4-f435-4a9a-99f3-2a7bc3072051/values +++ b/interfaces/web/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/d5ffa1c4-f435-4a9a-99f3-2a7bc3072051/values diff --git a/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/f1fd8249-ded3-4e3c-a6ef-967d0a0edcd9/body b/interfaces/web/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/f1fd8249-ded3-4e3c-a6ef-967d0a0edcd9/body index 20dbfd2..20dbfd2 100644 --- a/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/f1fd8249-ded3-4e3c-a6ef-967d0a0edcd9/body +++ b/interfaces/web/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/f1fd8249-ded3-4e3c-a6ef-967d0a0edcd9/body diff --git a/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/f1fd8249-ded3-4e3c-a6ef-967d0a0edcd9/values b/interfaces/web/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/f1fd8249-ded3-4e3c-a6ef-967d0a0edcd9/values index 759a973..759a973 100644 --- a/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/f1fd8249-ded3-4e3c-a6ef-967d0a0edcd9/values +++ b/interfaces/web/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/comments/f1fd8249-ded3-4e3c-a6ef-967d0a0edcd9/values diff --git a/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/values b/interfaces/web/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/values index 017229d..017229d 100644 --- a/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/values +++ b/interfaces/web/.be/bugs/e645d562-6f84-4df2-b8ee-86ef42546c16/values diff --git a/.be/bugs/fd96c69d-6f78-4c0c-af6e-e01e9b8516d3/values b/interfaces/web/.be/bugs/fd96c69d-6f78-4c0c-af6e-e01e9b8516d3/values index 837213e..837213e 100644 --- a/.be/bugs/fd96c69d-6f78-4c0c-af6e-e01e9b8516d3/values +++ b/interfaces/web/.be/bugs/fd96c69d-6f78-4c0c-af6e-e01e9b8516d3/values diff --git a/.be/settings b/interfaces/web/.be/settings index 8ef3e76..8ef3e76 100644 --- a/.be/settings +++ b/interfaces/web/.be/settings diff --git a/interfaces/web/.be/version b/interfaces/web/.be/version new file mode 100644 index 0000000..990837e --- /dev/null +++ b/interfaces/web/.be/version @@ -0,0 +1 @@ +Bugs Everywhere Tree 1 0 diff --git a/.hgignore b/interfaces/web/.hgignore index a0e81b7..a0e81b7 100644 --- a/.hgignore +++ b/interfaces/web/.hgignore diff --git a/.hgtags b/interfaces/web/.hgtags index eeea432..eeea432 100644 --- a/.hgtags +++ b/interfaces/web/.hgtags diff --git a/LICENSE b/interfaces/web/LICENSE index 44f0935..44f0935 100644 --- a/LICENSE +++ b/interfaces/web/LICENSE diff --git a/interfaces/web/README b/interfaces/web/README new file mode 100644 index 0000000..6bd04e5 --- /dev/null +++ b/interfaces/web/README @@ -0,0 +1,20 @@ +-*- markdown -*- + +Cherry Flavored Bugs Everywhere +=============================== + +CFBE is a quick web interface to [BugsEverywhere](http://bugseverywhere.org/). It's still very much a work-in-progress. + +Installing +---------- + +I intend to streamline the installation once I'm satisfied with the interface itself. For now, the install process goes something like this: + +* Install [CherryPy](http://cherrypy.org/) if you don't have it. +* Install [Jinja2](http://jinja.pocoo.org/2/) if you don't have it. +* Install [BugsEverywhere](http://bugseverywhere.org/) if you don't have it. +* Download a zip/tar of CFBE (or hg clone) from the [Mercurial repository](http://bitbucket.org/sjl/cherryflavoredbugseverywhere/). +* Unzip (if you grabbed a zip) and put the folder in your Python site-packages directory (or put it anywhere and symlink it to site-packages). +* Symlink `site-packages/cherryflavoredbugseverywhere/cfbe.py` to `/usr/local/bin/cfbe` +* Use `cfbe [project_root]` to start up the web interface for that project. +* Visit http://localhost:8080/ in a browser. diff --git a/interfaces/web/__init__.py b/interfaces/web/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/interfaces/web/__init__.py diff --git a/cfbe.py b/interfaces/web/cfbe.py index 63fbc7e..63fbc7e 100755 --- a/cfbe.py +++ b/interfaces/web/cfbe.py diff --git a/static/scripts/jquery.corners.min.js b/interfaces/web/static/scripts/jquery.corners.min.js index 0b2f979..0b2f979 100644 --- a/static/scripts/jquery.corners.min.js +++ b/interfaces/web/static/scripts/jquery.corners.min.js diff --git a/static/style/aal.css b/interfaces/web/static/style/aal.css index 9bad98f..9bad98f 100644 --- a/static/style/aal.css +++ b/interfaces/web/static/style/aal.css diff --git a/static/style/cfbe.css b/interfaces/web/static/style/cfbe.css index c5f726e..c5f726e 100644 --- a/static/style/cfbe.css +++ b/interfaces/web/static/style/cfbe.css diff --git a/templates/base.html b/interfaces/web/templates/base.html index 8f22d73..8f22d73 100644 --- a/templates/base.html +++ b/interfaces/web/templates/base.html diff --git a/templates/bug.html b/interfaces/web/templates/bug.html index 4d15536..4d15536 100644 --- a/templates/bug.html +++ b/interfaces/web/templates/bug.html diff --git a/templates/list.html b/interfaces/web/templates/list.html index 1d409f7..1d409f7 100644 --- a/templates/list.html +++ b/interfaces/web/templates/list.html diff --git a/web.py b/interfaces/web/web.py index 9155c97..9155c97 100644 --- a/web.py +++ b/interfaces/web/web.py diff --git a/libbe/__init__.py b/libbe/__init__.py new file mode 100644 index 0000000..d32716f --- /dev/null +++ b/libbe/__init__.py @@ -0,0 +1,53 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""The libbe module does all the legwork for bugs-everywhere_ (BE). + +.. _bugs-everywhere: http://bugseverywhere.org + +To facilitate faster loading, submodules are not imported by default. +The available submodules are: + +* :mod:`libbe.bugdir` +* :mod:`libbe.bug` +* :mod:`libbe.comment` +* :mod:`libbe.command` +* :mod:`libbe.diff` +* :mod:`libbe.error` +* :mod:`libbe.storage` +* :mod:`libbe.ui` +* :mod:`libbe.util` +* :mod:`libbe.version` +* :mod:`libbe._version` +""" + +TESTING = False +"""Flag controlling test-suite generation. + +To reduce module load time, test suite generation is turned of by +default. If you *do* want to generate the test suites, set +``TESTING=True`` before loading any :mod:`libbe` submodules. + +Examples +-------- + +>>> import libbe +>>> libbe.TESTING = True +>>> import libbe.bugdir +>>> 'SimpleBugDir' in dir(libbe.bugdir) +True +""" diff --git a/libbe/bug.py b/libbe/bug.py new file mode 100644 index 0000000..8bf32dd --- /dev/null +++ b/libbe/bug.py @@ -0,0 +1,853 @@ +# Copyright (C) 2008-2010 Gianluca Montecchi <gian@grys.it> +# Thomas Habets <thomas@habets.pp.se> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Define the :class:`Bug` class for representing bugs. +""" + +import copy +import os +import os.path +import errno +import sys +import time +import types +try: # import core module, Python >= 2.5 + from xml.etree import ElementTree +except ImportError: # look for non-core module + from elementtree import ElementTree +import xml.sax.saxutils + +import libbe +import libbe.util.id +from libbe.storage.util.properties import Property, doc_property, \ + local_property, defaulting_property, checked_property, cached_property, \ + primed_property, change_hook_property, settings_property +import libbe.storage.util.settings_object as settings_object +import libbe.storage.util.mapfile as mapfile +import libbe.comment as comment +import libbe.util.utility as utility + +if libbe.TESTING == True: + import doctest + + +class DiskAccessRequired (Exception): + def __init__(self, goal): + msg = "Cannot %s without accessing the disk" % goal + Exception.__init__(self, msg) + +### 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/ + +# in order of increasing severity. (name, description) pairs +severity_def = ( + ("target", "The issue is a target or milestone, not a bug."), + ("wishlist","A feature that could improve usefulness, but not a bug."), + ("minor","The standard bug level."), + ("serious","A bug that requires workarounds."), + ("critical","A bug that prevents some features from working at all."), + ("fatal","A bug that makes the package unusable.")) + +# in order of increasing resolution +# roughly following http://www.bugzilla.org/docs/3.2/en/html/lifecycle.html +active_status_def = ( + ("unconfirmed","A possible bug which lacks independent existance confirmation."), + ("open","A working bug that has not been assigned to a developer."), + ("assigned","A working bug that has been assigned to a developer."), + ("test","The code has been adjusted, but the fix is still being tested.")) +inactive_status_def = ( + ("closed", "The bug is no longer relevant."), + ("fixed", "The bug should no longer occur."), + ("wontfix","It's not a bug, it's a feature.")) + + +### Convert the description tuples to more useful formats + +severity_values = () +severity_description = {} +severity_index = {} +def load_severities(severity_def): + global severity_values + global severity_description + global severity_index + if severity_def == None: + return + severity_values = tuple([val for val,description in severity_def]) + severity_description = dict(severity_def) + severity_index = {} + for i,severity in enumerate(severity_values): + severity_index[severity] = i +load_severities(severity_def) + +active_status_values = [] +inactive_status_values = [] +status_values = [] +status_description = {} +status_index = {} +def load_status(active_status_def, inactive_status_def): + global active_status_values + global inactive_status_values + global status_values + global status_description + global status_index + if active_status_def == None: + active_status_def = globals()["active_status_def"] + if inactive_status_def == None: + inactive_status_def = globals()["inactive_status_def"] + active_status_values = tuple([val for val,description in active_status_def]) + inactive_status_values = tuple([val for val,description in inactive_status_def]) + status_values = active_status_values + inactive_status_values + status_description = dict(tuple(active_status_def) + tuple(inactive_status_def)) + status_index = {} + for i,status in enumerate(status_values): + status_index[status] = i +load_status(active_status_def, inactive_status_def) + + +class Bug (settings_object.SavedSettingsObject): + """A bug (or issue) is a place to store attributes and attach + :class:`~libbe.comment.Comment`\s. In mailing-list terms, a bug is + analogous to a thread. Bugs are normally stored in + :class:`~libbe.bugdir.BugDir`\s. + + >>> b = Bug() + >>> print b.status + open + >>> print b.severity + minor + + There are two formats for time, int and string. Setting either + one will adjust the other appropriately. The string form is the + one stored in the bug's settings file on disk. + + >>> print type(b.time) + <type 'int'> + >>> print type(b.time_string) + <type 'str'> + >>> b.time = 0 + >>> print b.time_string + Thu, 01 Jan 1970 00:00:00 +0000 + >>> b.time_string="Thu, 01 Jan 1970 00:01:00 +0000" + >>> b.time + 60 + >>> print b.settings["time"] + Thu, 01 Jan 1970 00:01:00 +0000 + """ + settings_properties = [] + required_saved_properties = [] + _prop_save_settings = settings_object.prop_save_settings + _prop_load_settings = settings_object.prop_load_settings + def _versioned_property(settings_properties=settings_properties, + required_saved_properties=required_saved_properties, + **kwargs): + if "settings_properties" not in kwargs: + kwargs["settings_properties"] = settings_properties + if "required_saved_properties" not in kwargs: + kwargs["required_saved_properties"]=required_saved_properties + return settings_object.versioned_property(**kwargs) + + @_versioned_property(name="severity", + doc="A measure of the bug's importance", + default="minor", + check_fn=lambda s: s in severity_values, + require_save=True) + def severity(): return {} + + @_versioned_property(name="status", + doc="The bug's current status", + default="open", + check_fn=lambda s: s in status_values, + require_save=True) + def status(): return {} + + @property + def active(self): + return self.status in active_status_values + + @_versioned_property(name="creator", + doc="The user who entered the bug into the system") + def creator(): return {} + + @_versioned_property(name="reporter", + doc="The user who reported the bug") + def reporter(): return {} + + @_versioned_property(name="assigned", + doc="The developer in charge of the bug") + def assigned(): return {} + + @_versioned_property(name="time", + doc="An RFC 2822 timestamp for bug creation") + def time_string(): return {} + + def _get_time(self): + if self.time_string == None: + return None + return utility.str_to_time(self.time_string) + def _set_time(self, value): + self.time_string = utility.time_to_str(value) + time = property(fget=_get_time, + fset=_set_time, + doc="An integer version of .time_string") + + def _extra_strings_check_fn(value): + return utility.iterable_full_of_strings(value, \ + alternative=settings_object.EMPTY) + def _extra_strings_change_hook(self, old, new): + self.extra_strings.sort() # to make merging easier + self._prop_save_settings(old, new) + @_versioned_property(name="extra_strings", + doc="Space for an array of extra strings. Useful for storing state for functionality implemented purely in becommands/<some_function>.py.", + default=[], + check_fn=_extra_strings_check_fn, + change_hook=_extra_strings_change_hook, + mutable=True) + def extra_strings(): return {} + + @_versioned_property(name="summary", + doc="A one-line bug description") + def summary(): return {} + + def _get_comment_root(self, load_full=False): + if self.storage != None and self.storage.is_readable(): + return comment.load_comments(self, load_full=load_full) + else: + return comment.Comment(self, uuid=comment.INVALID_UUID) + + @Property + @cached_property(generator=_get_comment_root) + @local_property("comment_root") + @doc_property(doc="The trunk of the comment tree. We use a dummy root comment by default, because there can be several comment threads rooted on the same parent bug. To simplify comment interaction, we condense these threads into a single thread with a Comment dummy root.") + def comment_root(): return {} + + def __init__(self, bugdir=None, uuid=None, from_storage=False, + load_comments=False, summary=None): + settings_object.SavedSettingsObject.__init__(self) + self.bugdir = bugdir + self.storage = None + self.uuid = uuid + self.id = libbe.util.id.ID(self, 'bug') + if from_storage == False: + if uuid == None: + self.uuid = libbe.util.id.uuid_gen() + self.time = int(time.time()) # only save to second precision + self.summary = summary + dummy = self.comment_root + if self.bugdir != None: + self.storage = self.bugdir.storage + if from_storage == False: + if self.storage != None and self.storage.is_writeable(): + self.save() + + def __repr__(self): + return "Bug(uuid=%r)" % self.uuid + + def __str__(self): + return self.string(shortlist=True) + + def __cmp__(self, other): + return cmp_full(self, other) + + # serializing methods + + def _setting_attr_string(self, setting): + value = getattr(self, setting) + if value == None: + return "" + if type(value) not in types.StringTypes: + return str(value) + return value + + def string(self, shortlist=False, show_comments=False): + if shortlist == False: + if self.time == None: + timestring = "" + else: + htime = utility.handy_time(self.time) + timestring = "%s (%s)" % (htime, self.time_string) + info = [("ID", self.uuid), + ("Short name", self.id.user()), + ("Severity", self.severity), + ("Status", self.status), + ("Assigned", self._setting_attr_string("assigned")), + ("Reporter", self._setting_attr_string("reporter")), + ("Creator", self._setting_attr_string("creator")), + ("Created", timestring)] + 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] + bugout = "".join(infolines) + "%s" % self.summary.rstrip('\n') + else: + statuschar = self.status[0] + severitychar = self.severity[0] + chars = "%c%c" % (statuschar, severitychar) + bugout = "%s:%s: %s" % (self.id.user(),chars,self.summary.rstrip('\n')) + + if show_comments == True: + self.comment_root.sort(cmp=libbe.comment.cmp_time, reverse=True) + comout = self.comment_root.string_thread(flatten=False) + output = bugout + '\n' + comout.rstrip('\n') + else : + output = bugout + return output + + def xml(self, indent=0, show_comments=False): + if self.time == None: + timestring = "" + else: + timestring = utility.time_to_str(self.time) + + info = [('uuid', self.uuid), + ('short-name', self.id.user()), + ('severity', self.severity), + ('status', self.status), + ('assigned', self.assigned), + ('reporter', self.reporter), + ('creator', self.creator), + ('created', timestring), + ('summary', self.summary)] + lines = ['<bug>'] + for (k,v) in info: + if v is not None: + lines.append(' <%s>%s</%s>' % (k,xml.sax.saxutils.escape(v),k)) + for estr in self.extra_strings: + lines.append(' <extra-string>%s</extra-string>' % estr) + if show_comments == True: + comout = self.comment_root.xml_thread(indent=indent+2) + if len(comout) > 0: + lines.append(comout) + lines.append('</bug>') + istring = ' '*indent + sep = '\n' + istring + return istring + sep.join(lines).rstrip('\n') + + def from_xml(self, xml_string, verbose=True): + u""" + Note: If a bug uuid is given, set .alt_id to it's value. + >>> bugA = Bug(uuid="0123", summary="Need to test Bug.from_xml()") + >>> bugA.date = "Thu, 01 Jan 1970 00:00:00 +0000" + >>> bugA.creator = u'Fran\xe7ois' + >>> bugA.extra_strings += ['TAG: very helpful'] + >>> commA = bugA.comment_root.new_reply(body='comment A') + >>> commB = bugA.comment_root.new_reply(body='comment B') + >>> commC = commA.new_reply(body='comment C') + >>> xml = bugA.xml(show_comments=True) + >>> bugB = Bug() + >>> bugB.from_xml(xml, verbose=True) + >>> bugB.xml(show_comments=True) == xml + False + >>> bugB.uuid = bugB.alt_id + >>> for comm in bugB.comments(): + ... comm.uuid = comm.alt_id + ... comm.alt_id = None + >>> bugB.xml(show_comments=True) == xml + True + >>> bugB.explicit_attrs # doctest: +NORMALIZE_WHITESPACE + ['severity', 'status', 'creator', 'created', 'summary'] + >>> len(list(bugB.comments())) + 3 + """ + if type(xml_string) == types.UnicodeType: + xml_string = xml_string.strip().encode('unicode_escape') + if hasattr(xml_string, 'getchildren'): # already an ElementTree Element + bug = xml_string + else: + bug = ElementTree.XML(xml_string) + if bug.tag != 'bug': + raise utility.InvalidXML( \ + 'bug', bug, 'root element must be <comment>') + tags=['uuid','short-name','severity','status','assigned', + 'reporter', 'creator','created','summary','extra-string'] + self.explicit_attrs = [] + uuid = None + estrs = [] + comments = [] + for child in bug.getchildren(): + if child.tag == 'short-name': + pass + elif child.tag == 'comment': + comm = comment.Comment(bug=self) + comm.from_xml(child) + comments.append(comm) + continue + elif child.tag in tags: + if child.text == None or len(child.text) == 0: + text = settings_object.EMPTY + else: + text = xml.sax.saxutils.unescape(child.text) + text = text.decode('unicode_escape').strip() + if child.tag == 'uuid': + uuid = text + continue # don't set the bug's uuid tag. + elif child.tag == 'extra-string': + estrs.append(text) + continue # don't set the bug's extra_string yet. + attr_name = child.tag.replace('-','_') + self.explicit_attrs.append(attr_name) + setattr(self, attr_name, text) + elif verbose == True: + print >> sys.stderr, 'Ignoring unknown tag %s in %s' \ + % (child.tag, comment.tag) + if uuid != self.uuid: + if not hasattr(self, 'alt_id') or self.alt_id == None: + self.alt_id = uuid + self.extra_strings = estrs + self.add_comments(comments, ignore_missing_references=True) + + def add_comment(self, comment, *args, **kwargs): + """ + Add a comment too the current bug, under the parent specified + by comment.in_reply_to. + Note: If a bug uuid is given, set .alt_id to it's value. + + >>> bugA = Bug(uuid='0123', summary='Need to test Bug.add_comment()') + >>> bugA.creator = 'Jack' + >>> commA = bugA.comment_root.new_reply(body='comment A') + >>> commA.uuid = 'commA' + >>> commB = comment.Comment(body='comment B') + >>> commB.uuid = 'commB' + >>> bugA.add_comment(commB) + >>> commC = comment.Comment(body='comment C') + >>> commC.uuid = 'commC' + >>> commC.in_reply_to = commA.uuid + >>> bugA.add_comment(commC) + >>> print bugA.xml(show_comments=True) # doctest: +ELLIPSIS + <bug> + <uuid>0123</uuid> + <short-name>/012</short-name> + <severity>minor</severity> + <status>open</status> + <creator>Jack</creator> + <created>...</created> + <summary>Need to test Bug.add_comment()</summary> + <comment> + <uuid>commA</uuid> + <short-name>/012/commA</short-name> + <author></author> + <date>...</date> + <content-type>text/plain</content-type> + <body>comment A</body> + </comment> + <comment> + <uuid>commC</uuid> + <short-name>/012/commC</short-name> + <in-reply-to>commA</in-reply-to> + <author></author> + <date>...</date> + <content-type>text/plain</content-type> + <body>comment C</body> + </comment> + <comment> + <uuid>commB</uuid> + <short-name>/012/commB</short-name> + <author></author> + <date>...</date> + <content-type>text/plain</content-type> + <body>comment B</body> + </comment> + </bug> + """ + self.add_comments([comment], **kwargs) + + def add_comments(self, comments, default_parent=None, + ignore_missing_references=False): + """ + Convert a raw list of comments to single root comment. If a + comment does not specify a parent with .in_reply_to, the + parent defaults to .comment_root, but you can specify another + default parent via default_parent. + """ + uuid_map = {} + if default_parent == None: + default_parent = self.comment_root + for c in list(self.comments()) + comments: + assert c.uuid != None + assert c.uuid not in uuid_map + uuid_map[c.uuid] = c + if c.alt_id != None: + uuid_map[c.alt_id] = c + uuid_map[None] = self.comment_root + uuid_map[comment.INVALID_UUID] = self.comment_root + if default_parent != self.comment_root: + assert default_parent.uuid in uuid_map, default_parent.uuid + for c in comments: + if c.in_reply_to == None \ + and default_parent.uuid != comment.INVALID_UUID: + c.in_reply_to = default_parent.uuid + elif c.in_reply_to == comment.INVALID_UUID: + c.in_reply_to = None + try: + parent = uuid_map[c.in_reply_to] + except KeyError: + if ignore_missing_references == True: + print >> sys.stderr, \ + 'Ignoring missing reference to %s' % c.in_reply_to + parent = default_parent + if parent.uuid != comment.INVALID_UUID: + c.in_reply_to = parent.uuid + else: + raise comment.MissingReference(c) + c.bug = self + parent.append(c) + + def merge(self, other, accept_changes=True, + accept_extra_strings=True, accept_comments=True, + change_exception=False): + """ + Merge info from other into this bug. Overrides any attributes + in self that are listed in other.explicit_attrs. + + >>> bugA = Bug(uuid='0123', summary='Need to test Bug.merge()') + >>> bugA.date = 'Thu, 01 Jan 1970 00:00:00 +0000' + >>> bugA.creator = 'Frank' + >>> bugA.extra_strings += ['TAG: very helpful'] + >>> bugA.extra_strings += ['TAG: favorite'] + >>> commA = bugA.comment_root.new_reply(body='comment A') + >>> commA.uuid = 'uuid-commA' + >>> bugB = Bug(uuid='3210', summary='More tests for Bug.merge()') + >>> bugB.date = 'Fri, 02 Jan 1970 00:00:00 +0000' + >>> bugB.creator = 'John' + >>> bugB.explicit_attrs = ['creator', 'summary'] + >>> bugB.extra_strings += ['TAG: very helpful'] + >>> bugB.extra_strings += ['TAG: useful'] + >>> commB = bugB.comment_root.new_reply(body='comment B') + >>> commB.uuid = 'uuid-commB' + >>> bugA.merge(bugB, accept_changes=False, accept_extra_strings=False, + ... accept_comments=False, change_exception=False) + >>> print bugA.creator + Frank + >>> bugA.merge(bugB, accept_changes=False, accept_extra_strings=False, + ... accept_comments=False, change_exception=True) + Traceback (most recent call last): + ... + ValueError: Merge would change creator "Frank"->"John" for bug 0123 + >>> print bugA.creator + Frank + >>> bugA.merge(bugB, accept_changes=True, accept_extra_strings=False, + ... accept_comments=False, change_exception=True) + Traceback (most recent call last): + ... + ValueError: Merge would add extra string "TAG: useful" for bug 0123 + >>> print bugA.creator + John + >>> print bugA.extra_strings + ['TAG: favorite', 'TAG: very helpful'] + >>> bugA.merge(bugB, accept_changes=True, accept_extra_strings=True, + ... accept_comments=False, change_exception=True) + Traceback (most recent call last): + ... + ValueError: Merge would add comment uuid-commB (alt: None) to bug 0123 + >>> print bugA.extra_strings + ['TAG: favorite', 'TAG: useful', 'TAG: very helpful'] + >>> bugA.merge(bugB, accept_changes=True, accept_extra_strings=True, + ... accept_comments=True, change_exception=True) + >>> print bugA.xml(show_comments=True) # doctest: +ELLIPSIS + <bug> + <uuid>0123</uuid> + <short-name>/012</short-name> + <severity>minor</severity> + <status>open</status> + <creator>John</creator> + <created>...</created> + <summary>More tests for Bug.merge()</summary> + <extra-string>TAG: favorite</extra-string> + <extra-string>TAG: useful</extra-string> + <extra-string>TAG: very helpful</extra-string> + <comment> + <uuid>uuid-commA</uuid> + <short-name>/012/uuid-commA</short-name> + <author></author> + <date>...</date> + <content-type>text/plain</content-type> + <body>comment A</body> + </comment> + <comment> + <uuid>uuid-commB</uuid> + <short-name>/012/uuid-commB</short-name> + <author></author> + <date>...</date> + <content-type>text/plain</content-type> + <body>comment B</body> + </comment> + </bug> + """ + for attr in other.explicit_attrs: + old = getattr(self, attr) + new = getattr(other, attr) + if old != new: + if accept_changes == True: + setattr(self, attr, new) + elif change_exception == True: + raise ValueError, \ + 'Merge would change %s "%s"->"%s" for bug %s' \ + % (attr, old, new, self.uuid) + for estr in other.extra_strings: + if not estr in self.extra_strings: + if accept_extra_strings == True: + self.extra_strings.append(estr) + elif change_exception == True: + raise ValueError, \ + 'Merge would add extra string "%s" for bug %s' \ + % (estr, self.uuid) + for o_comm in other.comments(): + try: + s_comm = self.comment_root.comment_from_uuid(o_comm.uuid) + except KeyError, e: + try: + s_comm = self.comment_root.comment_from_uuid(o_comm.alt_id) + except KeyError, e: + s_comm = None + if s_comm == None: + if accept_comments == True: + o_comm_copy = copy.copy(o_comm) + o_comm_copy.bug = self + o_comm_copy.id = libbe.util.id.ID(o_comm_copy, 'comment') + self.comment_root.add_reply(o_comm_copy) + elif change_exception == True: + raise ValueError, \ + 'Merge would add comment %s (alt: %s) to bug %s' \ + % (o_comm.uuid, o_comm.alt_id, self.uuid) + else: + s_comm.merge(o_comm, accept_changes=accept_changes, + accept_extra_strings=accept_extra_strings, + change_exception=change_exception) + + # methods for saving/loading/acessing settings and properties. + + def load_settings(self, settings_mapfile=None): + if settings_mapfile == None: + settings_mapfile = \ + self.storage.get(self.id.storage('values'), default='\n') + try: + settings = mapfile.parse(settings_mapfile) + except mapfile.InvalidMapfileContents, e: + raise Exception('Invalid settings file for bug %s\n' + '(BE version missmatch?)' % self.id.user()) + self._setup_saved_settings(settings) + + def save_settings(self): + mf = mapfile.generate(self._get_saved_settings()) + self.storage.set(self.id.storage('values'), mf) + + def save(self): + """ + Save any loaded contents to storage. Because of lazy loading + of comments, this is actually not too inefficient. + + However, if self.storage.is_writeable() == True, then any + changes are automatically written to storage as soon as they + happen, so calling this method will just waste time (unless + something else has been messing with your stored files). + """ + assert self.storage != None, "Can't save without storage" + if self.bugdir != None: + parent = self.bugdir.id.storage() + else: + parent = None + self.storage.add(self.id.storage(), parent=parent, directory=True) + self.storage.add(self.id.storage('values'), parent=self.id.storage(), + directory=False) + self.save_settings() + if len(self.comment_root) > 0: + comment.save_comments(self) + + def load_comments(self, load_full=True): + if load_full == True: + # Force a complete load of the whole comment tree + self.comment_root = self._get_comment_root(load_full=True) + else: + # Setup for fresh lazy-loading. Clear _comment_root, so + # next _get_comment_root returns a fresh version. Turn of + # writing temporarily so we don't write our blank comment + # tree to disk. + w = self.storage.writeable + self.storage.writeable = False + self.comment_root = None + self.storage.writeable = w + + def remove(self): + self.storage.recursive_remove(self.id.storage()) + + # methods for managing comments + + def uuids(self): + for comment in self.comments(): + yield comment.uuid + + def comments(self): + for comment in self.comment_root.traverse(): + yield comment + + def new_comment(self, body=None): + comm = self.comment_root.new_reply(body=body) + return comm + + def comment_from_uuid(self, uuid, *args, **kwargs): + return self.comment_root.comment_from_uuid(uuid, *args, **kwargs) + + # methods for id generation + + def sibling_uuids(self): + if self.bugdir != None: + return self.bugdir.uuids() + return [] + + +# 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 +# importance is unclear, the sorting follows some arbitrary convention +# (i.e. dictionary order). + +def cmp_severity(bug_1, bug_2): + """ + 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 + >>> bugB.severity = "minor" + >>> cmp_severity(bugA, bugB) > 0 + True + >>> 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. + + >>> bugA = Bug() + >>> bugB = Bug() + >>> bugA.status = bugB.status = "open" + >>> cmp_status(bugA, bugB) == 0 + True + >>> bugB.status = "closed" + >>> cmp_status(bugA, bugB) < 0 + True + >>> 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]) + +def cmp_attr(bug_1, bug_2, attr, invert=False): + """ + Compare a general attribute between two bugs using the + conventional comparison rule for that attribute type. If + ``invert==True``, sort *against* that convention. + + >>> attr="severity" + >>> bugA = Bug() + >>> bugB = Bug() + >>> bugA.severity = "critical" + >>> bugB.severity = "wishlist" + >>> cmp_attr(bugA, bugB, attr) < 0 + True + >>> cmp_attr(bugA, bugB, attr, invert=True) > 0 + True + >>> bugB.severity = "critical" + >>> cmp_attr(bugA, bugB, attr) == 0 + True + """ + if not hasattr(bug_2, attr) : + return 1 + val_1 = getattr(bug_1, attr) + val_2 = getattr(bug_2, attr) + if val_1 == None: val_1 = None + if val_2 == None: val_2 = None + + if invert == True : + return -cmp(val_1, val_2) + else : + return cmp(val_1, val_2) + +# alphabetical rankings (a < z) +cmp_uuid = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "uuid") +cmp_creator = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "creator") +cmp_assigned = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "assigned") +cmp_reporter = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "reporter") +cmp_summary = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "summary") +cmp_extra_strings = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "extra_strings") +# chronological rankings (newer < older) +cmp_time = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "time", invert=True) + +def cmp_comments(bug_1, bug_2): + """ + Compare two bugs' comments lists. Doesn't load any new comments, + so you should call each bug's .load_comments() first if you want a + full comparison. + """ + comms_1 = sorted(bug_1.comments(), key = lambda comm : comm.uuid) + comms_2 = sorted(bug_2.comments(), key = lambda comm : comm.uuid) + result = cmp(len(comms_1), len(comms_2)) + if result != 0: + return result + for c_1,c_2 in zip(comms_1, comms_2): + result = cmp(c_1, c_2) + if result != 0: + return result + return 0 + +DEFAULT_CMP_FULL_CMP_LIST = \ + (cmp_status, cmp_severity, cmp_assigned, cmp_time, cmp_creator, + cmp_reporter, cmp_comments, cmp_summary, cmp_uuid, cmp_extra_strings) + +class BugCompoundComparator (object): + def __init__(self, cmp_list=DEFAULT_CMP_FULL_CMP_LIST): + self.cmp_list = cmp_list + def __call__(self, bug_1, bug_2): + for comparison in self.cmp_list : + val = comparison(bug_1, bug_2) + if val != 0 : + return val + return 0 + +cmp_full = BugCompoundComparator() + + +# define some bonus cmp_* functions +def cmp_last_modified(bug_1, bug_2): + """ + Like cmp_time(), but use most recent comment instead of bug + creation for the timestamp. + """ + def last_modified(bug): + time = bug.time + for comment in bug.comment_root.traverse(): + if comment.time > time: + time = comment.time + return time + val_1 = last_modified(bug_1) + val_2 = last_modified(bug_2) + return -cmp(val_1, val_2) + + +if libbe.TESTING == True: + suite = doctest.DocTestSuite() diff --git a/libbe/bugdir.py b/libbe/bugdir.py new file mode 100644 index 0000000..65136fe --- /dev/null +++ b/libbe/bugdir.py @@ -0,0 +1,563 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Alexander Belchenko <bialix@ukr.net> +# Chris Ball <cjb@laptop.org> +# Gianluca Montecchi <gian@grys.it> +# Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Define the :class:`BugDir` class for storing a collection of bugs. +""" + +import copy +import errno +import os +import os.path +import time + +import libbe +import libbe.storage as storage +from libbe.storage.util.properties import Property, doc_property, \ + local_property, defaulting_property, checked_property, \ + fn_checked_property, cached_property, primed_property, \ + change_hook_property, settings_property +import libbe.storage.util.settings_object as settings_object +import libbe.storage.util.mapfile as mapfile +import libbe.bug as bug +import libbe.util.utility as utility +import libbe.util.id + +if libbe.TESTING == True: + import doctest + import sys + import unittest + + import libbe.storage.base + + +class NoBugMatches(libbe.util.id.NoIDMatches): + def __init__(self, *args, **kwargs): + libbe.util.id.NoIDMatches.__init__(self, *args, **kwargs) + def __str__(self): + if self.msg == None: + return 'No bug matches %s' % self.id + return self.msg + + +class BugDir (list, settings_object.SavedSettingsObject): + """A BugDir is a container for :class:`~libbe.bug.Bug`\s, with some + additional attributes. + + Parameters + ---------- + storage : :class:`~libbe.storage.base.Storage` + Storage instance containing the bug directory. If + `from_storage` is `False`, `storage` may be `None`. + uuid : str, optional + Set the bugdir UUID (see :mod:`libbe.util.id`). + Useful if you are loading one of several bugdirs + stored in a single Storage instance. + from_storage : bool, optional + If `True`, attempt to load from storage. Otherwise, + setup in memory, saving to `storage` if it is not `None`. + + See Also + -------- + :class:`SimpleBugDir` for some bugdir manipulation exampes. + """ + + settings_properties = [] + required_saved_properties = [] + _prop_save_settings = settings_object.prop_save_settings + _prop_load_settings = settings_object.prop_load_settings + def _versioned_property(settings_properties=settings_properties, + required_saved_properties=required_saved_properties, + **kwargs): + if "settings_properties" not in kwargs: + kwargs["settings_properties"] = settings_properties + if "required_saved_properties" not in kwargs: + kwargs["required_saved_properties"]=required_saved_properties + return settings_object.versioned_property(**kwargs) + + @_versioned_property(name="target", + doc="The current project development target.") + def target(): return {} + + def _setup_severities(self, severities): + if severities not in [None, settings_object.EMPTY]: + bug.load_severities(severities) + def _set_severities(self, old_severities, new_severities): + self._setup_severities(new_severities) + self._prop_save_settings(old_severities, new_severities) + @_versioned_property(name="severities", + doc="The allowed bug severities and their descriptions.", + change_hook=_set_severities) + def severities(): return {} + + def _setup_status(self, active_status, inactive_status): + bug.load_status(active_status, inactive_status) + def _set_active_status(self, old_active_status, new_active_status): + self._setup_status(new_active_status, self.inactive_status) + self._prop_save_settings(old_active_status, new_active_status) + @_versioned_property(name="active_status", + doc="The allowed active bug states and their descriptions.", + change_hook=_set_active_status) + def active_status(): return {} + + def _set_inactive_status(self, old_inactive_status, new_inactive_status): + self._setup_status(self.active_status, new_inactive_status) + self._prop_save_settings(old_inactive_status, new_inactive_status) + @_versioned_property(name="inactive_status", + doc="The allowed inactive bug states and their descriptions.", + change_hook=_set_inactive_status) + def inactive_status(): return {} + + def _extra_strings_check_fn(value): + return utility.iterable_full_of_strings(value, \ + alternative=settings_object.EMPTY) + def _extra_strings_change_hook(self, old, new): + self.extra_strings.sort() # to make merging easier + self._prop_save_settings(old, new) + @_versioned_property(name="extra_strings", + doc="Space for an array of extra strings. Useful for storing state for functionality implemented purely in becommands/<some_function>.py.", + default=[], + check_fn=_extra_strings_check_fn, + change_hook=_extra_strings_change_hook, + mutable=True) + def extra_strings(): return {} + + def _bug_map_gen(self): + map = {} + for bug in self: + map[bug.uuid] = bug + for uuid in self.uuids(): + if uuid not in map: + map[uuid] = None + self._bug_map_value = map # ._bug_map_value used by @local_property + + @Property + @primed_property(primer=_bug_map_gen) + @local_property("bug_map") + @doc_property(doc="A dict of (bug-uuid, bug-instance) pairs.") + def _bug_map(): return {} + + def __init__(self, storage, uuid=None, from_storage=False): + list.__init__(self) + settings_object.SavedSettingsObject.__init__(self) + self.storage = storage + self.id = libbe.util.id.ID(self, 'bugdir') + self.uuid = uuid + if from_storage == True: + if self.uuid == None: + self.uuid = [c for c in self.storage.children() + if c != 'version'][0] + self.load_settings() + else: + if self.uuid == None: + self.uuid = libbe.util.id.uuid_gen() + if self.storage != None and self.storage.is_writeable(): + self.save() + + # methods for saving/loading/accessing settings and properties. + + def load_settings(self, settings_mapfile=None): + if settings_mapfile == None: + settings_mapfile = \ + self.storage.get(self.id.storage('settings'), default='\n') + try: + settings = mapfile.parse(settings_mapfile) + except mapfile.InvalidMapfileContents, e: + raise Exception('Invalid settings file for bugdir %s\n' + '(BE version missmatch?)' % self.id.user()) + self._setup_saved_settings(settings) + self._setup_severities(self.severities) + self._setup_status(self.active_status, self.inactive_status) + + def save_settings(self): + mf = mapfile.generate(self._get_saved_settings()) + self.storage.set(self.id.storage('settings'), mf) + + def load_all_bugs(self): + """ + Warning: this could take a while. + """ + self._clear_bugs() + for uuid in self.uuids(): + self._load_bug(uuid) + + def save(self): + """ + Save any loaded contents to storage. Because of lazy loading + of bugs and comments, this is actually not too inefficient. + + However, if self.storage.is_writeable() == True, then any + changes are automatically written to storage as soon as they + happen, so calling this method will just waste time (unless + something else has been messing with your stored files). + """ + self.storage.add(self.id.storage(), directory=True) + self.storage.add(self.id.storage('settings'), parent=self.id.storage(), + directory=False) + self.save_settings() + for bug in self: + bug.save() + + # methods for managing bugs + + def uuids(self, use_cached_disk_uuids=True): + if use_cached_disk_uuids==False or not hasattr(self, '_uuids_cache'): + self._uuids_cache = [] + # list bugs that are in storage + if self.storage != None and self.storage.is_readable(): + child_uuids = libbe.util.id.child_uuids( + self.storage.children(self.id.storage())) + for id in child_uuids: + self._uuids_cache.append(id) + return list(set([bug.uuid for bug in self] + self._uuids_cache)) + + def _clear_bugs(self): + while len(self) > 0: + self.pop() + if hasattr(self, '_uuids_cache'): + del(self._uuids_cache) + self._bug_map_gen() + + def _load_bug(self, uuid): + bg = bug.Bug(bugdir=self, uuid=uuid, from_storage=True) + self.append(bg) + self._bug_map_gen() + return bg + + def new_bug(self, summary=None, _uuid=None): + bg = bug.Bug(bugdir=self, uuid=_uuid, summary=summary, + from_storage=False) + self.append(bg) + self._bug_map_gen() + if hasattr(self, '_uuids_cache') and not bg.uuid in self._uuids_cache: + self._uuids_cache.append(bg.uuid) + return bg + + def remove_bug(self, bug): + if hasattr(self, '_uuids_cache') and bug.uuid in self._uuids_cache: + self._uuids_cache.remove(bug.uuid) + self.remove(bug) + if self.storage != None and self.storage.is_writeable(): + bug.remove() + + def bug_from_uuid(self, uuid): + if not self.has_bug(uuid): + raise NoBugMatches( + uuid, self.uuids(), + 'No bug matches %s in %s' % (uuid, self.storage)) + if self._bug_map[uuid] == None: + self._load_bug(uuid) + return self._bug_map[uuid] + + def has_bug(self, bug_uuid): + if bug_uuid not in self._bug_map: + self._bug_map_gen() + if bug_uuid not in self._bug_map: + return False + return True + + # methods for id generation + + def sibling_uuids(self): + return [] + +class RevisionedBugDir (BugDir): + """ + RevisionedBugDirs are read-only copies used for generating + diffs between revisions. + """ + def __init__(self, bugdir, revision): + storage_version = bugdir.storage.storage_version(revision) + if storage_version != libbe.storage.STORAGE_VERSION: + raise libbe.storage.InvalidStorageVersion(storage_version) + s = copy.deepcopy(bugdir.storage) + s.writeable = False + class RevisionedStorage (object): + def __init__(self, storage, default_revision): + self.s = storage + self.sget = self.s.get + self.sancestors = self.s.ancestors + self.schildren = self.s.children + self.schanged = self.s.changed + self.r = default_revision + def get(self, *args, **kwargs): + if not 'revision' in kwargs or kwargs['revision'] == None: + kwargs['revision'] = self.r + return self.sget(*args, **kwargs) + def ancestors(self, *args, **kwargs): + print 'getting ancestors', args, kwargs + if not 'revision' in kwargs or kwargs['revision'] == None: + kwargs['revision'] = self.r + ret = self.sancestors(*args, **kwargs) + print 'got ancestors', ret + return ret + def children(self, *args, **kwargs): + if not 'revision' in kwargs or kwargs['revision'] == None: + kwargs['revision'] = self.r + return self.schildren(*args, **kwargs) + def changed(self, *args, **kwargs): + if not 'revision' in kwargs or kwargs['revision'] == None: + kwargs['revision'] = self.r + return self.schanged(*args, **kwargs) + rs = RevisionedStorage(s, revision) + s.get = rs.get + s.ancestors = rs.ancestors + s.children = rs.children + s.changed = rs.changed + BugDir.__init__(self, s, from_storage=True) + self.revision = revision + def changed(self): + return self.storage.changed() + + +if libbe.TESTING == True: + class SimpleBugDir (BugDir): + """ + For testing. Set ``memory=True`` for a memory-only bugdir. + + >>> bugdir = SimpleBugDir() + >>> uuids = list(bugdir.uuids()) + >>> uuids.sort() + >>> print uuids + ['a', 'b'] + >>> bugdir.cleanup() + """ + def __init__(self, memory=True, versioned=False): + if memory == True: + storage = None + else: + dir = utility.Dir() + self._dir_ref = dir # postpone cleanup since dir.cleanup() removes dir. + if versioned == False: + storage = libbe.storage.base.Storage(dir.path) + else: + storage = libbe.storage.base.VersionedStorage(dir.path) + storage.init() + storage.connect() + BugDir.__init__(self, storage=storage, uuid='abc123') + bug_a = self.new_bug(summary='Bug A', _uuid='a') + bug_a.creator = 'John Doe <jdoe@example.com>' + bug_a.time = 0 + bug_b = self.new_bug(summary='Bug B', _uuid='b') + bug_b.creator = 'Jane Doe <jdoe@example.com>' + bug_b.time = 0 + bug_b.status = 'closed' + if self.storage != None: + self.storage.disconnect() # flush to storage + self.storage.connect() + + def cleanup(self): + if self.storage != None: + self.storage.writeable = True + self.storage.disconnect() + self.storage.destroy() + if hasattr(self, '_dir_ref'): + self._dir_ref.cleanup() + + def flush_reload(self): + if self.storage != None: + self.storage.disconnect() + self.storage.connect() + self._clear_bugs() + +# class BugDirTestCase(unittest.TestCase): +# def setUp(self): +# self.dir = utility.Dir() +# self.bugdir = BugDir(self.dir.path, sink_to_existing_root=False, +# allow_storage_init=True) +# self.storage = self.bugdir.storage +# def tearDown(self): +# self.bugdir.cleanup() +# self.dir.cleanup() +# def fullPath(self, path): +# return os.path.join(self.dir.path, path) +# def assertPathExists(self, path): +# fullpath = self.fullPath(path) +# self.failUnless(os.path.exists(fullpath)==True, +# "path %s does not exist" % fullpath) +# self.assertRaises(AlreadyInitialized, BugDir, +# self.dir.path, assertNewBugDir=True) +# def versionTest(self): +# if self.storage != None and self.storage.versioned == False: +# return +# original = self.bugdir.storage.commit("Began versioning") +# bugA = self.bugdir.bug_from_uuid("a") +# bugA.status = "fixed" +# self.bugdir.save() +# new = self.storage.commit("Fixed bug a") +# dupdir = self.bugdir.duplicate_bugdir(original) +# self.failUnless(dupdir.root != self.bugdir.root, +# "%s, %s" % (dupdir.root, self.bugdir.root)) +# bugAorig = dupdir.bug_from_uuid("a") +# self.failUnless(bugA != bugAorig, +# "\n%s\n%s" % (bugA.string(), bugAorig.string())) +# bugAorig.status = "fixed" +# self.failUnless(bug.cmp_status(bugA, bugAorig)==0, +# "%s, %s" % (bugA.status, bugAorig.status)) +# self.failUnless(bug.cmp_severity(bugA, bugAorig)==0, +# "%s, %s" % (bugA.severity, bugAorig.severity)) +# self.failUnless(bug.cmp_assigned(bugA, bugAorig)==0, +# "%s, %s" % (bugA.assigned, bugAorig.assigned)) +# self.failUnless(bug.cmp_time(bugA, bugAorig)==0, +# "%s, %s" % (bugA.time, bugAorig.time)) +# self.failUnless(bug.cmp_creator(bugA, bugAorig)==0, +# "%s, %s" % (bugA.creator, bugAorig.creator)) +# self.failUnless(bugA == bugAorig, +# "\n%s\n%s" % (bugA.string(), bugAorig.string())) +# self.bugdir.remove_duplicate_bugdir() +# self.failUnless(os.path.exists(dupdir.root)==False, +# str(dupdir.root)) +# def testRun(self): +# self.bugdir.new_bug(uuid="a", summary="Ant") +# self.bugdir.new_bug(uuid="b", summary="Cockroach") +# self.bugdir.new_bug(uuid="c", summary="Praying mantis") +# length = len(self.bugdir) +# self.failUnless(length == 3, "%d != 3 bugs" % length) +# uuids = list(self.bugdir.uuids()) +# self.failUnless(len(uuids) == 3, "%d != 3 uuids" % len(uuids)) +# self.failUnless(uuids == ["a","b","c"], str(uuids)) +# bugA = self.bugdir.bug_from_uuid("a") +# bugAprime = self.bugdir.bug_from_shortname("a") +# self.failUnless(bugA == bugAprime, "%s != %s" % (bugA, bugAprime)) +# self.bugdir.save() +# self.versionTest() +# def testComments(self, sync_with_disk=False): +# if sync_with_disk == True: +# self.bugdir.set_sync_with_disk(True) +# self.bugdir.new_bug(uuid="a", summary="Ant") +# bug = self.bugdir.bug_from_uuid("a") +# comm = bug.comment_root +# rep = comm.new_reply("Ants are small.") +# rep.new_reply("And they have six legs.") +# if sync_with_disk == False: +# self.bugdir.save() +# self.bugdir.set_sync_with_disk(True) +# self.bugdir._clear_bugs() +# bug = self.bugdir.bug_from_uuid("a") +# bug.load_comments() +# if sync_with_disk == False: +# self.bugdir.set_sync_with_disk(False) +# self.failUnless(len(bug.comment_root)==1, len(bug.comment_root)) +# for index,comment in enumerate(bug.comments()): +# if index == 0: +# repLoaded = comment +# self.failUnless(repLoaded.uuid == rep.uuid, repLoaded.uuid) +# self.failUnless(comment.sync_with_disk == sync_with_disk, +# comment.sync_with_disk) +# self.failUnless(comment.content_type == "text/plain", +# comment.content_type) +# self.failUnless(repLoaded.settings["Content-type"] == \ +# "text/plain", +# repLoaded.settings) +# self.failUnless(repLoaded.body == "Ants are small.", +# repLoaded.body) +# elif index == 1: +# self.failUnless(comment.in_reply_to == repLoaded.uuid, +# repLoaded.uuid) +# self.failUnless(comment.body == "And they have six legs.", +# comment.body) +# else: +# self.failIf(True, +# "Invalid comment: %d\n%s" % (index, comment)) +# def testSyncedComments(self): +# self.testComments(sync_with_disk=True) + + class SimpleBugDirTestCase (unittest.TestCase): + def setUp(self): + # create a pre-existing bugdir in a temporary directory + self.dir = utility.Dir() + self.storage = libbe.storage.base.Storage(self.dir.path) + self.storage.init() + self.storage.connect() + self.bugdir = BugDir(self.storage) + self.bugdir.new_bug(summary="Hopefully not imported", + _uuid="preexisting") + self.storage.disconnect() + self.storage.connect() + def tearDown(self): + if self.storage != None: + self.storage.disconnect() + self.storage.destroy() + self.dir.cleanup() + def testOnDiskCleanLoad(self): + """ + SimpleBugDir(memory==False) should not import + preexisting bugs. + """ + bugdir = SimpleBugDir(memory=False) + self.failUnless(bugdir.storage.is_readable() == True, + bugdir.storage.is_readable()) + self.failUnless(bugdir.storage.is_writeable() == True, + bugdir.storage.is_writeable()) + uuids = sorted([bug.uuid for bug in bugdir]) + self.failUnless(uuids == ['a', 'b'], uuids) + bugdir.flush_reload() + uuids = sorted(bugdir.uuids()) + self.failUnless(uuids == ['a', 'b'], uuids) + uuids = sorted([bug.uuid for bug in bugdir]) + self.failUnless(uuids == [], uuids) + bugdir.load_all_bugs() + uuids = sorted([bug.uuid for bug in bugdir]) + self.failUnless(uuids == ['a', 'b'], uuids) + bugdir.cleanup() + def testInMemoryCleanLoad(self): + """ + SimpleBugDir(memory==True) should not import + preexisting bugs. + """ + bugdir = SimpleBugDir(memory=True) + self.failUnless(bugdir.storage == None, bugdir.storage) + uuids = sorted([bug.uuid for bug in bugdir]) + self.failUnless(uuids == ['a', 'b'], uuids) + uuids = sorted([bug.uuid for bug in bugdir]) + self.failUnless(uuids == ['a', 'b'], uuids) + bugdir._clear_bugs() + uuids = sorted(bugdir.uuids()) + self.failUnless(uuids == [], uuids) + uuids = sorted([bug.uuid for bug in bugdir]) + self.failUnless(uuids == [], uuids) + bugdir.cleanup() + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) + +# def _get_settings(self, settings_path, for_duplicate_bugdir=False): +# allow_no_storage = not self.storage.path_in_root(settings_path) +# if allow_no_storage == True: +# assert for_duplicate_bugdir == True +# if self.sync_with_disk == False and for_duplicate_bugdir == False: +# # duplicates can ignore this bugdir's .sync_with_disk status +# raise DiskAccessRequired("_get settings") +# try: +# settings = mapfile.map_load(self.storage, settings_path, allow_no_storage) +# except storage.NoSuchFile: +# settings = {"storage_name": "None"} +# return settings + +# def _save_settings(self, settings_path, settings, +# for_duplicate_bugdir=False): +# allow_no_storage = not self.storage.path_in_root(settings_path) +# if allow_no_storage == True: +# assert for_duplicate_bugdir == True +# if self.sync_with_disk == False and for_duplicate_bugdir == False: +# # duplicates can ignore this bugdir's .sync_with_disk status +# raise DiskAccessRequired("_save settings") +# self.storage.mkdir(self.get_path(), allow_no_storage) +# mapfile.map_save(self.storage, settings_path, settings, allow_no_storage) diff --git a/libbe/command/__init__.py b/libbe/command/__init__.py new file mode 100644 index 0000000..0c8d4ff --- /dev/null +++ b/libbe/command/__init__.py @@ -0,0 +1,40 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import base + +UserError = base.UserError +UnknownCommand = base.UnknownCommand +get_command = base.get_command +get_command_class = base.get_command_class +commands = base.commands +Option = base.Option +Argument = base.Argument +Command = base.Command +InputOutput = base.InputOutput +StdInputOutput = base.StdInputOutput +StringInputOutput = base.StringInputOutput +UnconnectedStorageGetter = base.UnconnectedStorageGetter +StorageCallbacks = base.StorageCallbacks +UserInterface = base.UserInterface + +__all__ = [UserError, UnknownCommand, + get_command, get_command_class, commands, + Option, Argument, Command, + InputOutput, StdInputOutput, StringInputOutput, + StorageCallbacks, UnconnectedStorageGetter, + UserInterface] diff --git a/libbe/command/assign.py b/libbe/command/assign.py new file mode 100644 index 0000000..6abf05e --- /dev/null +++ b/libbe/command/assign.py @@ -0,0 +1,98 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# Marien Zwart <marienz@gentoo.org> +# Thomas Gerigk <tgerigk@gmx.de> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import libbe +import libbe.command +import libbe.command.util + + +class Assign (libbe.command.Command): + u"""Assign an individual or group to fix a bug + + >>> import sys + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_storage(bd.storage) + >>> cmd = Assign(ui=ui) + + >>> bd.bug_from_uuid('a').assigned is None + True + >>> ui._user_id = u'Fran\xe7ois' + >>> ret = ui.run(cmd, args=['-', '/a']) + >>> bd.flush_reload() + >>> bd.bug_from_uuid('a').assigned + u'Fran\\xe7ois' + + >>> ret = ui.run(cmd, args=['someone', '/a', '/b']) + >>> bd.flush_reload() + >>> bd.bug_from_uuid('a').assigned + 'someone' + >>> bd.bug_from_uuid('b').assigned + 'someone' + + >>> ret = ui.run(cmd, args=['none', '/a']) + >>> bd.flush_reload() + >>> bd.bug_from_uuid('a').assigned is None + True + >>> ui.cleanup() + >>> bd.cleanup() + """ + name = 'assign' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.args.extend([ + libbe.command.Argument( + name='assigned', metavar='ASSIGNED', default=None, + completion_callback=libbe.command.util.complete_assigned), + libbe.command.Argument( + name='bug-id', metavar='BUG-ID', default=None, + repeatable=True, + completion_callback=libbe.command.util.complete_bug_id), + ]) + + def _run(self, **params): + assigned = params['assigned'] + if assigned == 'none': + assigned = None + elif assigned == '-': + assigned = self._get_user_id() + bugdir = self._get_bugdir() + for bug_id in params['bug-id']: + bug,dummy_comment = \ + libbe.command.util.bug_comment_from_user_id(bugdir, bug_id) + if bug.assigned != assigned: + bug.assigned = assigned + return 0 + + def _long_help(self): + return """ +Assign a person to fix a bug. + +Assigneds should be the person's Bugs Everywhere identity, the same +string that appears in Creator fields. + +Special assigned strings: + "-" assign the bug to yourself + "none" un-assigns the bug +""" diff --git a/libbe/command/base.py b/libbe/command/base.py new file mode 100644 index 0000000..f8bbb1f --- /dev/null +++ b/libbe/command/base.py @@ -0,0 +1,554 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import codecs +import optparse +import os.path +import StringIO +import sys + +import libbe +import libbe.storage +import libbe.ui.util.user +import libbe.util.encoding +import libbe.util.plugin + +class UserError(Exception): + pass + +class UnknownCommand(UserError): + def __init__(self, cmd): + Exception.__init__(self, "Unknown command '%s'" % cmd) + self.cmd = cmd + +def get_command(command_name): + """Retrieves the module for a user command + + >>> try: + ... get_command('asdf') + ... except UnknownCommand, e: + ... print e + Unknown command 'asdf' + >>> repr(get_command('list')).startswith("<module 'libbe.command.list' from ") + True + """ + try: + cmd = libbe.util.plugin.import_by_name( + 'libbe.command.%s' % command_name.replace("-", "_")) + except ImportError, e: + raise UnknownCommand(command_name) + return cmd + +def get_command_class(module=None, command_name=None): + """Retrieves a command class from a module. + + >>> import_xml_mod = get_command('import-xml') + >>> import_xml = get_command_class(import_xml_mod, 'import-xml') + >>> repr(import_xml) + "<class 'libbe.command.import_xml.Import_XML'>" + >>> import_xml = get_command_class(command_name='import-xml') + >>> repr(import_xml) + "<class 'libbe.command.import_xml.Import_XML'>" + """ + if module == None: + module = get_command(command_name) + try: + cname = command_name.capitalize().replace('-', '_') + cmd = getattr(module, cname) + except ImportError, e: + raise UnknownCommand(command_name) + return cmd + +def modname_to_command_name(modname): + """Little hack to replicate + >>> import sys + >>> def real_modname_to_command_name(modname): + ... mod = libbe.util.plugin.import_by_name( + ... 'libbe.command.%s' % modname) + ... attrs = [getattr(mod, name) for name in dir(mod)] + ... commands = [] + ... for attr_name in dir(mod): + ... attr = getattr(mod, attr_name) + ... try: + ... if issubclass(attr, Command): + ... commands.append(attr) + ... except TypeError, e: + ... pass + ... if len(commands) == 0: + ... raise Exception('No Command classes in %s' % dir(mod)) + ... return commands[0].name + >>> real_modname_to_command_name('new') + 'new' + >>> real_modname_to_command_name('import_xml') + 'import-xml' + """ + return modname.replace('_', '-') + +def commands(command_names=False): + for modname in libbe.util.plugin.modnames('libbe.command'): + if modname not in ['base', 'util']: + if command_names == False: + yield modname + else: + yield modname_to_command_name(modname) + +class CommandInput (object): + def __init__(self, name, help=''): + self.name = name + self.help = help + + def __str__(self): + return '<%s %s>' % (self.__class__.__name__, self.name) + + def __repr__(self): + return self.__str__() + +class Argument (CommandInput): + def __init__(self, metavar=None, default=None, type='string', + optional=False, repeatable=False, + completion_callback=None, *args, **kwargs): + CommandInput.__init__(self, *args, **kwargs) + self.metavar = metavar + self.default = default + self.type = type + self.optional = optional + self.repeatable = repeatable + self.completion_callback = completion_callback + if self.metavar == None: + self.metavar = self.name.upper() + +class Option (CommandInput): + def __init__(self, callback=None, short_name=None, arg=None, + *args, **kwargs): + CommandInput.__init__(self, *args, **kwargs) + self.callback = callback + self.short_name = short_name + self.arg = arg + if self.arg == None and self.callback == None: + # use an implicit boolean argument + self.arg = Argument(name=self.name, help=self.help, + default=False, type='bool') + self.validate() + + def validate(self): + if self.arg == None: + assert self.callback != None, self.name + return + assert self.callback == None, '%s: %s' (self.name, self.callback) + assert self.arg.name == self.name, \ + 'Name missmatch: %s != %s' % (self.arg.name, self.name) + assert self.arg.optional == False, self.name + assert self.arg.repeatable == False, self.name + + def __str__(self): + return '--%s' % self.name + + def __repr__(self): + return '<Option %s>' % self.__str__() + +class _DummyParser (optparse.OptionParser): + def __init__(self, command): + optparse.OptionParser.__init__(self) + self.remove_option('-h') + self.command = command + self._command_opts = [] + for option in self.command.options: + self._add_option(option) + + def _add_option(self, option): + # from libbe.ui.command_line.CmdOptionParser._add_option + option.validate() + long_opt = '--%s' % option.name + if option.short_name != None: + short_opt = '-%s' % option.short_name + assert '_' not in option.name, \ + 'Non-reconstructable option name %s' % option.name + kwargs = {'dest':option.name.replace('-', '_'), + 'help':option.help} + if option.arg == None or option.arg.type == 'bool': + kwargs['action'] = 'store_true' + kwargs['metavar'] = None + kwargs['default'] = False + else: + kwargs['type'] = option.arg.type + kwargs['action'] = 'store' + kwargs['metavar'] = option.arg.metavar + kwargs['default'] = option.arg.default + if option.short_name != None: + opt = optparse.Option(short_opt, long_opt, **kwargs) + else: + opt = optparse.Option(long_opt, **kwargs) + #option.takes_value = lambda : option.arg != None + opt._option = option + self._command_opts.append(opt) + self.add_option(opt) + +class OptionFormatter (optparse.IndentedHelpFormatter): + def __init__(self, command): + optparse.IndentedHelpFormatter.__init__(self) + self.command = command + def option_help(self): + # based on optparse.OptionParser.format_option_help() + parser = _DummyParser(self.command) + self.store_option_strings(parser) + ret = [] + ret.append(self.format_heading('Options')) + self.indent() + for option in parser._command_opts: + ret.append(self.format_option(option)) + ret.append('\n') + self.dedent() + # Drop the last '\n', or the header if no options or option groups: + return ''.join(ret[:-1]) + +class Command (object): + """One-line command description here. + + >>> c = Command() + >>> print c.help() + usage: be command [options] + <BLANKLINE> + Options: + -h, --help Print a help message. + <BLANKLINE> + --complete Print a list of possible completions. + <BLANKLINE> + A detailed help message. + """ + + name = 'command' + + def __init__(self, ui=None): + self.ui = ui # calling user-interface + self.status = None + self.result = None + self.restrict_file_access = True + self.options = [ + Option(name='help', short_name='h', + help='Print a help message.', + callback=self.help), + Option(name='complete', + help='Print a list of possible completions.', + callback=self.complete), + ] + self.args = [] + + def run(self, options=None, args=None): + self.status = 1 # in case we raise an exception + params = self._parse_options_args(options, args) + if params['help'] == True: + pass + else: + params.pop('help') + if params['complete'] != None: + pass + else: + params.pop('complete') + + self.status = self._run(**params) + return self.status + + def _parse_options_args(self, options=None, args=None): + if options == None: + options = {} + if args == None: + args = [] + params = {} + for option in self.options: + assert option.name not in params, params[option.name] + if option.name in options: + params[option.name] = options.pop(option.name) + elif option.arg != None: + params[option.name] = option.arg.default + else: # non-arg options are flags, set to default flag value + params[option.name] = False + assert 'user-id' not in params, params['user-id'] + if 'user-id' in options: + self._user_id = options.pop('user-id') + if len(options) > 0: + raise UserError, 'Invalid option passed to command %s:\n %s' \ + % (self.name, '\n '.join(['%s: %s' % (k,v) + for k,v in options.items()])) + in_optional_args = False + for i,arg in enumerate(self.args): + if arg.repeatable == True: + assert i == len(self.args)-1, arg.name + if in_optional_args == True: + assert arg.optional == True, arg.name + else: + in_optional_args = arg.optional + if i < len(args): + if arg.repeatable == True: + params[arg.name] = [args[i]] + else: + params[arg.name] = args[i] + else: # no value given + assert in_optional_args == True, arg.name + params[arg.name] = arg.default + if len(args) > len(self.args): # add some additional repeats + assert self.args[-1].repeatable == True, self.args[-1].name + params[self.args[-1].name].extend(args[len(self.args):]) + return params + + def _run(self, **kwargs): + raise NotImplementedError + + def help(self, *args): + return '\n\n'.join([self.usage(), + self._option_help(), + self._long_help().rstrip('\n')]) + + def usage(self): + usage = 'usage: be %s [options]' % self.name + num_optional = 0 + for arg in self.args: + usage += ' ' + if arg.optional == True: + usage += '[' + num_optional += 1 + usage += arg.metavar + if arg.repeatable == True: + usage += ' ...' + usage += ']'*num_optional + return usage + + def _option_help(self): + o = OptionFormatter(self) + return o.option_help().strip('\n') + + def _long_help(self): + return "A detailed help message." + + def complete(self, argument=None, fragment=None): + if argument == None: + ret = ['--%s' % o.name for o in self.options] + if len(self.args) > 0 and self.args[0].completion_callback != None: + ret.extend(self.args[0].completion_callback(self, argument, fragment)) + return ret + elif argument.completion_callback != None: + # finish a particular argument + return argument.completion_callback(self, argument, fragment) + return [] # the particular argument doesn't supply completion info + + def _check_restricted_access(self, storage, path): + """ + Check that the file at path is inside bugdir.root. This is + important if you allow other users to execute becommands with + your username (e.g. if you're running be-handle-mail through + your ~/.procmailrc). If this check wasn't made, a user could + e.g. run + be commit -b ~/.ssh/id_rsa "Hack to expose ssh key" + which would expose your ssh key to anyone who could read the + VCS log. + + >>> class DummyStorage (object): pass + >>> s = DummyStorage() + >>> s.repo = os.path.expanduser('~/x/') + >>> c = Command() + >>> try: + ... c._check_restricted_access(s, os.path.expanduser('~/.ssh/id_rsa')) + ... except UserError, e: + ... assert str(e).startswith('file access restricted!'), str(e) + ... print 'we got the expected error' + we got the expected error + >>> c._check_restricted_access(s, os.path.expanduser('~/x')) + >>> c._check_restricted_access(s, os.path.expanduser('~/x/y')) + >>> c.restrict_file_access = False + >>> c._check_restricted_access(s, os.path.expanduser('~/.ssh/id_rsa')) + """ + if self.restrict_file_access == True: + path = os.path.abspath(path) + repo = os.path.abspath(storage.repo).rstrip(os.path.sep) + if path == repo or path.startswith(repo+os.path.sep): + return + raise UserError('file access restricted!\n %s not in %s' + % (path, repo)) + + def cleanup(self): + pass + +class InputOutput (object): + def __init__(self, stdin=None, stdout=None): + self.stdin = stdin + self.stdout = stdout + + def setup_command(self, command): + if not hasattr(self.stdin, 'encoding'): + self.stdin.encoding = libbe.util.encoding.get_input_encoding() + if not hasattr(self.stdout, 'encoding'): + self.stdout.encoding = libbe.util.encoding.get_output_encoding() + command.stdin = self.stdin + command.stdin.encoding = self.stdin.encoding + command.stdout = self.stdout + command.stdout.encoding = self.stdout.encoding + + def cleanup(self): + pass + +class StdInputOutput (InputOutput): + def __init__(self, input_encoding=None, output_encoding=None): + stdin,stdout = self._get_io(input_encoding, output_encoding) + InputOutput.__init__(self, stdin, stdout) + + def _get_io(self, input_encoding=None, output_encoding=None): + if input_encoding == None: + input_encoding = libbe.util.encoding.get_input_encoding() + if output_encoding == None: + output_encoding = libbe.util.encoding.get_output_encoding() + stdin = codecs.getreader(input_encoding)(sys.stdin) + stdin.encoding = input_encoding + stdout = codecs.getwriter(output_encoding)(sys.stdout) + stdout.encoding = output_encoding + return (stdin, stdout) + +class StringInputOutput (InputOutput): + """ + >>> s = StringInputOutput() + >>> s.set_stdin('hello') + >>> s.stdin.read() + 'hello' + >>> s.stdin.read() + '' + >>> print >> s.stdout, 'goodbye' + >>> s.get_stdout() + 'goodbye\\n' + >>> s.get_stdout() + '' + + Also works with unicode strings + + >>> s.set_stdin(u'hello') + >>> s.stdin.read() + u'hello' + >>> print >> s.stdout, u'goodbye' + >>> s.get_stdout() + u'goodbye\\n' + """ + def __init__(self): + stdin = StringIO.StringIO() + stdin.encoding = 'utf-8' + stdout = StringIO.StringIO() + stdout.encoding = 'utf-8' + InputOutput.__init__(self, stdin, stdout) + + def set_stdin(self, stdin_string): + self.stdin = StringIO.StringIO(stdin_string) + + def get_stdout(self): + ret = self.stdout.getvalue() + self.stdout = StringIO.StringIO() # clear stdout for next read + self.stdin.encoding = 'utf-8' + return ret + +class UnconnectedStorageGetter (object): + def __init__(self, location): + self.location = location + + def __call__(self): + return libbe.storage.get_storage(self.location) + +class StorageCallbacks (object): + def __init__(self, location=None): + if location == None: + location = '.' + self.location = location + self._get_unconnected_storage = UnconnectedStorageGetter(location) + + def setup_command(self, command): + command._get_unconnected_storage = self.get_unconnected_storage + command._get_storage = self.get_storage + command._get_bugdir = self.get_bugdir + + def get_unconnected_storage(self): + """ + Callback for use by commands that need it. + + The returned Storage instance is may actually be connected, + but commands that make use of the returned value should only + make use of non-connected Storage methods. This is mainly + intended for the init command, which calls Storage.init(). + """ + if not hasattr(self, '_unconnected_storage'): + if self._get_unconnected_storage == None: + raise NotImplementedError + self._unconnected_storage = self._get_unconnected_storage() + return self._unconnected_storage + + def set_unconnected_storage(self, unconnected_storage): + self._unconnected_storage = unconnected_storage + + def get_storage(self): + """Callback for use by commands that need it.""" + if not hasattr(self, '_storage'): + self._storage = self.get_unconnected_storage() + self._storage.connect() + version = self._storage.storage_version() + if version != libbe.storage.STORAGE_VERSION: + raise libbe.storage.InvalidStorageVersion(version) + return self._storage + + def set_storage(self, storage): + self._storage = storage + + def get_bugdir(self): + """Callback for use by commands that need it.""" + if not hasattr(self, '_bugdir'): + self._bugdir = libbe.bugdir.BugDir(self.get_storage(), + from_storage=True) + return self._bugdir + + def set_bugdir(self, bugdir): + self._bugdir = bugdir + + def cleanup(self): + if hasattr(self, '_storage'): + self._storage.disconnect() + +class UserInterface (object): + def __init__(self, io=None, location=None): + if io == None: + io = StringInputOutput() + self.io = io + self.storage_callbacks = StorageCallbacks(location) + self.restrict_file_access = True + + def help(self): + raise NotImplementedError + + def run(self, command, options=None, args=None): + self.setup_command(command) + return command.run(options, args) + + def setup_command(self, command): + if command.ui == None: + command.ui = self + if self.io != None: + self.io.setup_command(command) + if self.storage_callbacks != None: + self.storage_callbacks.setup_command(command) + command.restrict_file_access = self.restrict_file_access + command._get_user_id = self._get_user_id + + def _get_user_id(self): + """Callback for use by commands that need it.""" + if not hasattr(self, '_user_id'): + self._user_id = libbe.ui.util.user.get_user_id( + self.storage_callbacks.get_storage()) + return self._user_id + + def cleanup(self): + self.storage_callbacks.cleanup() + self.io.cleanup() diff --git a/libbe/command/close.py b/libbe/command/close.py new file mode 100644 index 0000000..0532ed2 --- /dev/null +++ b/libbe/command/close.py @@ -0,0 +1,60 @@ +# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# Marien Zwart <marienz@gentoo.org> +# Thomas Gerigk <tgerigk@gmx.de> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Close a bug""" +from libbe import cmdutil, bugdir +__desc__ = __doc__ + +def execute(args, manipulate_encodings=True): + """ + >>> from libbe import bugdir + >>> import os + >>> bd = bugdir.SimpleBugDir() + >>> os.chdir(bd.root) + >>> print bd.bug_from_shortname("a").status + open + >>> execute(["a"], manipulate_encodings=False) + >>> bd._clear_bugs() + >>> print bd.bug_from_shortname("a").status + closed + >>> bd.cleanup() + """ + parser = get_parser() + options, args = parser.parse_args(args) + cmdutil.default_complete(options, args, parser, + bugid_args={0: lambda bug : bug.active==True}) + if len(args) == 0: + raise cmdutil.UsageError("Please specify a bug id.") + if len(args) > 1: + raise cmdutil.UsageError("Too many arguments.") + bd = bugdir.BugDir(from_disk=True, + manipulate_encodings=manipulate_encodings) + bug = cmdutil.bug_from_shortname(bd, args[0]) + bug.status = "closed" + bd.save() + +def get_parser(): + parser = cmdutil.CmdOptionParser("be close BUG-ID") + return parser + +longhelp=""" +Close the bug identified by BUG-ID. +""" + +def help(): + return get_parser().help_str() + longhelp diff --git a/libbe/command/comment.py b/libbe/command/comment.py new file mode 100644 index 0000000..5bf6acf --- /dev/null +++ b/libbe/command/comment.py @@ -0,0 +1,169 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os +import sys + +import libbe +import libbe.command +import libbe.command.util +import libbe.comment +import libbe.ui.util.editor +import libbe.util.id + + +class Comment (libbe.command.Command): + """Add a comment to a bug + + >>> import time + >>> import libbe.bugdir + >>> import libbe.util.id + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_storage(bd.storage) + >>> cmd = Comment(ui=ui) + + >>> uuid_gen = libbe.util.id.uuid_gen + >>> libbe.util.id.uuid_gen = lambda: 'X' + >>> ui._user_id = u'Fran\\xe7ois' + >>> ret = ui.run(cmd, args=['/a', 'This is a comment about a']) + Created comment with ID abc/a/X + >>> libbe.util.id.uuid_gen = uuid_gen + >>> bd.flush_reload() + >>> bug = bd.bug_from_uuid('a') + >>> bug.load_comments(load_full=False) + >>> comment = bug.comment_root[0] + >>> comment.id.storage() == comment.uuid + True + >>> print comment.body + This is a comment about a + <BLANKLINE> + >>> comment.author + u'Fran\\xe7ois' + >>> comment.time <= int(time.time()) + True + >>> comment.in_reply_to is None + True + + >>> if 'EDITOR' in os.environ: + ... del os.environ['EDITOR'] + >>> if 'VISUAL' in os.environ: + ... del os.environ['VISUAL'] + >>> ui._user_id = u'Frank' + >>> ret = ui.run(cmd, args=['/b']) + Traceback (most recent call last): + UserError: No comment supplied, and EDITOR not specified. + + >>> os.environ['EDITOR'] = "echo 'I like cheese' > " + >>> libbe.util.id.uuid_gen = lambda: 'Y' + >>> ret = ui.run(cmd, args=['/b']) + Created comment with ID abc/b/Y + >>> libbe.util.id.uuid_gen = uuid_gen + >>> bd.flush_reload() + >>> bug = bd.bug_from_uuid('b') + >>> bug.load_comments(load_full=False) + >>> comment = bug.comment_root[0] + >>> print comment.body + I like cheese + <BLANKLINE> + >>> ui.cleanup() + >>> bd.cleanup() + >>> del os.environ["EDITOR"] + """ + name = 'comment' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='author', short_name='a', + help='Set the comment author', + arg=libbe.command.Argument( + name='author', metavar='AUTHOR')), + libbe.command.Option(name='alt-id', + help='Set an alternate comment ID', + arg=libbe.command.Argument( + name='alt-id', metavar='ID')), + libbe.command.Option(name='content-type', short_name='c', + help='Set comment content-type (e.g. text/plain)', + arg=libbe.command.Argument(name='content-type', + metavar='MIME')), + ]) + self.args.extend([ + libbe.command.Argument( + name='id', metavar='ID', default=None, + completion_callback=libbe.command.util.complete_bug_comment_id), + libbe.command.Argument( + name='comment', metavar='COMMENT', default=None, + optional=True, + completion_callback=libbe.command.util.complete_assigned), + ]) + + def _run(self, **params): + bugdir = self._get_bugdir() + bug,parent = \ + libbe.command.util.bug_comment_from_user_id(bugdir, params['id']) + if params['comment'] == None: + # try to launch an editor for comment-body entry + try: + if parent == bug.comment_root: + parent_body = bug.summary+'\n' + else: + parent_body = parent.body + estr = 'Please enter your comment above\n\n> %s\n' \ + % ('\n> '.join(parent_body.splitlines())) + body = libbe.ui.util.editor.editor_string(estr) + except libbe.ui.util.editor.CantFindEditor, e: + raise libbe.command.UserError( + 'No comment supplied, and EDITOR not specified.') + if body is None: + raise libbe.command.UserError('No comment entered.') + elif params['comment'] == '-': # read body from stdin + binary = not (params['content-type'] == None + or params['content-type'].startswith("text/")) + if not binary: + body = self.stdin.read() + if not body.endswith('\n'): + body += '\n' + else: # read-in without decoding + body = sys.stdin.read() + else: # body given on command line + body = params['comment'] + if not body.endswith('\n'): + body+='\n' + if params['author'] == None: + params['author'] = self._get_user_id() + + new = parent.new_reply(body=body, content_type=params['content-type']) + for key in ['alt-id', 'author']: + if params[key] != None: + setattr(new, new._setting_name_to_attr_name(key), params[key]) + print >> self.stdout, 'Created comment with ID %s' % new.id.user() + return 0 + + def _long_help(self): + return """ +To add a comment to a bug, use the bug ID as the argument. To reply +to another comment, specify the comment name (as shown in "be show" +output). COMMENT, if specified, should be either the text of your +comment or "-", in which case the text will be read from stdin. If +you do not specify a COMMENT, $EDITOR is used to launch an editor. If +COMMENT is unspecified and EDITOR is not set, no comment will be +created. +""" diff --git a/libbe/command/commit.py b/libbe/command/commit.py new file mode 100644 index 0000000..fd15630 --- /dev/null +++ b/libbe/command/commit.py @@ -0,0 +1,93 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import sys + +import libbe +import libbe.bugdir +import libbe.command +import libbe.command.util +import libbe.storage +import libbe.ui.util.editor + + +class Commit (libbe.command.Command): + """Commit the currently pending changes to the repository + + >>> import sys + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False, versioned=True) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_storage(bd.storage) + >>> cmd = Commit(ui=ui) + + >>> bd.extra_strings = ['hi there'] + >>> bd.flush_reload() + >>> ui.run(cmd, args=['Making a commit']) # doctest: +ELLIPSIS + Committed ... + >>> ui.cleanup() + >>> bd.cleanup() + """ + name = 'commit' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='body', short_name='b', + help='Provide the detailed body for the commit message. In the special case that FILE == "EDITOR", spawn an editor to enter the body text (in which case you cannot use stdin for the summary)', + arg=libbe.command.Argument(name='body', metavar='FILE', + completion_callback=libbe.command.util.complete_path)), + libbe.command.Option(name='allow-empty', short_name='a', + help='Allow empty commits'), + ]) + self.args.extend([ + libbe.command.Argument( + name='comment', metavar='COMMENT', default=None), + ]) + + def _run(self, **params): + if params['comment'] == '-': # read summary from stdin + assert params['body'] != 'EDITOR', \ + 'Cannot spawn and editor when the summary is using stdin.' + summary = sys.stdin.readline() + else: + summary = params['comment'] + storage = self._get_storage() + if params['body'] == None: + body = None + elif params['body'] == 'EDITOR': + body = libbe.ui.util.editor.editor_string( + 'Please enter your commit message above') + else: + self._check_restricted_access(storage, params['body']) + body = libbe.util.encoding.get_file_contents( + params['body'], decode=True) + try: + revision = storage.commit(summary, body=body, + allow_empty=params['allow-empty']) + print >> self.stdout, 'Committed %s' % revision + except libbe.storage.EmptyCommit, e: + print >> self.stdout, e + return 1 + + def _long_help(self): + return """ +Commit the current repository status. The summary specified on the +commandline is a string (only one line) that describes the commit +briefly or "-", in which case the string will be read from stdin. +""" diff --git a/libbe/command/depend.py b/libbe/command/depend.py new file mode 100644 index 0000000..f87657b --- /dev/null +++ b/libbe/command/depend.py @@ -0,0 +1,408 @@ +# Copyright (C) 2009-2010 Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import copy +import os + +import libbe +import libbe.bug +import libbe.command +import libbe.command.util +import libbe.util.tree + +BLOCKS_TAG="BLOCKS:" +BLOCKED_BY_TAG="BLOCKED-BY:" + +class BrokenLink (Exception): + def __init__(self, blocked_bug, blocking_bug, blocks=True): + if blocks == True: + msg = "Missing link: %s blocks %s" \ + % (blocking_bug.uuid, blocked_bug.uuid) + else: + msg = "Missing link: %s blocked by %s" \ + % (blocked_bug.uuid, blocking_bug.uuid) + Exception.__init__(self, msg) + self.blocked_bug = blocked_bug + self.blocking_bug = blocking_bug + +class Depend (libbe.command.Command): + """Add/remove bug dependencies + + >>> import sys + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_storage(bd.storage) + >>> cmd = Depend(ui=ui) + + >>> ret = ui.run(cmd, args=['/a', '/b']) + a blocked by: + b + >>> ret = ui.run(cmd, args=['/a']) + a blocked by: + b + >>> ret = ui.run(cmd, {'show-status':True}, ['/a']) # doctest: +NORMALIZE_WHITESPACE + a blocked by: + b closed + >>> ret = ui.run(cmd, args=['/b', '/a']) + b blocked by: + a + b blocks: + a + >>> ret = ui.run(cmd, {'show-status':True}, ['/a']) # doctest: +NORMALIZE_WHITESPACE + a blocked by: + b closed + a blocks: + b closed + >>> ret = ui.run(cmd, {'repair':True}) + >>> ret = ui.run(cmd, {'remove':True}, ['/b', '/a']) + b blocks: + a + >>> ret = ui.run(cmd, {'remove':True}, ['/a', '/b']) + >>> ui.cleanup() + >>> bd.cleanup() + """ + name = 'depend' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='remove', short_name='r', + help='Remove dependency (instead of adding it)'), + libbe.command.Option(name='show-status', short_name='s', + help='Show status of blocking bugs'), + libbe.command.Option(name='status', + help='Only show bugs matching the STATUS specifier', + arg=libbe.command.Argument( + name='status', metavar='STATUS', default=None, + completion_callback=libbe.command.util.complete_status)), + libbe.command.Option(name='severity', + help='Only show bugs matching the SEVERITY specifier', + arg=libbe.command.Argument( + name='severity', metavar='SEVERITY', default=None, + completion_callback=libbe.command.util.complete_severity)), + libbe.command.Option(name='tree-depth', short_name='t', + help='Print dependency tree rooted at BUG-ID with DEPTH levels of both blockers and blockees. Set DEPTH <= 0 to disable the depth limit.', + arg=libbe.command.Argument( + name='tree-depth', metavar='INT', type='int', + completion_callback=libbe.command.util.complete_severity)), + libbe.command.Option(name='repair', + help='Check for and repair one-way links'), + ]) + self.args.extend([ + libbe.command.Argument( + name='bug-id', metavar='BUG-ID', default=None, + optional=True, + completion_callback=libbe.command.util.complete_bug_id), + libbe.command.Argument( + name='blocking-bug-id', metavar='BUG-ID', default=None, + optional=True, + completion_callback=libbe.command.util.complete_bug_id), + ]) + + def _run(self, **params): + if params['repair'] == True and params['bug-id'] != None: + raise libbe.command.UsageError( + 'No arguments with --repair calls.') + if params['repair'] == False and params['bug-id'] == None: + raise libbe.command.UsageError( + 'Must specify either --repair or a BUG-ID') + if params['tree-depth'] != None \ + and params['blocking-bug-id'] != None: + raise libbe.command.UsageError( + 'Only one bug id used in tree mode.') + bugdir = self._get_bugdir() + if params['repair'] == True: + good,fixed,broken = check_dependencies(bugdir, repair_broken_links=True) + assert len(broken) == 0, broken + if len(fixed) > 0: + print >> self.stdout, 'Fixed the following links:' + print >> self.stdout, \ + '\n'.join(['%s |-- %s' % (blockee.uuid, blocker.uuid) + for blockee,blocker in fixed]) + return 0 + allowed_status_values = \ + libbe.command.util.select_values( + params['status'], libbe.bug.status_values) + allowed_severity_values = \ + libbe.command.util.select_values( + params['severity'], libbe.bug.severity_values) + + bugA, dummy_comment = libbe.command.util.bug_comment_from_user_id( + bugdir, params['bug-id']) + + if params['tree-depth'] != None: + dtree = DependencyTree(bugdir, bugA, params['tree-depth'], + allowed_status_values, + allowed_severity_values) + if len(dtree.blocked_by_tree()) > 0: + print >> self.stdout, '%s blocked by:' % bugA.uuid + for depth,node in dtree.blocked_by_tree().thread(): + if depth == 0: continue + print >> self.stdout, \ + '%s%s' % (' '*(depth), + node.bug.string(shortlist=True)) + if len(dtree.blocks_tree()) > 0: + print >> self.stdout, '%s blocks:' % bugA.uuid + for depth,node in dtree.blocks_tree().thread(): + if depth == 0: continue + print >> self.stdout, \ + '%s%s' % (' '*(depth), + node.bug.string(shortlist=True)) + return 0 + + if params['blocking-bug-id'] != None: + bugB,dummy_comment = libbe.command.util.bug_comment_from_user_id( + bugdir, params['blocking-bug-id']) + if params['remove'] == True: + remove_block(bugA, bugB) + else: # add the dependency + add_block(bugA, bugB) + + blocked_by = get_blocked_by(bugdir, bugA) + if len(blocked_by) > 0: + print >> self.stdout, '%s blocked by:' % bugA.uuid + if params['show-status'] == True: + print >> self.stdout, \ + '\n'.join(['%s\t%s' % (_bug.uuid, _bug.status) + for _bug in blocked_by]) + else: + print >> self.stdout, \ + '\n'.join([_bug.uuid for _bug in blocked_by]) + blocks = get_blocks(bugdir, bugA) + if len(blocks) > 0: + print >> self.stdout, '%s blocks:' % bugA.uuid + if params['show-status'] == True: + print >> self.stdout, \ + '\n'.join(['%s\t%s' % (_bug.uuid, _bug.status) + for _bug in blocks]) + else: + print >> self.stdout, \ + '\n'.join([_bug.uuid for _bug in blocks]) + return 0 + + def _long_help(self): + return """ +Set a dependency with the second bug (B) blocking the first bug (A). +If bug B is not specified, just print a list of bugs blocking (A). + +To search for bugs blocked by a particular bug, try + $ be list --extra-strings BLOCKED-BY:<your-bug-uuid> + +The --status and --severity options allow you to either blacklist or +whitelist values, for example + $ be list --status open,assigned +will only follow and print dependencies with open or assigned status. +You select blacklist mode by starting the list with a minus sign, for +example + $ be list --severity -target +which will only follow and print dependencies with non-target severity. + +If neither bug A nor B is specified, check for and repair the missing +side of any one-way links. + +The "|--" symbol in the repair-mode output is inspired by the +"negative feedback" arrow common in biochemistry. See, for example + http://www.nature.com/nature/journal/v456/n7223/images/nature07513-f5.0.jpg +""" + +# internal helper functions + +def _generate_blocks_string(blocked_bug): + return '%s%s' % (BLOCKS_TAG, blocked_bug.uuid) + +def _generate_blocked_by_string(blocking_bug): + return '%s%s' % (BLOCKED_BY_TAG, blocking_bug.uuid) + +def _parse_blocks_string(string): + assert string.startswith(BLOCKS_TAG) + return string[len(BLOCKS_TAG):] + +def _parse_blocked_by_string(string): + assert string.startswith(BLOCKED_BY_TAG) + return string[len(BLOCKED_BY_TAG):] + +def _add_remove_extra_string(bug, string, add): + estrs = bug.extra_strings + if add == True: + estrs.append(string) + else: # remove the string + estrs.remove(string) + bug.extra_strings = estrs # reassign to notice change + +def _get_blocks(bug): + uuids = [] + for line in bug.extra_strings: + if line.startswith(BLOCKS_TAG): + uuids.append(_parse_blocks_string(line)) + return uuids + +def _get_blocked_by(bug): + uuids = [] + for line in bug.extra_strings: + if line.startswith(BLOCKED_BY_TAG): + uuids.append(_parse_blocked_by_string(line)) + return uuids + +def _repair_one_way_link(blocked_bug, blocking_bug, blocks=None): + if blocks == True: # add blocks link + blocks_string = _generate_blocks_string(blocked_bug) + _add_remove_extra_string(blocking_bug, blocks_string, add=True) + else: # add blocked by link + blocked_by_string = _generate_blocked_by_string(blocking_bug) + _add_remove_extra_string(blocked_bug, blocked_by_string, add=True) + +# functions exposed to other modules + +def add_block(blocked_bug, blocking_bug): + blocked_by_string = _generate_blocked_by_string(blocking_bug) + _add_remove_extra_string(blocked_bug, blocked_by_string, add=True) + blocks_string = _generate_blocks_string(blocked_bug) + _add_remove_extra_string(blocking_bug, blocks_string, add=True) + +def remove_block(blocked_bug, blocking_bug): + blocked_by_string = _generate_blocked_by_string(blocking_bug) + _add_remove_extra_string(blocked_bug, blocked_by_string, add=False) + blocks_string = _generate_blocks_string(blocked_bug) + _add_remove_extra_string(blocking_bug, blocks_string, add=False) + +def get_blocks(bugdir, bug): + """ + Return a list of bugs that the given bug blocks. + """ + blocks = [] + for uuid in _get_blocks(bug): + blocks.append(bugdir.bug_from_uuid(uuid)) + return blocks + +def get_blocked_by(bugdir, bug): + """ + Return a list of bugs blocking the given bug. + """ + blocked_by = [] + for uuid in _get_blocked_by(bug): + blocked_by.append(bugdir.bug_from_uuid(uuid)) + return blocked_by + +def check_dependencies(bugdir, repair_broken_links=False): + """ + Check that links are bi-directional for all bugs in bugdir. + + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir() + >>> a = bd.bug_from_uuid("a") + >>> b = bd.bug_from_uuid("b") + >>> blocked_by_string = _generate_blocked_by_string(b) + >>> _add_remove_extra_string(a, blocked_by_string, add=True) + >>> good,repaired,broken = check_dependencies(bd, repair_broken_links=False) + >>> good + [] + >>> repaired + [] + >>> broken + [(Bug(uuid='a'), Bug(uuid='b'))] + >>> _get_blocks(b) + [] + >>> good,repaired,broken = check_dependencies(bd, repair_broken_links=True) + >>> _get_blocks(b) + ['a'] + >>> good + [] + >>> repaired + [(Bug(uuid='a'), Bug(uuid='b'))] + >>> broken + [] + """ + if bugdir.storage != None: + bugdir.load_all_bugs() + good_links = [] + fixed_links = [] + broken_links = [] + for bug in bugdir: + for blocker in get_blocked_by(bugdir, bug): + blocks = get_blocks(bugdir, blocker) + if (bug, blocks) in good_links+fixed_links+broken_links: + continue # already checked that link + if bug not in blocks: + if repair_broken_links == True: + _repair_one_way_link(bug, blocker, blocks=True) + fixed_links.append((bug, blocker)) + else: + broken_links.append((bug, blocker)) + else: + good_links.append((bug, blocker)) + for blockee in get_blocks(bugdir, bug): + blocked_by = get_blocked_by(bugdir, blockee) + if (blockee, bug) in good_links+fixed_links+broken_links: + continue # already checked that link + if bug not in blocked_by: + if repair_broken_links == True: + _repair_one_way_link(blockee, bug, blocks=False) + fixed_links.append((blockee, bug)) + else: + broken_links.append((blockee, bug)) + else: + good_links.append((blockee, bug)) + return (good_links, fixed_links, broken_links) + +class DependencyTree (object): + """ + Note: should probably be DependencyDiGraph. + """ + def __init__(self, bugdir, root_bug, depth_limit=0, + allowed_status_values=None, + allowed_severity_values=None): + self.bugdir = bugdir + self.root_bug = root_bug + self.depth_limit = depth_limit + self.allowed_status_values = allowed_status_values + self.allowed_severity_values = allowed_severity_values + + def _build_tree(self, child_fn): + root = tree.Tree() + root.bug = self.root_bug + root.depth = 0 + stack = [root] + while len(stack) > 0: + node = stack.pop() + if self.depth_limit > 0 and node.depth == self.depth_limit: + continue + for bug in child_fn(self.bugdir, node.bug): + if self.allowed_status_values != None \ + and not bug.status in self.allowed_status_values: + continue + if self.allowed_severity_values != None \ + and not bug.severity in self.allowed_severity_values: + continue + child = tree.Tree() + child.bug = bug + child.depth = node.depth+1 + node.append(child) + stack.append(child) + return root + + def blocks_tree(self): + if not hasattr(self, "_blocks_tree"): + self._blocks_tree = self._build_tree(get_blocks) + return self._blocks_tree + + def blocked_by_tree(self): + if not hasattr(self, "_blocked_by_tree"): + self._blocked_by_tree = self._build_tree(get_blocked_by) + return self._blocked_by_tree diff --git a/libbe/command/diff.py b/libbe/command/diff.py new file mode 100644 index 0000000..967ab14 --- /dev/null +++ b/libbe/command/diff.py @@ -0,0 +1,139 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import libbe +import libbe.bugdir +import libbe.bug +import libbe.command +import libbe.command.util +import libbe.storage + +import libbe.diff + +class Diff (libbe.command.Command): + __doc__ = """Compare bug reports with older tree + + >>> import sys + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False, versioned=True) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_storage(bd.storage) + >>> cmd = Diff() + + >>> original = bd.storage.commit('Original status') + >>> bug = bd.bug_from_uuid('a') + >>> bug.status = 'closed' + >>> changed = bd.storage.commit('Closed bug a') + >>> ret = ui.run(cmd, args=[original]) + Modified bugs: + abc/a:cm: Bug A + Changed bug settings: + status: open -> closed + >>> ret = ui.run(cmd, {'subscribe':'%(bugdir_id)s:mod', 'uuids':True}, [original]) + a + >>> bd.storage.versioned = False + >>> ret = ui.run(cmd, args=[original]) + Traceback (most recent call last): + ... + UserError: This repository is not revision-controlled. + >>> ui.cleanup() + >>> bd.cleanup() + """ % {'bugdir_id':libbe.diff.BUGDIR_ID} + name = 'diff' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='repo', short_name='r', + help='Compare with repository in REPO instead' + ' of the current repository.', + arg=libbe.command.Argument( + name='repo', metavar='REPO', + completion_callback=libbe.command.util.complete_path)), + libbe.command.Option(name='subscribe', short_name='s', + help='Only print changes matching SUBSCRIPTION, ' + 'subscription is a comma-separated list of ID:TYPE ' + 'tuples. See `be subscribe --help` for descriptions ' + 'of ID and TYPE.', + arg=libbe.command.Argument( + name='subscribe', metavar='SUBSCRIPTION')), + libbe.command.Option(name='uuids', short_name='u', + help='Only print the changed bug UUIDS.'), + ]) + self.args.extend([ + libbe.command.Argument( + name='revision', metavar='REVISION', default=None, + optional=True) + ]) + + def _run(self, **params): + try: + subscriptions = libbe.diff.subscriptions_from_string( + params['subscribe']) + except ValueError, e: + raise libbe.command.UserError(e.msg) + bugdir = self._get_bugdir() + if bugdir.storage.versioned == False: + raise libbe.command.UserError( + 'This repository is not revision-controlled.') + if params['repo'] == None: + if params['revision'] == None: # get the most recent revision + params['revision'] = bugdir.storage.revision_id(-1) + old_bd = libbe.bugdir.RevisionedBugDir(bugdir, params['revision']) + else: + old_storage = libbe.storage.get_storage(params['repo']) + old_storage.connect() + old_bd_current = libbe.bugdir.BugDir(old_storage, from_disk=True) + if params['revision'] == None: # use the current working state + old_bd = old_bd_current + else: + if old_bd_current.storage.versioned == False: + raise libbe.command.UserError( + '%s is not revision-controlled.' + % storage.repo) + old_bd = libbe.bugdir.RevisionedBugDir(old_bd_current,revision) + d = libbe.diff.Diff(old_bd, bugdir) + tree = d.report_tree(subscriptions) + + if params['uuids'] == True: + uuids = [] + bugs = tree.child_by_path('/bugs') + for bug_type in bugs: + uuids.extend([bug.name for bug in bug_type]) + print >> self.stdout, '\n'.join(uuids) + else : + rep = tree.report_string() + if rep != None: + print >> self.stdout, rep + return 0 + + def _long_help(self): + return """ +Uses the storage backend to compare the current tree with a previous +tree, and prints a pretty report. If REVISION is given, it is a +specifier for the particular previous tree to use. Specifiers are +specific to their storage backend. + +For Arch your specifier must be a fully-qualified revision name. + +Besides the standard summary output, you can use the options to output +UUIDS for the different categories. This output can be used as the +input to 'be show' to get an understanding of the current status. +""" diff --git a/libbe/command/due.py b/libbe/command/due.py new file mode 100644 index 0000000..4463455 --- /dev/null +++ b/libbe/command/due.py @@ -0,0 +1,117 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import libbe +import libbe.command +import libbe.command.util +import libbe.util.utility + + +DUE_TAG = 'DUE:' + + +class Due (libbe.command.Command): + """Set bug due dates + + >>> import sys + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_storage(bd.storage) + >>> cmd = Due(ui=ui) + + >>> ret = ui.run(cmd, args=['/a']) + No due date assigned. + >>> ret = ui.run(cmd, args=['/a', 'Thu, 01 Jan 1970 00:00:00 +0000']) + >>> ret = ui.run(cmd, args=['/a']) + Thu, 01 Jan 1970 00:00:00 +0000 + >>> ret = ui.run(cmd, args=['/a', 'none']) + >>> ret = ui.run(cmd, args=['/a']) + No due date assigned. + >>> ui.cleanup() + >>> bd.cleanup() + """ + name = 'due' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.args.extend([ + libbe.command.Argument( + name='bug-id', metavar='BUG-ID', + completion_callback=libbe.command.util.complete_bug_id), + libbe.command.Argument( + name='due', metavar='DUE', optional=True), + ]) + + def _run(self, **params): + bugdir = self._get_bugdir() + bug,dummy_comment = libbe.command.util.bug_comment_from_user_id( + bugdir, params['bug-id']) + if params['due'] == None: + due_time = get_due(bug) + if due_time is None: + print >> self.stdout, 'No due date assigned.' + else: + print >> self.stdout, libbe.util.utility.time_to_str(due_time) + else: + if params['due'] == 'none': + remove_due(bug) + else: + due_time = libbe.util.utility.str_to_time(params['due']) + set_due(bug, due_time) + + def _long_help(self): + return """ +If no DATE is specified, the bug's current due date is printed. If +DATE is specified, it will be assigned to the bug. +""" + +# internal helper functions + +def _generate_due_string(time): + return "%s%s" % (DUE_TAG, libbe.util.utility.time_to_str(time)) + +def _parse_due_string(string): + assert string.startswith(DUE_TAG) + return libbe.util.utility.str_to_time(string[len(DUE_TAG):]) + +# functions exposed to other modules + +def get_due(bug): + matched = [] + for line in bug.extra_strings: + if line.startswith(DUE_TAG): + matched.append(_parse_due_string(line)) + if len(matched) == 0: + return None + if len(matched) > 1: + raise Exception('Several due dates for %s?:\n %s' + % (bug.uuid, '\n '.join(matched))) + return matched[0] + +def remove_due(bug): + estrs = bug.extra_strings + for due_str in [s for s in estrs if s.startswith(DUE_TAG)]: + estrs.remove(due_str) + bug.extra_strings = estrs # reassign to notice change + +def set_due(bug, time): + remove_due(bug) + estrs = bug.extra_strings + estrs.append(_generate_due_string(time)) + bug.extra_strings = estrs # reassign to notice change diff --git a/libbe/command/help.py b/libbe/command/help.py new file mode 100644 index 0000000..1fc88f0 --- /dev/null +++ b/libbe/command/help.py @@ -0,0 +1,82 @@ +# Copyright (C) 2006-2010 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# Thomas Gerigk <tgerigk@gmx.de> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import libbe +import libbe.command +import libbe.command.util + +TOPICS = {} + +class Help (libbe.command.Command): + """Print help for given command or topic + + >>> import sys + >>> import libbe.bugdir + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> cmd = Help() + + >>> ret = ui.run(cmd, args=['help']) + usage: be help [options] [TOPIC] + <BLANKLINE> + Options: + -h, --help Print a help message. + <BLANKLINE> + --complete Print a list of possible completions. + <BLANKLINE> + <BLANKLINE> + Print help for specified command/topic or list of all commands. + """ + name = 'help' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.args.extend([ + libbe.command.Argument( + name='topic', metavar='TOPIC', default=None, + optional=True, + completion_callback=self.complete_topic) + ]) + + def _run(self, **params): + if params['topic'] == None: + if hasattr(self.ui, 'help'): + print >> self.stdout, self.ui.help().rstrip('\n') + elif params['topic'] in libbe.command.commands(): + module = libbe.command.get_command(params['topic']) + Class = libbe.command.get_command_class(module,params['topic']) + c = Class(ui=self.ui) + print >> self.stdout, c.help().rstrip('\n') + elif params['topic'] in TOPICS: + print >> self.stdout, TOPICS[params['topic']].rstrip('\n') + else: + raise libbe.command.UserError( + '"%s" is neither a command nor topic' % params['topic']) + return 0 + + def _long_help(self): + return """ +Print help for specified command/topic or list of all commands. +""" + + def complete_topic(self, command, argument, fragment=None): + commands = libbe.command.util.complete_command() + topics = sorted(TOPICS.keys()) + return commands + topics diff --git a/libbe/command/html.py b/libbe/command/html.py new file mode 100644 index 0000000..c9f89f3 --- /dev/null +++ b/libbe/command/html.py @@ -0,0 +1,719 @@ +# Copyright (C) 2009-2010 Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import codecs +import htmlentitydefs +import os +import os.path +import re +import string +import time +import xml.sax.saxutils + +import libbe +import libbe.command +import libbe.command.util +import libbe.comment +import libbe.util.encoding +import libbe.util.id + + +class HTML (libbe.command.Command): + """Generate a static HTML dump of the current repository status + + >>> import sys + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_storage(bd.storage) + >>> cmd = HTML(ui=ui) + + >>> ret = ui.run(cmd, {'output':os.path.join(bd.storage.repo, 'html_export')}) + >>> os.path.exists(os.path.join(bd.storage.repo, 'html_export')) + True + >>> os.path.exists(os.path.join(bd.storage.repo, 'html_export', 'index.html')) + True + >>> os.path.exists(os.path.join(bd.storage.repo, 'html_export', 'index_inactive.html')) + True + >>> os.path.exists(os.path.join(bd.storage.repo, 'html_export', 'bugs')) + True + >>> os.path.exists(os.path.join(bd.storage.repo, 'html_export', 'bugs', 'a', 'index.html')) + True + >>> os.path.exists(os.path.join(bd.storage.repo, 'html_export', 'bugs', 'b', 'index.html')) + True + >>> ui.cleanup() + >>> bd.cleanup() + """ + name = 'html' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='output', short_name='o', + help='Set the output path (%default)', + arg=libbe.command.Argument( + name='output', metavar='DIR', default='./html_export', + completion_callback=libbe.command.util.complete_path)), + libbe.command.Option(name='template-dir', short_name='t', + help='Use a different template. Defaults to internal templates', + arg=libbe.command.Argument( + name='template-dir', metavar='DIR', + completion_callback=libbe.command.util.complete_path)), + libbe.command.Option(name='title', + help='Set the bug repository title (%default)', + arg=libbe.command.Argument( + name='title', metavar='STRING', + default='BugsEverywhere Issue Tracker')), + libbe.command.Option(name='index-header', + help='Set the index page headers (%default)', + arg=libbe.command.Argument( + name='index-header', metavar='STRING', + default='BugsEverywhere Bug List')), + libbe.command.Option(name='export-template', short_name='e', + help='Export the default template and exit.'), + libbe.command.Option(name='export-template-dir', short_name='d', + help='Set the directory for the template export (%default)', + arg=libbe.command.Argument( + name='export-template-dir', metavar='DIR', + default='./default-templates/', + completion_callback=libbe.command.util.complete_path)), + libbe.command.Option(name='min-id-length', short_name='l', + help='Attempt to truncate bug and comment IDs to this length. Set to -1 for non-truncated IDs (%default)', + arg=libbe.command.Argument( + name='min-id-length', metavar='INT', + default=-1, type='int')), + libbe.command.Option(name='verbose', short_name='v', + help='Verbose output, default is %default'), + ]) + + def _run(self, **params): + if params['export-template'] == True: + html_gen.write_default_template(params['export-template-dir']) + return 0 + bugdir = self._get_bugdir() + bugdir.load_all_bugs() + html_gen = HTMLGen(bugdir, + template=params['template-dir'], + title=params['title'], + index_header=params['index-header'], + min_id_length=params['min-id-length'], + verbose=params['verbose'], + stdout=self.stdout) + html_gen.run(params['output']) + return 0 + + def _long_help(self): + return """ +Generate a set of html pages representing the current state of the bug +directory. +""" + +Html = HTML # alias for libbe.command.base.get_command_class() + +class HTMLGen (object): + def __init__(self, bd, template=None, + title="Site Title", index_header="Index Header", + min_id_length=-1, + verbose=False, encoding=None, stdout=None, + ): + self.generation_time = time.ctime() + self.bd = bd + if template == None: + self.template = "default" + else: + self.template = os.path.abspath(os.path.expanduser(template)) + self.title = title + self.index_header = index_header + self.verbose = verbose + self.stdout = stdout + if encoding != None: + self.encoding = encoding + else: + self.encoding = libbe.util.encoding.get_filesystem_encoding() + self._load_default_templates() + if template != None: + self._load_user_templates() + self.min_id_length = min_id_length + + def run(self, out_dir): + if self.verbose == True: + print >> self.stdout, \ + 'Creating the html output in %s using templates in %s' \ + % (out_dir, self.template) + + bugs_active = [] + bugs_inactive = [] + bugs = [b for b in self.bd] + bugs.sort() + bugs_active = [b for b in bugs if b.active == True] + bugs_inactive = [b for b in bugs if b.active != True] + + self._create_output_directories(out_dir) + self._write_css_file() + for b in bugs: + if b.active: + up_link = '../../index.html' + else: + up_link = '../../index_inactive.html' + self._write_bug_file(b, up_link) + self._write_index_file( + bugs_active, title=self.title, + index_header=self.index_header, bug_type='active') + self._write_index_file( + bugs_inactive, title=self.title, + index_header=self.index_header, bug_type='inactive') + + def _truncated_bug_id(self, bug): + return libbe.util.id._truncate( + bug.uuid, bug.sibling_uuids(), + min_length=self.min_id_length) + + def _truncated_comment_id(self, comment): + return libbe.util.id._truncate( + comment.uuid, comment.sibling_uuids(), + min_length=self.min_id_length) + + def _create_output_directories(self, out_dir): + if self.verbose: + print >> self.stdout, 'Creating output directories' + self.out_dir = self._make_dir(out_dir) + self.out_dir_bugs = self._make_dir( + os.path.join(self.out_dir, 'bugs')) + + def _write_css_file(self): + if self.verbose: + print >> self.stdout, 'Writing css file' + assert hasattr(self, 'out_dir'), \ + 'Must run after ._create_output_directories()' + self._write_file(self.css_file, + [self.out_dir,'style.css']) + + def _write_bug_file(self, bug, up_link): + if self.verbose: + print >> self.stdout, '\tCreating bug file for %s' % bug.id.user() + assert hasattr(self, 'out_dir_bugs'), \ + 'Must run after ._create_output_directories()' + + bug.load_comments(load_full=True) + comment_entries = self._generate_bug_comment_entries(bug) + dirname = self._truncated_bug_id(bug) + fullpath = os.path.join(self.out_dir_bugs, dirname, 'index.html') + template_info = {'title':self.title, + 'charset':self.encoding, + 'up_link':up_link, + 'shortname':bug.id.user(), + 'comment_entries':comment_entries, + 'generation_time':self.generation_time} + for attr in ['uuid', 'severity', 'status', 'assigned', + 'reporter', 'creator', 'time_string', 'summary']: + template_info[attr] = self._escape(getattr(bug, attr)) + fulldir = os.path.join(self.out_dir_bugs, dirname) + if not os.path.exists(fulldir): + os.mkdir(fulldir) + self._write_file(self.bug_file % template_info, [fullpath]) + + def _generate_bug_comment_entries(self, bug): + assert hasattr(self, 'out_dir_bugs'), \ + 'Must run after ._create_output_directories()' + + stack = [] + comment_entries = [] + bug.comment_root.sort(cmp=libbe.comment.cmp_time, reverse=True) + for depth,comment in bug.comment_root.thread(flatten=False): + while len(stack) > depth: + # pop non-parents off the stack + stack.pop(-1) + # close non-parent <div class="comment... + comment_entries.append('</div>\n') + assert len(stack) == depth + stack.append(comment) + template_info = { + 'shortname': comment.id.user(), + 'truncated_id': self._truncated_comment_id(comment)} + if depth == 0: + comment_entries.append('<div class="comment root">') + else: + comment_entries.append( + '<div class="comment" id="%s">' + % template_info['truncated_id']) + for attr in ['uuid', 'author', 'date', 'body']: + value = getattr(comment, attr) + if attr == 'body': + link_long_ids = False + save_body = False + if comment.content_type == 'text/html': + link_long_ids = True + elif comment.content_type.startswith('text/'): + value = '<pre>\n'+self._escape(value)+'\n</pre>' + link_long_ids = True + elif comment.content_type.startswith('image/'): + save_body = True + value = '<img src="./%s/%s" />' \ + % (self._truncated_bug_id(bug), + self._truncated_comment_id(comment)) + else: + save_body = True + value = '<a href="./%s/%s">Link to %s file</a>.' \ + % (self._truncated_bug_id(bug), + self._truncated_comment_id(comment), + comment.content_type) + if link_long_ids == True: + value = self._long_to_linked_user(value) + if save_body == True: + per_bug_dir = os.path.join(self.out_dir_bugs, bug.uuid) + if not os.path.exists(per_bug_dir): + os.mkdir(per_bug_dir) + comment_path = os.path.join(per_bug_dir, comment.uuid) + self._write_file( + '<Files %s>\n ForceType %s\n</Files>' \ + % (comment.uuid, comment.content_type), + [per_bug_dir, '.htaccess'], mode='a') + self._write_file(comment.body, + [per_bug_dir, comment.uuid], mode='wb') + else: + value = self._escape(value) + template_info[attr] = value + comment_entries.append(self.bug_comment_entry % template_info) + while len(stack) > 0: + stack.pop(-1) + comment_entries.append('</div>\n') # close every remaining <div class='comment... + return '\n'.join(comment_entries) + + def _long_to_linked_user(self, text): + """ + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> h = HTMLGen(bd) + >>> h._long_to_linked_user('A link #abc123/a#, and a non-link #x#y#.') + 'A link <a href="./a/">abc/a</a>, and a non-link #x#y#.' + >>> bd.cleanup() + """ + replacer = libbe.util.id.IDreplacer( + [self.bd], self._long_to_linked_user_replacer, wrap=False) + return re.sub( + libbe.util.id.REGEXP, replacer, text) + + def _long_to_linked_user_replacer(self, bugdirs, long_id): + """ + >>> import libbe.bugdir + >>> import libbe.util.id + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> a = bd.bug_from_uuid('a') + >>> uuid_gen = libbe.util.id.uuid_gen + >>> libbe.util.id.uuid_gen = lambda : '0123' + >>> c = a.new_comment('comment for link testing') + >>> libbe.util.id.uuid_gen = uuid_gen + >>> c.uuid + '0123' + >>> h = HTMLGen(bd) + >>> h._long_to_linked_user_replacer([bd], 'abc123') + '#abc123#' + >>> h._long_to_linked_user_replacer([bd], 'abc123/a') + '<a href="./a/">abc/a</a>' + >>> h._long_to_linked_user_replacer([bd], 'abc123/a/0123') + '<a href="./a/#0123">abc/a/012</a>' + >>> h._long_to_linked_user_replacer([bd], 'x') + '#x#' + >>> h._long_to_linked_user_replacer([bd], '') + '##' + >>> bd.cleanup() + """ + try: + p = libbe.util.id.parse_user(bugdirs[0], long_id) + except (libbe.util.id.MultipleIDMatches, + libbe.util.id.NoIDMatches, + libbe.util.id.InvalidIDStructure), e: + return '#%s#' % long_id # re-wrap failures + if p['type'] == 'bugdir': + return '#%s#' % long_id + elif p['type'] == 'bug': + bug,comment = libbe.command.util.bug_comment_from_user_id( + bugdirs[0], long_id) + return '<a href="./%s/">%s</a>' \ + % (self._truncated_bug_id(bug), bug.id.user()) + elif p['type'] == 'comment': + bug,comment = libbe.command.util.bug_comment_from_user_id( + bugdirs[0], long_id) + return '<a href="./%s/#%s">%s</a>' \ + % (self._truncated_bug_id(bug), + self._truncated_comment_id(comment), + comment.id.user()) + raise Exception('Invalid id type %s for "%s"' + % (p['type'], long_id)) + + def _write_index_file(self, bugs, title, index_header, bug_type='active'): + if self.verbose: + print >> self.stdout, 'Writing %s index file for %d bugs' % (bug_type, len(bugs)) + assert hasattr(self, 'out_dir'), 'Must run after ._create_output_directories()' + esc = self._escape + + bug_entries = self._generate_index_bug_entries(bugs) + + if bug_type == 'active': + filename = 'index.html' + elif bug_type == 'inactive': + filename = 'index_inactive.html' + else: + raise Exception, 'Unrecognized bug_type: "%s"' % bug_type + template_info = {'title':title, + 'index_header':index_header, + 'charset':self.encoding, + 'active_class':'tab sel', + 'inactive_class':'tab nsel', + 'bug_entries':bug_entries, + 'generation_time':self.generation_time} + if bug_type == 'inactive': + template_info['active_class'] = 'tab nsel' + template_info['inactive_class'] = 'tab sel' + + self._write_file(self.index_file % template_info, + [self.out_dir, filename]) + + def _generate_index_bug_entries(self, bugs): + bug_entries = [] + for bug in bugs: + if self.verbose: + print >> self.stdout, '\tCreating bug entry for %s' % bug.id.user() + template_info = {'shortname':bug.id.user()} + for attr in ['uuid', 'severity', 'status', 'assigned', + 'reporter', 'creator', 'time_string', 'summary']: + template_info[attr] = self._escape(getattr(bug, attr)) + template_info['dir'] = self._truncated_bug_id(bug) + bug_entries.append(self.index_bug_entry % template_info) + return '\n'.join(bug_entries) + + def _escape(self, string): + if string == None: + return '' + return xml.sax.saxutils.escape(string) + + def _load_user_templates(self): + for filename,attr in [('style.css','css_file'), + ('index_file.tpl','index_file'), + ('index_bug_entry.tpl','index_bug_entry'), + ('bug_file.tpl','bug_file'), + ('bug_comment_entry.tpl','bug_comment_entry')]: + fullpath = os.path.join(self.template, filename) + if os.path.exists(fullpath): + setattr(self, attr, self._read_file([fullpath])) + + def _make_dir(self, dir_path): + dir_path = os.path.abspath(os.path.expanduser(dir_path)) + if not os.path.exists(dir_path): + try: + os.makedirs(dir_path) + except: + raise libbe.command.UserError( + 'Cannot create output directory "%s".' % dir_path) + return dir_path + + def _write_file(self, content, path_array, mode='w'): + return libbe.util.encoding.set_file_contents( + os.path.join(*path_array), content, mode, self.encoding) + + def _read_file(self, path_array, mode='r'): + return libbe.util.encoding.get_file_contents( + os.path.join(*path_array), mode, self.encoding, decode=True) + + def write_default_template(self, out_dir): + if self.verbose: + print >> self.stdout, 'Creating output directories' + self.out_dir = self._make_dir(out_dir) + if self.verbose: + print >> self.stdout, 'Creating css file' + self._write_css_file() + if self.verbose: + print >> self.stdout, 'Creating index_file.tpl file' + self._write_file(self.index_file, + [self.out_dir, 'index_file.tpl']) + if self.verbose: + print >> self.stdout, 'Creating index_bug_entry.tpl file' + self._write_file(self.index_bug_entry, + [self.out_dir, 'index_bug_entry.tpl']) + if self.verbose: + print >> self.stdout, 'Creating bug_file.tpl file' + self._write_file(self.bug_file, + [self.out_dir, 'bug_file.tpl']) + if self.verbose: + print >> self.stdout, 'Creating bug_comment_entry.tpl file' + self._write_file(self.bug_comment_entry, + [self.out_dir, 'bug_comment_entry.tpl']) + + def _load_default_templates(self): + self.css_file = """ + body { + font-family: "lucida grande", "sans serif"; + color: #333; + width: auto; + margin: auto; + } + + div.main { + padding: 20px; + margin: auto; + padding-top: 0; + margin-top: 1em; + background-color: #fcfcfc; + } + + div.footer { + font-size: small; + padding-left: 20px; + padding-right: 20px; + padding-top: 5px; + padding-bottom: 5px; + margin: auto; + background: #305275; + color: #fffee7; + } + + table { + border-style: solid; + border: 10px #313131; + border-spacing: 0; + width: auto; + } + + tb { border: 1px; } + + tr { + vertical-align: top; + width: auto; + } + + td { + border-width: 0; + border-style: none; + padding-right: 0.5em; + padding-left: 0.5em; + width: auto; + } + + img { border-style: none; } + + h1 { + padding: 0.5em; + background-color: #305275; + margin-top: 0; + margin-bottom: 0; + color: #fff; + margin-left: -20px; + margin-right: -20px; + } + + ul { + list-style-type: none; + padding: 0; + } + + p { width: auto; } + + a, a:visited { + background: inherit; + text-decoration: none; + } + + a { color: #003d41; } + a:visited { color: #553d41; } + .footer a { color: #508d91; } + + /* bug index pages */ + + td.tab { + padding-right: 1em; + padding-left: 1em; + } + + td.sel.tab { + background-color: #afafaf; + border: 1px solid #afafaf; + font-weight:bold; + } + + td.nsel.tab { border: 0px; } + + table.bug_list { + background-color: #afafaf; + border: 2px solid #afafaf; + } + + .bug_list tr { width: auto; } + tr.wishlist { background-color: #B4FF9B; } + tr.minor { background-color: #FCFF98; } + tr.serious { background-color: #FFB648; } + tr.critical { background-color: #FF752A; } + tr.fatal { background-color: #FF3300; } + + /* bug detail pages */ + + td.bug_detail_label { text-align: right; } + td.bug_detail { } + td.bug_comment_label { text-align: right; vertical-align: top; } + td.bug_comment { } + + div.comment { + padding: 20px; + padding-top: 20px; + margin: auto; + margin-top: 0; + } + + div.root.comment { + padding: 0px; + /* padding-top: 0px; */ + padding-bottom: 20px; + } + """ + + self.index_file = """ + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> + <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <title>%(title)s</title> + <meta http-equiv="Content-Type" content="text/html; charset=%(charset)s" /> + <link rel="stylesheet" href="style.css" type="text/css" /> + </head> + <body> + + <div class="main"> + <h1>%(index_header)s</h1> + <p></p> + <table> + + <tr> + <td class="%(active_class)s"><a href="index.html">Active Bugs</a></td> + <td class="%(inactive_class)s"><a href="index_inactive.html">Inactive Bugs</a></td> + </tr> + + </table> + <table class="bug_list"> + <tbody> + + %(bug_entries)s + + </tbody> + </table> + </div> + + <div class="footer"> + <p>Generated by <a href="http://www.bugseverywhere.org/"> + BugsEverywhere</a> on %(generation_time)s</p> + <p> + <a href="http://validator.w3.org/check?uri=referer">Validate XHTML</a> | + <a href="http://jigsaw.w3.org/css-validator/check?uri=referer">Validate CSS</a> + </p> + </div> + + </body> + </html> + """ + + self.index_bug_entry =""" + <tr class="%(severity)s"> + <td><a href="bugs/%(dir)s/">%(shortname)s</a></td> + <td><a href="bugs/%(dir)s/">%(status)s</a></td> + <td><a href="bugs/%(dir)s/">%(severity)s</a></td> + <td><a href="bugs/%(dir)s/">%(summary)s</a></td> + <td><a href="bugs/%(dir)s/">%(time_string)s</a></td> + </tr> + """ + + self.bug_file = """ + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> + <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <title>%(title)s</title> + <meta http-equiv="Content-Type" content="text/html; charset=%(charset)s" /> + <link rel="stylesheet" href="../../style.css" type="text/css" /> + </head> + <body> + + <div class="main"> + <h1>BugsEverywhere Bug List</h1> + <h5><a href="%(up_link)s">Back to Index</a></h5> + <h2>Bug: %(shortname)s</h2> + <table> + <tbody> + + <tr><td class="bug_detail_label">ID :</td> + <td class="bug_detail">%(uuid)s</td></tr> + <tr><td class="bug_detail_label">Short name :</td> + <td class="bug_detail">%(shortname)s</td></tr> + <tr><td class="bug_detail_label">Status :</td> + <td class="bug_detail">%(status)s</td></tr> + <tr><td class="bug_detail_label">Severity :</td> + <td class="bug_detail">%(severity)s</td></tr> + <tr><td class="bug_detail_label">Assigned :</td> + <td class="bug_detail">%(assigned)s</td></tr> + <tr><td class="bug_detail_label">Reporter :</td> + <td class="bug_detail">%(reporter)s</td></tr> + <tr><td class="bug_detail_label">Creator :</td> + <td class="bug_detail">%(creator)s</td></tr> + <tr><td class="bug_detail_label">Created :</td> + <td class="bug_detail">%(time_string)s</td></tr> + <tr><td class="bug_detail_label">Summary :</td> + <td class="bug_detail">%(summary)s</td></tr> + </tbody> + </table> + + <hr/> + + %(comment_entries)s + + </div> + <h5><a href="%(up_link)s">Back to Index</a></h5> + + <div class="footer"> + <p>Generated by <a href="http://www.bugseverywhere.org/"> + BugsEverywhere</a> on %(generation_time)s</p> + <p> + <a href="http://validator.w3.org/check?uri=referer">Validate XHTML</a> | + <a href="http://jigsaw.w3.org/css-validator/check?uri=referer">Validate CSS</a> + </p> + </div> + + </body> + </html> + """ + + self.bug_comment_entry =""" + <table> + <tr> + <td class="bug_comment_label">Comment:</td> + <td class="bug_comment"> + --------- Comment ---------<br/> + ID: %(uuid)s<br/> + Short name: %(shortname)s<br/> + From: %(author)s<br/> + Date: %(date)s<br/> + <br/> + %(body)s + </td> + </tr> + </table> + """ + + # strip leading whitespace + for attr in ['css_file', 'index_file', 'index_bug_entry', 'bug_file', + 'bug_comment_entry']: + value = getattr(self, attr) + value = value.replace('\n'+' '*12, '\n') + setattr(self, attr, value.strip()+'\n') diff --git a/libbe/command/import_xml.py b/libbe/command/import_xml.py new file mode 100644 index 0000000..a890669 --- /dev/null +++ b/libbe/command/import_xml.py @@ -0,0 +1,541 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import copy +import os +import sys +try: # import core module, Python >= 2.5 + from xml.etree import ElementTree +except ImportError: # look for non-core module + from elementtree import ElementTree + +import libbe +import libbe.bug +import libbe.command +import libbe.command.util +import libbe.comment +import libbe.util.encoding +import libbe.util.utility + +if libbe.TESTING == True: + import doctest + import StringIO + import unittest + + import libbe.bugdir + +class Import_XML (libbe.command.Command): + """Import comments and bugs from XML + + >>> import time + >>> import StringIO + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_storage(bd.storage) + >>> cmd = Import_XML(ui=ui) + + >>> ui.io.set_stdin('<be-xml><comment><uuid>c</uuid><body>This is a comment about a</body></comment></be-xml>') + >>> ret = ui.run(cmd, {'comment-root':'/a'}, ['-']) + >>> bd.flush_reload() + >>> bug = bd.bug_from_uuid('a') + >>> bug.load_comments(load_full=False) + >>> comment = bug.comment_root[0] + >>> print comment.body + This is a comment about a + <BLANKLINE> + >>> comment.time <= int(time.time()) + True + >>> comment.in_reply_to is None + True + >>> ui.cleanup() + >>> bd.cleanup() + """ + name = 'import-xml' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='ignore-missing-references', short_name='i', + help="If any comment's <in-reply-to> refers to a non-existent comment, ignore it (instead of raising an exception)."), + libbe.command.Option(name='add-only', short_name='a', + help='If any bug or comment listed in the XML file already exists in the bug repository, do not alter the repository version.'), + libbe.command.Option(name='comment-root', short_name='c', + help='Supply a bug or comment ID as the root of any <comment> elements that are direct children of the <be-xml> element. If any such <comment> elements exist, you are required to set this option.', + arg=libbe.command.Argument( + name='comment-root', metavar='ID', + completion_callback=libbe.command.util.complete_bug_comment_id)), + ]) + self.args.extend([ + libbe.command.Argument( + name='xml-file', metavar='XML-FILE'), + ]) + + def _run(self, **params): + bugdir = self._get_bugdir() + writeable = bugdir.storage.writeable + bugdir.storage.writeable = False + if params['comment-root'] != None: + croot_bug,croot_comment = \ + libbe.command.util.bug_comment_from_user_id( + bugdir, params['comment-root']) + croot_bug.load_comments(load_full=True) + if croot_comment.uuid == libbe.comment.INVALID_UUID: + croot_comment = croot_bug.comment_root + else: + croot_comment = croot_bug.comment_from_uuid(croot_comment.uuid) + new_croot_bug = libbe.bug.Bug(bugdir=bugdir, uuid=croot_bug.uuid) + new_croot_bug.explicit_attrs = [] + new_croot_bug.comment_root = copy.deepcopy(croot_bug.comment_root) + if croot_comment.uuid == libbe.comment.INVALID_UUID: + new_croot_comment = new_croot_bug.comment_root + else: + new_croot_comment = \ + new_croot_bug.comment_from_uuid(croot_comment.uuid) + for new in new_croot_bug.comments(): + new.explicit_attrs = [] + else: + croot_bug,croot_comment = (None, None) + + if params['xml-file'] == '-': + xml = self.stdin.read().encode(self.stdin.encoding) + else: + self._check_restricted_access(bugdir.storage, params['xml-file']) + xml = libbe.util.encoding.get_file_contents( + params['xml-file']) + + # parse the xml + root_bugs = [] + root_comments = [] + version = {} + be_xml = ElementTree.XML(xml) + if be_xml.tag != 'be-xml': + raise libbe.util.utility.InvalidXML( + 'import-xml', be_xml, 'root element must be <be-xml>') + for child in be_xml.getchildren(): + if child.tag == 'bug': + new = libbe.bug.Bug(bugdir=bugdir) + new.from_xml(child) + root_bugs.append(new) + elif child.tag == 'comment': + new = libbe.comment.Comment(croot_bug) + new.from_xml(child) + root_comments.append(new) + elif child.tag == 'version': + for gchild in child.getchildren(): + if child.tag in ['tag', 'nick', 'revision', 'revision-id']: + text = xml.sax.saxutils.unescape(child.text) + text = text.decode('unicode_escape').strip() + version[child.tag] = text + else: + print >> sys.stderr, 'ignoring unknown tag %s in %s' \ + % (gchild.tag, child.tag) + else: + print >> sys.stderr, 'ignoring unknown tag %s in %s' \ + % (child.tag, comment_list.tag) + + # merge the new root_comments + if params['add-only'] == True: + accept_changes = False + accept_extra_strings = False + else: + accept_changes = True + accept_extra_strings = True + accept_comments = True + if len(root_comments) > 0: + if croot_bug == None: + raise libbe.command.UserError( + '--comment-root option is required for your root comments:\n%s' + % '\n\n'.join([c.string() for c in root_comments])) + try: + # link new comments + new_croot_bug.add_comments(root_comments, + default_parent=new_croot_comment, + ignore_missing_references= \ + params['ignore-missing-references']) + except libbe.comment.MissingReference, e: + raise libbe.command.UserError(e) + croot_bug.merge(new_croot_bug, accept_changes=accept_changes, + accept_extra_strings=accept_extra_strings, + accept_comments=accept_comments) + + # merge the new croot_bugs + merged_bugs = [] + old_bugs = [] + for new in root_bugs: + try: + old = bugdir.bug_from_uuid(new.alt_id) + except KeyError: + old = None + if old == None: + bd.append(new) + else: + old.load_comments(load_full=True) + old.merge(new, accept_changes=accept_changes, + accept_extra_strings=accept_extra_strings, + accept_comments=accept_comments) + merged_bugs.append(new) + old_bugs.append(old) + + # protect against programmer error causing data loss: + if croot_bug != None: + comms = [] + for c in croot_comment.traverse(): + comms.append(c.uuid) + if c.alt_id != None: + comms.append(c.alt_id) + if croot_comment.uuid == libbe.comment.INVALID_UUID: + root_text = croot_bug.id.user() + else: + root_text = croot_comment.id.user() + for new in root_comments: + assert new.uuid in comms or new.alt_id in comms, \ + "comment %s (alt: %s) wasn't added to %s" \ + % (new.uuid, new.alt_id, root_text) + for new in root_bugs: + if not new in merged_bugs: + assert bugdir.has_bug(new.uuid), \ + "bug %s wasn't added" % (new.uuid) + + # save new information + bugdir.storage.writeable = writeable + if croot_bug != None: + croot_bug.save() + for new in root_bugs: + if not new in merged_bugs: + new.save() + for old in old_bugs: + old.save() + + def _long_help(self): + return """ +Import comments and bugs from XMLFILE. If XMLFILE is '-', the file is +read from stdin. + +This command provides a fallback mechanism for passing bugs between +repositories, in case the repositories VCSs are incompatible. If the +VCSs are compatible, it's better to use their builtin merge/push/pull +to share this information, as that will preserve a more detailed +history. + +The XML file should be formatted similarly to + <be-xml> + <version> + <tag>1.0.0</tag> + <branch-nick>be</branch-nick> + <revno>446</revno> + <revision-id>a@b.com-20091119214553-iqyw2cpqluww3zna</revision-id> + <version> + <bug> + ... + <comment>...</comment> + <comment>...</comment> + </bug> + <bug>...</bug> + <comment>...</comment> + <comment>...</comment> + </be-xml> +where the ellipses mark output commpatible with Bug.xml() and +Comment.xml(). Take a look at the output of `be show --xml` for some +explicit examples. Unrecognized tags are ignored. Missing tags are +left at the default value. The version tag is not required, but is +strongly recommended. + +The bug and comment UUIDs are always auto-generated, so if you set a +<uuid> field, but no <alt-id> field, your <uuid> will be used as the +comment's <alt-id>. An exception is raised if <alt-id> conflicts with +an existing comment. Bugs do not have a permantent alt-id, so they +the <uuid>s you specify are not saved. The <uuid>s _are_ used to +match agains prexisting bug and comment uuids, and comment alt-ids, +and fields explicitly given in the XML file will replace old versions +unless the --add-only flag. + +*.extra_strings recieves special treatment, and if --add-only is not +set, the resulting list concatenates both source lists and removes +repeats. + +Here's an example of import activity: + Repository + bug (uuid=B, creator=John, status=open) + estr (don't forget your towel) + estr (helps with space travel) + com (uuid=C1, author=Jane, body=Hello) + com (uuid=C2, author=Jess, body=World) + XML + bug (uuid=B, status=fixed) + estr (don't forget your towel) + estr (watch out for flying dolphins) + com (uuid=C1, body=So long) + com (uuid=C3, author=Jed, body=And thanks) + Result + bug (uuid=B, creator=John, status=fixed) + estr (don't forget your towel) + estr (helps with space travel) + estr (watch out for flying dolphins) + com (uuid=C1, author=Jane, body=So long) + com (uuid=C2, author=Jess, body=World) + com (uuid=C4, alt-id=C3, author=Jed, body=And thanks) + Result, with --add-only + bug (uuid=B, creator=John, status=open) + estr (don't forget your towel) + estr (helps with space travel) + com (uuid=C1, author=Jane, body=Hello) + com (uuid=C2, author=Jess, body=World) + com (uuid=C4, alt-id=C3, author=Jed, body=And thanks) + +Examples: + +Import comments (e.g. emails from an mbox) and append to bug XYZ + $ be-mbox-to-xml mail.mbox | be import-xml --c XYZ - +Or you can append those emails underneath the prexisting comment XYZ-3 + $ be-mbox-to-xml mail.mbox | be import-xml --c XYZ-3 - + +User creates a new bug + user$ be new "The demuxulizer is broken" + Created bug with ID 48f + user$ be comment 48f + <Describe bug> + ... +User exports bug as xml and emails it to the developers + user$ be show --xml 48f > 48f.xml + user$ cat 48f.xml | mail -s "Demuxulizer bug xml" devs@b.com +or equivalently (with a slightly fancier be-handle-mail compatible +email): + user$ be email-bugs 48f +Devs recieve email, and save it's contents as demux-bug.xml + dev$ cat demux-bug.xml | be import-xml - +""" + + +Import_xml = Import_XML # alias for libbe.command.base.get_command_class() + +if libbe.TESTING == True: + class LonghelpTestCase (unittest.TestCase): + """ + Test import scenarios given in longhelp. + """ + def setUp(self): + self.bugdir = libbe.bugdir.SimpleBugDir(memory=False) + io = libbe.command.StringInputOutput() + self.ui = libbe.command.UserInterface(io=io) + self.ui.storage_callbacks.set_storage(self.bugdir.storage) + self.cmd = Import_XML(ui=self.ui) + self.cmd._storage = self.bugdir.storage + self.cmd._setup_io = lambda i_enc,o_enc : None + bugA = self.bugdir.bug_from_uuid('a') + self.bugdir.remove_bug(bugA) + self.bugdir.storage.writeable = False + bugB = self.bugdir.bug_from_uuid('b') + bugB.creator = 'John' + bugB.status = 'open' + bugB.extra_strings += ["don't forget your towel"] + bugB.extra_strings += ['helps with space travel'] + comm1 = bugB.comment_root.new_reply(body='Hello\n') + comm1.uuid = 'c1' + comm1.author = 'Jane' + comm2 = bugB.comment_root.new_reply(body='World\n') + comm2.uuid = 'c2' + comm2.author = 'Jess' + self.bugdir.storage.writeable = True + bugB.save() + self.xml = """ + <be-xml> + <bug> + <uuid>b</uuid> + <status>fixed</status> + <summary>a test bug</summary> + <extra-string>don't forget your towel</extra-string> + <extra-string>watch out for flying dolphins</extra-string> + <comment> + <uuid>c1</uuid> + <body>So long</body> + </comment> + <comment> + <uuid>c3</uuid> + <author>Jed</author> + <body>And thanks</body> + </comment> + </bug> + </be-xml> + """ + self.root_comment_xml = """ + <be-xml> + <comment> + <uuid>c1</uuid> + <body>So long</body> + </comment> + <comment> + <uuid>c3</uuid> + <author>Jed</author> + <body>And thanks</body> + </comment> + </be-xml> + """ + def tearDown(self): + self.bugdir.cleanup() + self.ui.cleanup() + def _execute(self, xml, params={}, args=[]): + self.ui.io.set_stdin(xml) + self.ui.run(self.cmd, params, args) + self.bugdir.flush_reload() + def testCleanBugdir(self): + uuids = list(self.bugdir.uuids()) + self.failUnless(uuids == ['b'], uuids) + def testNotAddOnly(self): + bugB = self.bugdir.bug_from_uuid('b') + self._execute(self.xml, {}, ['-']) + uuids = list(self.bugdir.uuids()) + self.failUnless(uuids == ['b'], uuids) + bugB = self.bugdir.bug_from_uuid('b') + self.failUnless(bugB.uuid == 'b', bugB.uuid) + self.failUnless(bugB.creator == 'John', bugB.creator) + self.failUnless(bugB.status == 'fixed', bugB.status) + self.failUnless(bugB.summary == 'a test bug', bugB.summary) + estrs = ["don't forget your towel", + 'helps with space travel', + 'watch out for flying dolphins'] + self.failUnless(bugB.extra_strings == estrs, bugB.extra_strings) + comments = list(bugB.comments()) + self.failUnless(len(comments) == 3, + ['%s (%s, %s)' % (c.uuid, c.alt_id, c.body) + for c in comments]) + c1 = bugB.comment_from_uuid('c1') + comments.remove(c1) + self.failUnless(c1.uuid == 'c1', c1.uuid) + self.failUnless(c1.alt_id == None, c1.alt_id) + self.failUnless(c1.author == 'Jane', c1.author) + self.failUnless(c1.body == 'So long\n', c1.body) + c2 = bugB.comment_from_uuid('c2') + comments.remove(c2) + self.failUnless(c2.uuid == 'c2', c2.uuid) + self.failUnless(c2.alt_id == None, c2.alt_id) + self.failUnless(c2.author == 'Jess', c2.author) + self.failUnless(c2.body == 'World\n', c2.body) + c4 = comments[0] + self.failUnless(len(c4.uuid) == 36, c4.uuid) + self.failUnless(c4.alt_id == 'c3', c4.alt_id) + self.failUnless(c4.author == 'Jed', c4.author) + self.failUnless(c4.body == 'And thanks\n', c4.body) + def testAddOnly(self): + bugB = self.bugdir.bug_from_uuid('b') + initial_bugB_summary = bugB.summary + self._execute(self.xml, {'add-only':True}, ['-']) + uuids = list(self.bugdir.uuids()) + self.failUnless(uuids == ['b'], uuids) + bugB = self.bugdir.bug_from_uuid('b') + self.failUnless(bugB.uuid == 'b', bugB.uuid) + self.failUnless(bugB.creator == 'John', bugB.creator) + self.failUnless(bugB.status == 'open', bugB.status) + self.failUnless(bugB.summary == initial_bugB_summary, bugB.summary) + estrs = ["don't forget your towel", + 'helps with space travel'] + self.failUnless(bugB.extra_strings == estrs, bugB.extra_strings) + comments = list(bugB.comments()) + self.failUnless(len(comments) == 3, + ['%s (%s)' % (c.uuid, c.alt_id) for c in comments]) + c1 = bugB.comment_from_uuid('c1') + comments.remove(c1) + self.failUnless(c1.uuid == 'c1', c1.uuid) + self.failUnless(c1.alt_id == None, c1.alt_id) + self.failUnless(c1.author == 'Jane', c1.author) + self.failUnless(c1.body == 'Hello\n', c1.body) + c2 = bugB.comment_from_uuid('c2') + comments.remove(c2) + self.failUnless(c2.uuid == 'c2', c2.uuid) + self.failUnless(c2.alt_id == None, c2.alt_id) + self.failUnless(c2.author == 'Jess', c2.author) + self.failUnless(c2.body == 'World\n', c2.body) + c4 = comments[0] + self.failUnless(len(c4.uuid) == 36, c4.uuid) + self.failUnless(c4.alt_id == 'c3', c4.alt_id) + self.failUnless(c4.author == 'Jed', c4.author) + self.failUnless(c4.body == 'And thanks\n', c4.body) + def testRootCommentsNotAddOnly(self): + bugB = self.bugdir.bug_from_uuid('b') + initial_bugB_summary = bugB.summary + self._execute(self.root_comment_xml, {'comment-root':'/b'}, ['-']) + uuids = list(self.bugdir.uuids()) + uuids = list(self.bugdir.uuids()) + self.failUnless(uuids == ['b'], uuids) + bugB = self.bugdir.bug_from_uuid('b') + self.failUnless(bugB.uuid == 'b', bugB.uuid) + self.failUnless(bugB.creator == 'John', bugB.creator) + self.failUnless(bugB.status == 'open', bugB.status) + self.failUnless(bugB.summary == initial_bugB_summary, bugB.summary) + estrs = ["don't forget your towel", + 'helps with space travel'] + self.failUnless(bugB.extra_strings == estrs, bugB.extra_strings) + comments = list(bugB.comments()) + self.failUnless(len(comments) == 3, + ['%s (%s, %s)' % (c.uuid, c.alt_id, c.body) + for c in comments]) + c1 = bugB.comment_from_uuid('c1') + comments.remove(c1) + self.failUnless(c1.uuid == 'c1', c1.uuid) + self.failUnless(c1.alt_id == None, c1.alt_id) + self.failUnless(c1.author == 'Jane', c1.author) + self.failUnless(c1.body == 'So long\n', c1.body) + c2 = bugB.comment_from_uuid('c2') + comments.remove(c2) + self.failUnless(c2.uuid == 'c2', c2.uuid) + self.failUnless(c2.alt_id == None, c2.alt_id) + self.failUnless(c2.author == 'Jess', c2.author) + self.failUnless(c2.body == 'World\n', c2.body) + c4 = comments[0] + self.failUnless(len(c4.uuid) == 36, c4.uuid) + self.failUnless(c4.alt_id == 'c3', c4.alt_id) + self.failUnless(c4.author == 'Jed', c4.author) + self.failUnless(c4.body == 'And thanks\n', c4.body) + def testRootCommentsAddOnly(self): + bugB = self.bugdir.bug_from_uuid('b') + initial_bugB_summary = bugB.summary + self._execute(self.root_comment_xml, + {'comment-root':'/b', 'add-only':True}, ['-']) + uuids = list(self.bugdir.uuids()) + self.failUnless(uuids == ['b'], uuids) + bugB = self.bugdir.bug_from_uuid('b') + self.failUnless(bugB.uuid == 'b', bugB.uuid) + self.failUnless(bugB.creator == 'John', bugB.creator) + self.failUnless(bugB.status == 'open', bugB.status) + self.failUnless(bugB.summary == initial_bugB_summary, bugB.summary) + estrs = ["don't forget your towel", + 'helps with space travel'] + self.failUnless(bugB.extra_strings == estrs, bugB.extra_strings) + comments = list(bugB.comments()) + self.failUnless(len(comments) == 3, + ['%s (%s)' % (c.uuid, c.alt_id) for c in comments]) + c1 = bugB.comment_from_uuid('c1') + comments.remove(c1) + self.failUnless(c1.uuid == 'c1', c1.uuid) + self.failUnless(c1.alt_id == None, c1.alt_id) + self.failUnless(c1.author == 'Jane', c1.author) + self.failUnless(c1.body == 'Hello\n', c1.body) + c2 = bugB.comment_from_uuid('c2') + comments.remove(c2) + self.failUnless(c2.uuid == 'c2', c2.uuid) + self.failUnless(c2.alt_id == None, c2.alt_id) + self.failUnless(c2.author == 'Jess', c2.author) + self.failUnless(c2.body == 'World\n', c2.body) + c4 = comments[0] + self.failUnless(len(c4.uuid) == 36, c4.uuid) + self.failUnless(c4.alt_id == 'c3', c4.alt_id) + self.failUnless(c4.author == 'Jed', c4.author) + self.failUnless(c4.body == 'And thanks\n', c4.body) + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/command/init.py b/libbe/command/init.py new file mode 100644 index 0000000..7b83645 --- /dev/null +++ b/libbe/command/init.py @@ -0,0 +1,132 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os.path + +import libbe +import libbe.bugdir +import libbe.command +import libbe.storage + +class Init (libbe.command.Command): + """Create an on-disk bug repository + + >>> import os, sys + >>> import libbe.storage.vcs + >>> import libbe.storage.vcs.base + >>> import libbe.util.utility + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> cmd = Init() + + >>> dir = libbe.util.utility.Dir() + >>> vcs = libbe.storage.vcs.vcs_by_name('None') + >>> vcs.repo = dir.path + >>> try: + ... vcs.connect() + ... except libbe.storage.ConnectionError: + ... 'got error' + 'got error' + >>> ui.storage_callbacks.set_unconnected_storage(vcs) + >>> ui.run(cmd) + No revision control detected. + BE repository initialized. + >>> bd = libbe.bugdir.BugDir(vcs) + >>> vcs.disconnect() + >>> vcs.connect() + >>> bugdir = libbe.bugdir.BugDir(vcs, from_storage=True) + >>> vcs.disconnect() + >>> vcs.destroy() + >>> dir.cleanup() + + >>> dir = libbe.util.utility.Dir() + >>> vcs = libbe.storage.vcs.installed_vcs() + >>> vcs.repo = dir.path + >>> vcs._vcs_init(vcs.repo) + >>> ui.storage_callbacks.set_unconnected_storage(vcs) + >>> if vcs.name in libbe.storage.vcs.base.VCS_ORDER: + ... ui.run(cmd) # doctest: +ELLIPSIS + ... else: + ... vcs.init() + ... vcs.connect() + ... print 'Using ... for revision control.\\nDirectory initialized.' + Using ... for revision control. + BE repository initialized. + >>> vcs.disconnect() + >>> vcs.connect() + >>> bugdir = libbe.bugdir.BugDir(vcs, from_storage=True) + >>> vcs.disconnect() + >>> vcs.destroy() + >>> dir.cleanup() + """ + name = 'init' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + + def _run(self, **params): + storage = self._get_unconnected_storage() + if not os.path.isdir(storage.repo): + raise libbe.command.UserError( + 'No such directory: %s' % storage.repo) + try: + storage.connect() + raise libbe.command.UserError( + 'Directory already initialized: %s' % storage.repo) + except libbe.storage.ConnectionError: + pass + storage.init() + storage.connect() + self.ui.storage_callbacks.set_storage(storage) + bd = libbe.bugdir.BugDir(storage, from_storage=False) + self.ui.storage_callbacks.set_bugdir(bd) + if bd.storage.name is not 'None': + print >> self.stdout, \ + 'Using %s for revision control.' % storage.name + else: + print >> self.stdout, 'No revision control detected.' + print >> self.stdout, 'BE repository initialized.' + + def _long_help(self): + return """ +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 vcs_name" to change the vcs being used. + +The directory defaults to your current working directory, but you can +change that by passing the --repo option to be + $ be --repo path/to/new/bug/root init + +When initialized in a version-controlled directory, BE sinks to the +version-control root. In that case, the BE repository will be created +under that directory, rather than the current directory or the one +passed in --repo. Consider the following tree, versioned in Git. + ~ + `--projectX + |-- .git + `-- src +Calling + ~$ be --repo ./projectX/src init +will create the BE repository rooted in projectX: + ~ + `--projectX + |-- .be + |-- .git + `-- src +""" diff --git a/libbe/command/list.py b/libbe/command/list.py new file mode 100644 index 0000000..3803257 --- /dev/null +++ b/libbe/command/list.py @@ -0,0 +1,279 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os +import re + +import libbe +import libbe.bug +import libbe.command +import libbe.command.depend +import libbe.command.target +import libbe.command.util + +# get a list of * for cmp_*() comparing two bugs. +AVAILABLE_CMPS = [fn[4:] for fn in dir(libbe.bug) if fn[:4] == 'cmp_'] +AVAILABLE_CMPS.remove('attr') # a cmp_* template. + +class Filter (object): + def __init__(self, status='all', severity='all', assigned='all', + target='all', extra_strings_regexps=[]): + self.status = status + self.severity = severity + self.assigned = assigned + self.target = target + self.extra_strings_regexps = extra_strings_regexps + + def __call__(self, bugdir, bug): + if self.status != 'all' and not bug.status in self.status: + return False + if self.severity != 'all' and not bug.severity in self.severity: + return False + if self.assigned != 'all' and not bug.assigned in self.assigned: + return False + if self.target == 'all': + pass + else: + target_bug = libbe.command.target.bug_target(bugdir, bug) + if self.target in ['none', None]: + if target_bug.summary != None: + return False + else: + if target_bug.summary != self.target: + return False + if len(bug.extra_strings) == 0: + if len(self.extra_strings_regexps) > 0: + return False + else: + for string in bug.extra_strings: + for regexp in self.extra_strings_regexps: + if not regexp.match(string): + return False + return True + +class List (libbe.command.Command): + """List bugs + + >>> import sys + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_storage(bd.storage) + >>> cmd = List(ui=ui) + + >>> ret = ui.run(cmd) + abc/a:om: Bug A + >>> ret = ui.run(cmd, {'status':'closed'}) + abc/b:cm: Bug B + >>> bd.storage.writeable + True + >>> ui.cleanup() + >>> bd.cleanup() + """ + + name = 'list' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='status', + help='Only show bugs matching the STATUS specifier', + arg=libbe.command.Argument( + name='status', metavar='STATUS', default='active', + completion_callback=libbe.command.util.complete_status)), + libbe.command.Option(name='severity', + help='Only show bugs matching the SEVERITY specifier', + arg=libbe.command.Argument( + name='severity', metavar='SEVERITY', default='all', + completion_callback=libbe.command.util.complete_severity)), + libbe.command.Option(name='important', + help='List bugs with >= "serious" severity'), + libbe.command.Option(name='assigned', short_name='a', + help='Only show bugs matching ASSIGNED', + arg=libbe.command.Argument( + name='assigned', metavar='ASSIGNED', default=None, + completion_callback=libbe.command.util.complete_assigned)), + libbe.command.Option(name='mine', short_name='m', + help='List bugs assigned to you'), + libbe.command.Option(name='extra-strings', short_name='e', + help='Only show bugs matching STRINGS, e.g. --extra-strings' + ' TAG:working,TAG:xml', + arg=libbe.command.Argument( + name='extra-strings', metavar='STRINGS', default=None, + completion_callback=libbe.command.util.complete_extra_strings)), + libbe.command.Option(name='sort', short_name='S', + help='Adjust bug-sort criteria with comma-separated list ' + 'SORT. e.g. "--sort creator,time". ' + 'Available criteria: %s' % ','.join(AVAILABLE_CMPS), + arg=libbe.command.Argument( + name='sort', metavar='SORT', default=None, + completion_callback=libbe.command.util.Completer(AVAILABLE_CMPS))), + libbe.command.Option(name='ids', short_name='i', + help='Only print the bug IDS'), + libbe.command.Option(name='xml', short_name='x', + help='Dump output in XML format'), + ]) +# parser.add_option("-S", "--sort", metavar="SORT-BY", dest="sort_by", +# help="Adjust bug-sort criteria with comma-separated list SORT-BY. e.g. \"--sort creator,time\". Available criteria: %s" % ','.join(AVAILABLE_CMPS), default=None) +# # boolean options. All but ids and xml are special cases of long forms +# ("w", "wishlist", "List bugs with 'wishlist' severity"), +# ("A", "active", "List all active bugs"), +# ("U", "unconfirmed", "List unconfirmed bugs"), +# ("o", "open", "List open bugs"), +# ("T", "test", "List bugs in testing"), +# for s in bools: +# attr = s[1].replace('-','_') +# short = "-%c" % s[0] +# long = "--%s" % s[1] +# help = s[2] +# parser.add_option(short, long, action="store_true", +# dest=attr, help=help, default=False) +# return parser +# +# ]) + + def _run(self, **params): + bugdir = self._get_bugdir() + writeable = bugdir.storage.writeable + bugdir.storage.writeable = False + cmp_list, status, severity, assigned, extra_strings_regexps = \ + self._parse_params(bugdir, params) + filter = Filter(status, severity, assigned, + extra_strings_regexps=extra_strings_regexps) + bugs = [bugdir.bug_from_uuid(uuid) for uuid in bugdir.uuids()] + bugs = [b for b in bugs if filter(bugdir, b) == True] + self.result = bugs + if len(bugs) == 0 and params['xml'] == False: + print >> self.stdout, 'No matching bugs found' + + # sort bugs + bugs = self._sort_bugs(bugs, cmp_list) + + # print list of bugs + if params['ids'] == True: + for bug in bugs: + print >> self.stdout, bug.id.user() + else: + self._list_bugs(bugs, xml=params['xml']) + bugdir.storage.writeable = writeable + return 0 + + def _parse_params(self, bugdir, params): + cmp_list = [] + if params['sort'] != None: + for cmp in params['sort'].sort_by.split(','): + if cmp not in AVAILABLE_CMPS: + raise libbe.command.UserError( + 'Invalid sort on "%s".\nValid sorts:\n %s' + % (cmp, '\n '.join(AVAILABLE_CMPS))) + cmp_list.append(eval('libbe.bug.cmp_%s' % cmp)) + # select status + if params['status'] == 'all': + status = libbe.bug.status_values + elif params['status'] == 'active': + status = list(libbe.bug.active_status_values) + elif params['status'] == 'inactive': + status = list(libbe.bug.inactive_status_values) + else: + status = libbe.command.util.select_values( + params['status'], libbe.bug.status_values) + # select severity + if params['severity'] == 'all': + severity = libbe.bug.severity_values + elif params['important'] == True: + serious = libbe.bug.severity_values.index('serious') + severity.append(list(libbe.bug.severity_values[serious:])) + else: + severity = libbe.command.util.select_values( + params['severity'], libbe.bug.severity_values) + # select assigned + if params['assigned'] == None: + if params['mine'] == True: + assigned = [self._get_user_id()] + else: + assigned = 'all' + else: + assigned = libbe.command.util.select_values( + params['assigned'], libbe.command.util.assignees(bugdir)) + for i in range(len(assigned)): + if assigned[i] == '-': + assigned[i] = params['user-id'] + if params['extra-strings'] == None: + extra_strings_regexps = [] + else: + extra_strings_regexps = [re.compile(x) + for x in params['extra-strings'].split(',')] + return (cmp_list, status, severity, assigned, extra_strings_regexps) + + def _sort_bugs(self, bugs, cmp_list=[]): + cmp_list.extend(libbe.bug.DEFAULT_CMP_FULL_CMP_LIST) + cmp_fn = libbe.bug.BugCompoundComparator(cmp_list=cmp_list) + bugs.sort(cmp_fn) + return bugs + + def _list_bugs(self, bugs, xml=False): + if xml == True: + print >> self.stdout, \ + '<?xml version="1.0" encoding="%s" ?>' % self.stdout.encoding + print >> self.stdout, '<be-xml>' + if len(bugs) > 0: + for bug in bugs: + if xml == True: + print >> self.stdout, bug.xml(show_comments=True) + else: + print >> self.stdout, bug.string(shortlist=True) + if xml == True: + print >> self.stdout, '</be-xml>' + + def _long_help(self): + return """ +This command lists bugs. Normally it prints a short string like + bea/576:om: Allow attachments +Where + bea/576 the bug id + o the bug status is 'open' (first letter) + m the bug severity is 'minor' (first letter) + Allo... the bug summary string + +You can optionally (-u) print only the bug ids. + +There are several criteria that you can filter by: + * status + * severity + * assigned (who the bug is assigned to) +Allowed values for each criterion may be given in a comma seperated +list. The special string "all" may be used with any of these options +to match all values of the criterion. As with the --status and +--severity options for `be depend`, starting the list with a minus +sign makes your selections a blacklist instead of the default +whitelist. + +status + %s +severity + %s +assigned + free form, with the string '-' being a shortcut for yourself. + +In addition, there are some shortcut options that set boolean flags. +The boolean options are ignored if the matching string option is used. +""" % (','.join(libbe.bug.status_values), + ','.join(libbe.bug.severity_values)) diff --git a/libbe/command/merge.py b/libbe/command/merge.py new file mode 100644 index 0000000..2dff59c --- /dev/null +++ b/libbe/command/merge.py @@ -0,0 +1,189 @@ +# Copyright (C) 2008-2010 Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import copy +import os + +import libbe +import libbe.command +import libbe.command.util + + +class Merge (libbe.command.Command): + """Merge duplicate bugs + + >>> import sys + >>> import libbe.bugdir + >>> import libbe.comment + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_bugdir(bd) + >>> cmd = Merge(ui=ui) + + >>> a = bd.bug_from_uuid('a') + >>> a.comment_root.time = 0 + >>> dummy = a.new_comment('Testing') + >>> dummy.time = 1 + >>> dummy = dummy.new_reply('Testing...') + >>> dummy.time = 2 + >>> b = bd.bug_from_uuid('b') + >>> b.status = 'open' + >>> b.comment_root.time = 0 + >>> dummy = b.new_comment('1 2') + >>> dummy.time = 1 + >>> dummy = dummy.new_reply('1 2 3 4') + >>> dummy.time = 2 + + >>> ret = ui.run(cmd, args=['/a', '/b']) + Merged bugs #abc/a# and #abc/b# + >>> bd.flush_reload() + >>> a = bd.bug_from_uuid('a') + >>> a.load_comments() + >>> a_comments = sorted([c for c in a.comments()], + ... cmp=libbe.comment.cmp_time) + >>> mergeA = a_comments[0] + >>> mergeA.time = 3 + >>> print a.string(show_comments=True) # doctest: +ELLIPSIS + ID : a + Short name : abc/a + Severity : minor + Status : open + Assigned : + Reporter : + Creator : John Doe <jdoe@example.com> + Created : ... + Bug A + --------- Comment --------- + Name: abc/a/... + From: ... + Date: ... + <BLANKLINE> + Testing + --------- Comment --------- + Name: abc/a/... + From: ... + Date: ... + <BLANKLINE> + Testing... + --------- Comment --------- + Name: abc/a/... + From: ... + Date: ... + <BLANKLINE> + Merged from bug #abc/b# + --------- Comment --------- + Name: abc/a/... + From: ... + Date: ... + <BLANKLINE> + 1 2 + --------- Comment --------- + Name: abc/a/... + From: ... + Date: ... + <BLANKLINE> + 1 2 3 4 + >>> b = bd.bug_from_uuid('b') + >>> b.load_comments() + >>> b_comments = sorted([c for c in b.comments()], + ... libbe.comment.cmp_time) + >>> mergeB = b_comments[0] + >>> mergeB.time = 3 + >>> print b.string(show_comments=True) # doctest: +ELLIPSIS + ID : b + Short name : abc/b + Severity : minor + Status : closed + Assigned : + Reporter : + Creator : Jane Doe <jdoe@example.com> + Created : ... + Bug B + --------- Comment --------- + Name: abc/b/... + From: ... + Date: ... + <BLANKLINE> + 1 2 + --------- Comment --------- + Name: abc/b/... + From: ... + Date: ... + <BLANKLINE> + 1 2 3 4 + --------- Comment --------- + Name: abc/b/... + From: ... + Date: ... + <BLANKLINE> + Merged into bug #abc/a# + >>> print b.status + closed + >>> ui.cleanup() + >>> bd.cleanup() + """ + name = 'merge' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.args.extend([ + libbe.command.Argument( + name='bug-id', metavar='BUG-ID', default=None, + completion_callback=libbe.command.util.complete_bug_id), + libbe.command.Argument( + name='bug-id-to-merge', metavar='BUG-ID', default=None, + completion_callback=libbe.command.util.complete_bug_id), + ]) + + def _run(self, **params): + bugdir = self._get_bugdir() + bugA,dummy_comment = \ + libbe.command.util.bug_comment_from_user_id( + bugdir, params['bug-id']) + bugA.load_comments() + bugB,dummy_comment = \ + libbe.command.util.bug_comment_from_user_id( + bugdir, params['bug-id-to-merge']) + bugB.load_comments() + mergeA = bugA.new_comment('Merged from bug #%s#' % bugB.id.long_user()) + newCommTree = copy.deepcopy(bugB.comment_root) + for comment in newCommTree.traverse(): # all descendant comments + comment.bug = bugA + # uuids must be unique in storage + if comment.alt_id == None: + comment.storage = None + comment.alt_id = comment.uuid + comment.storage = bugdir.storage + comment.uuid = libbe.util.id.uuid_gen() + comment.save() # force onto disk under bugA + + for comment in newCommTree: # just the child comments + mergeA.add_reply(comment, allow_time_inversion=True) + bugB.new_comment('Merged into bug #%s#' % bugA.id.long_user()) + bugB.status = 'closed' + print >> self.stdout, 'Merged bugs #%s# and #%s#' \ + % (bugA.id.user(), bugB.id.user()) + return 0 + + def _long_help(self): + return """ +The second bug (B) is merged into the first (A). This adds merge +comments to both bugs, closes B, and appends B's comment tree to A's +merge comment. +""" diff --git a/libbe/command/new.py b/libbe/command/new.py new file mode 100644 index 0000000..be18306 --- /dev/null +++ b/libbe/command/new.py @@ -0,0 +1,103 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import libbe +import libbe.command +import libbe.command.util + + +class New (libbe.command.Command): + """Create a new bug + + >>> import os + >>> import sys + >>> import time + >>> import libbe.bugdir + >>> import libbe.util.id + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_storage(bd.storage) + >>> cmd = New() + + >>> uuid_gen = libbe.util.id.uuid_gen + >>> libbe.util.id.uuid_gen = lambda: 'X' + >>> ui._user_id = u'Fran\\xe7ois' + >>> ret = ui.run(cmd, args=['this is a test',]) + Created bug with ID abc/X + >>> libbe.util.id.uuid_gen = uuid_gen + >>> bd.flush_reload() + >>> bug = bd.bug_from_uuid('X') + >>> print bug.summary + this is a test + >>> bug.creator + u'Fran\\xe7ois' + >>> bug.reporter + u'Fran\\xe7ois' + >>> bug.time <= int(time.time()) + True + >>> print bug.severity + minor + >>> print bug.status + open + >>> ui.cleanup() + >>> bd.cleanup() + """ + name = 'new' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='reporter', short_name='r', + help='The user who reported the bug', + arg=libbe.command.Argument( + name='reporter', metavar='NAME')), + libbe.command.Option(name='assigned', short_name='a', + help='The developer in charge of the bug', + arg=libbe.command.Argument( + name='assigned', metavar='NAME', + completion_callback=libbe.command.util.complete_assigned)), + ]) + self.args.extend([ + libbe.command.Argument(name='summary', metavar='SUMMARY') + ]) + + def _run(self, **params): + if params['summary'] == '-': # read summary from stdin + summary = self.stdin.readline() + else: + summary = params['summary'] + bugdir = self._get_bugdir() + bug = bugdir.new_bug(summary=summary.strip()) + bug.creator = self._get_user_id() + if params['reporter'] != None: + bug.reporter = params['reporter'] + else: + bug.reporter = bug.creator + if params['assigned'] != None: + bug.assigned = params['assigned'] + print >> self.stdout, 'Created bug with ID %s' % bug.id.user() + return 0 + + def _long_help(self): + return """ +Create a new bug, with a new ID. The summary specified on the +commandline is a string (only one line) that describes the bug briefly +or "-", in which case the string will be read from stdin. +""" diff --git a/libbe/command/open.py b/libbe/command/open.py new file mode 100644 index 0000000..0c6bf05 --- /dev/null +++ b/libbe/command/open.py @@ -0,0 +1,58 @@ +# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# Marien Zwart <marienz@gentoo.org> +# Thomas Gerigk <tgerigk@gmx.de> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Re-open a bug""" +from libbe import cmdutil, bugdir +__desc__ = __doc__ + +def execute(args, manipulate_encodings=True): + """ + >>> import os + >>> bd = bugdir.SimpleBugDir() + >>> os.chdir(bd.root) + >>> print bd.bug_from_shortname("b").status + closed + >>> execute(["b"], manipulate_encodings=False) + >>> bd._clear_bugs() + >>> print bd.bug_from_shortname("b").status + open + >>> bd.cleanup() + """ + parser = get_parser() + options, args = parser.parse_args(args) + cmdutil.default_complete(options, args, parser, + bugid_args={0: lambda bug : bug.active==False}) + if len(args) == 0: + raise cmdutil.UsageError, "Please specify a bug id." + if len(args) > 1: + raise cmdutil.UsageError, "Too many arguments." + bd = bugdir.BugDir(from_disk=True, + manipulate_encodings=manipulate_encodings) + bug = cmdutil.bug_from_shortname(bd, args[0]) + bug.status = "open" + +def get_parser(): + parser = cmdutil.CmdOptionParser("be open BUG-ID") + return parser + +longhelp=""" +Mark a bug as 'open'. +""" + +def help(): + return get_parser().help_str() + longhelp diff --git a/libbe/command/remove.py b/libbe/command/remove.py new file mode 100644 index 0000000..8d8e641 --- /dev/null +++ b/libbe/command/remove.py @@ -0,0 +1,79 @@ +# Copyright (C) 2008-2010 Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import libbe +import libbe.command +import libbe.command.util + + +class Remove (libbe.command.Command): + """Remove (delete) a bug and its comments + + >>> import sys + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_storage(bd.storage) + >>> cmd = Remove(ui=ui) + + >>> print bd.bug_from_uuid('b').status + closed + >>> ret = ui.run(cmd, args=['/b']) + Removed bug abc/b + >>> bd.flush_reload() + >>> try: + ... bd.bug_from_uuid('b') + ... except libbe.bugdir.NoBugMatches: + ... print 'Bug not found' + Bug not found + >>> ui.cleanup() + >>> bd.cleanup() + """ + name = 'remove' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.args.extend([ + libbe.command.Argument( + name='bug-id', metavar='BUG-ID', default=None, + repeatable=True, + completion_callback=libbe.command.util.complete_bug_id), + ]) + + def _run(self, **params): + bugdir = self._get_bugdir() + user_ids = [] + for bug_id in params['bug-id']: + bug,dummy_comment = libbe.command.util.bug_comment_from_user_id( + bugdir, bug_id) + user_ids.append(bug.id.user()) + bugdir.remove_bug(bug) + if len(user_ids) == 1: + print >> self.stdout, 'Removed bug %s' % user_ids[0] + else: + print >> self.stdout, 'Removed bugs %s' % ', '.join(user_ids) + return 0 + + def _long_help(self): + return """ +Remove (delete) existing bugs. Use with caution: if you're not using +a revision control system, there may be no way to recover the lost +information. You should use this command, for example, to get rid of +blank or otherwise mangled bugs. +""" diff --git a/libbe/command/serve.py b/libbe/command/serve.py new file mode 100644 index 0000000..7237343 --- /dev/null +++ b/libbe/command/serve.py @@ -0,0 +1,1172 @@ +# Copyright (C) 2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Define the :class:`Serve` serving BE Storage over HTTP. + +See Also +-------- +:mod:`libbe.storage.http` : the associated client +""" + +import hashlib +import logging +import os.path +import posixpath +import re +import sys +import time +import traceback +import types +import urllib +import wsgiref.simple_server +try: + # Python >= 2.6 + from urlparse import parse_qs +except ImportError: + # Python <= 2.5 + from cgi import parse_qs +try: + import cherrypy + import cherrypy.wsgiserver +except ImportError: + cherrypy = None +if cherrypy != None: + try: # CherryPy >= 3.2 + import cherrypy.wsgiserver.ssl_builtin + except ImportError: # CherryPy <= 3.1.X + cherrypy.wsgiserver.ssl_builtin = None +try: + import OpenSSL +except ImportError: + OpenSSL = None + +import libbe +import libbe.command +import libbe.command.util +import libbe.util.encoding +import libbe.version + +if libbe.TESTING == True: + import copy + import doctest + import StringIO + import unittest + import wsgiref.validate + try: + import cherrypy.test.webtest + cherrypy_test_webtest = True + except ImportError: + cherrypy_test_webtest = None + + import libbe.bugdir + +class _HandlerError (Exception): + def __init__(self, code, msg, headers=[]): + Exception.__init__(self, '%d %s' % (code, msg)) + self.code = code + self.msg = msg + self.headers = headers + +class _Unauthenticated (_HandlerError): + def __init__(self, realm, msg='User Not Authenticated', headers=[]): + _HandlerError.__init__(self, 401, msg, headers+[ + ('WWW-Authenticate','Basic realm="%s"' % realm)]) + +class _Unauthorized (_HandlerError): + def __init__(self, msg='User Not Authorized', headers=[]): + _HandlerError.__init__(self, 403, msg, headers) + +class User (object): + def __init__(self, uname=None, name=None, passhash=None, password=None): + self.uname = uname + self.name = name + self.passhash = passhash + if passhash == None: + if password != None: + self.passhash = self.hash(password) + else: + assert password == None, \ + 'Redundant password %s with passhash %s' % (password, passhash) + self.users = None + def from_string(self, string): + string = string.strip() + fields = string.split(':') + if len(fields) != 3: + raise ValueError, '%d!=3 fields in "%s"' % (len(fields), string) + self.uname,self.name,self.passhash = fields + def __str__(self): + return ':'.join([self.uname, self.name, self.passhash]) + def __cmp__(self, other): + return cmp(self.uname, other.uname) + def hash(self, password): + return hashlib.sha1(password).hexdigest() + def valid_login(self, password): + if self.hash(password) == self.passhash: + return True + return False + def set_name(self, name): + self._set_property('name', name) + def set_password(self, password): + self._set_property('passhash', self.hash(password)) + def _set_property(self, property, value): + if self.uname == 'guest': + raise _Unauthorized('guest user not allowed to change %s' % property) + if getattr(self, property) != value \ + and self.users != None: + self.users.changed = True + setattr(self, property, value) + +class Users (dict): + def __init__(self, filename=None): + dict.__init__(self) + self.filename = filename + self.changed = False + def load(self): + if self.filename == None: + return + user_file = libbe.util.encoding.get_file_contents( + self.filename, decode=True) + self.clear() + for line in user_file.splitlines(): + user = User() + user.from_string(line) + self.add_user(user) + def save(self): + if self.filename != None and self.changed == True: + lines = [] + for user in sorted(self.users): + lines.append(str(user)) + libbe.util.encoding.set_file_contents(self.filename) + self.changed = False + def add_user(self, user): + assert user.users == None, user.users + user.users = self + self[user.uname] = user + def valid_login(self, uname, password): + if uname in self and \ + self[uname].valid_login(password) == True: + return True + return False + +class WSGI_Object (object): + """Utility class for WGSI clients and middleware. + + For details on WGSI, see `PEP 333`_ + + .. _PEP 333: http://www.python.org/dev/peps/pep-0333/ + """ + def __init__(self, logger=None, log_level=logging.INFO, log_format=None): + self.logger = logger + self.log_level = log_level + if log_format == None: + self.log_format = ( + '%(REMOTE_ADDR)s - %(REMOTE_USER)s [%(time)s] ' + '"%(REQUEST_METHOD)s %(REQUEST_URI)s %(HTTP_VERSION)s" ' + '%(status)s %(bytes)s "%(HTTP_REFERER)s" "%(HTTP_USER_AGENT)s"') + else: + self.log_format = log_format + + def __call__(self, environ, start_response): + """The main WSGI entry point.""" + raise NotImplementedError + # start_response() is a callback for setting response headers + # start_response(status, response_headers, exc_info=None) + # status is an HTTP status string (e.g., "200 OK"). + # response_headers is a list of 2-tuples, the HTTP headers in + # key-value format. + # exc_info is used in exception handling. + # + # The application function then returns an iterable of body chunks. + + def error(self, environ, start_response, error, message, headers=[]): + """Make it easy to call start_response for errors.""" + response = '%d %s' % (error, message) + self.log_request(environ, status=response, bytes=len(message)) + start_response(response, + [('Content-Type', 'text/plain')]+headers) + return [message] + + def log_request(self, environ, status='-1 OK', bytes=-1): + if self.logger == None: + return + req_uri = urllib.quote(environ.get('SCRIPT_NAME', '') + + environ.get('PATH_INFO', '')) + if environ.get('QUERY_STRING'): + req_uri += '?'+environ['QUERY_STRING'] + start = time.localtime() + if time.daylight: + offset = time.altzone / 60 / 60 * -100 + else: + offset = time.timezone / 60 / 60 * -100 + if offset >= 0: + offset = "+%0.4d" % (offset) + elif offset < 0: + offset = "%0.4d" % (offset) + d = { + 'REMOTE_ADDR': environ.get('REMOTE_ADDR') or '-', + 'REMOTE_USER': environ.get('REMOTE_USER') or '-', + 'REQUEST_METHOD': environ['REQUEST_METHOD'], + 'REQUEST_URI': req_uri, + 'HTTP_VERSION': environ.get('SERVER_PROTOCOL'), + 'time': time.strftime('%d/%b/%Y:%H:%M:%S ', start) + offset, + 'status': status.split(None, 1)[0], + 'bytes': bytes, + 'HTTP_REFERER': environ.get('HTTP_REFERER', '-'), + 'HTTP_USER_AGENT': environ.get('HTTP_USER_AGENT', '-'), + } + self.logger.log(self.log_level, self.log_format % d) + +class ExceptionApp (WSGI_Object): + """Some servers (e.g. cherrypy) eat app-raised exceptions. + + Work around that by logging tracebacks by hand. + """ + def __init__(self, app, *args, **kwargs): + WSGI_Object.__init__(self, *args, **kwargs) + self.app = app + + def __call__(self, environ, start_response): + if self.logger != None: + self.logger.log(logging.DEBUG, 'ExceptionApp') + try: + return self.app(environ, start_response) + except Exception, e: + etype,value,tb = sys.exc_info() + trace = ''.join( + traceback.format_exception(etype, value, tb, None)) + self.logger.log(self.log_level, trace) + raise + +class UppercaseHeaderApp (WSGI_Object): + """WSGI middleware that uppercases incoming HTTP headers. + + From PEP 333, `The start_response() Callable`_ : + + A reminder for server/gateway authors: HTTP + header names are case-insensitive, so be sure + to take that into consideration when examining + application-supplied headers! + + .. _The start_response() Callable: + http://www.python.org/dev/peps/pep-0333/#id20 + """ + def __init__(self, app, *args, **kwargs): + WSGI_Object.__init__(self, *args, **kwargs) + self.app = app + + def __call__(self, environ, start_response): + if self.logger != None: + self.logger.log(logging.DEBUG, 'UppercaseHeaderApp') + for key,value in environ.items(): + if key.startswith('HTTP_'): + uppercase = key.upper() + if uppercase != key: + environ[uppercase] = environ.pop(key) + return self.app(environ, start_response) + +class AuthenticationApp (WSGI_Object): + """WSGI middleware for handling user authentication. + """ + def __init__(self, app, realm, setting='be-auth', users=None, *args, **kwargs): + WSGI_Object.__init__(self, *args, **kwargs) + self.app = app + self.realm = realm + self.setting = setting + self.users = users + + def __call__(self, environ, start_response): + if self.logger != None: + self.logger.log(logging.DEBUG, 'AuthenticationApp') + environ['%s.realm' % self.setting] = self.realm + try: + username = self.authenticate(environ) + environ['%s.user' % self.setting] = username + environ['%s.user.name' % self.setting] = \ + self.users[username].name + return self.app(environ, start_response) + except _Unauthorized, e: + return self.error(environ, start_response, + e.code, e.msg, e.headers) + + def authenticate(self, environ): + """Handle user-authentication sent in the "Authorization" header. + + This function implements ``Basic`` authentication as described in + HTTP/1.0 specification [1]_ . Do not use this module unless you + are using SSL, as it transmits unencrypted passwords. + + .. [1] http://www.w3.org/Protocols/HTTP/1.0/draft-ietf-http-spec.html#BasicAA + + Examples + -------- + + >>> users = Users() + >>> users.add_user(User('Aladdin', 'Big Al', password='open sesame')) + >>> app = AuthenticationApp(app=None, realm='Dummy Realm', users=users) + >>> app.authenticate({'HTTP_AUTHORIZATION':'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='}) + 'Aladdin' + >>> app.authenticate({'HTTP_AUTHORIZATION':'Basic AAAAAAAAAAAAAAAAAAAAAAAAAA=='}) + + Notes + ----- + + Code based on authkit/authenticate/basic.py + (c) 2005 Clark C. Evans. + Released under the MIT License: + http://www.opensource.org/licenses/mit-license.php + """ + authorization = environ.get('HTTP_AUTHORIZATION', None) + if authorization == None: + raise _Unauthorized('Authorization required') + try: + authmeth,auth = authorization.split(' ',1) + except ValueError: + return None + if 'basic' != authmeth.lower(): + return None # non-basic HTTP authorization not implemented + auth = auth.strip().decode('base64') + try: + username,password = auth.split(':',1) + except ValueError: + return None + if self.authfunc(environ, username, password) == True: + return username + + def authfunc(self, environ, username, password): + if not username in self.users: + return False + if self.users[username].valid_login(password) == True: + if self.logger != None: + self.logger.log(self.log_level, + 'Authenticated %s' % self.users[username].name) + return True + return False + +class WSGI_AppObject (WSGI_Object): + """Useful WSGI utilities for handling data (POST, QUERY) and + returning responses. + """ + def __init__(self, *args, **kwargs): + WSGI_Object.__init__(self, *args, **kwargs) + + # Maximum input we will accept when REQUEST_METHOD is POST + # 0 ==> unlimited input + self.maxlen = 0 + + def ok_response(self, environ, start_response, content, + content_type='application/octet-stream', + headers=[]): + if content == None: + start_response('200 OK', []) + return [] + if type(content) == types.UnicodeType: + content = content.encode('utf-8') + for i,header in enumerate(headers): + header_name,header_value = header + if type(header_value) == types.UnicodeType: + headers[i] = (header_name, header_value.encode('ISO-8859-1')) + response = '200 OK' + content_length = len(content) + self.log_request(environ, status=response, bytes=content_length) + start_response('200 OK', [ + ('Content-Type', content_type), + ('Content-Length', str(content_length)), + ]+headers) + if self.is_head(environ) == True: + return [] + return [content] + + def query_data(self, environ): + if not environ['REQUEST_METHOD'] in ['GET', 'HEAD']: + raise _HandlerError(404, 'Not Found') + return self._parse_query(environ.get('QUERY_STRING', '')) + + def _parse_query(self, query): + if len(query) == 0: + return {} + data = parse_qs( + query, keep_blank_values=True, strict_parsing=True) + for k,v in data.items(): + if len(v) == 1: + data[k] = v[0] + return data + + def post_data(self, environ): + if environ['REQUEST_METHOD'] != 'POST': + raise _HandlerError(404, 'Not Found') + post_data = self._read_post_data(environ) + return self._parse_post(post_data) + + def _parse_post(self, post): + return self._parse_query(post) + + def _read_post_data(self, environ): + try: + clen = int(environ.get('CONTENT_LENGTH', '0')) + except ValueError: + clen = 0 + if clen != 0: + if self.maxlen > 0 and clen > self.maxlen: + raise ValueError, 'Maximum content length exceeded' + return environ['wsgi.input'].read(clen) + return '' + + def data_get_string(self, data, key, default=None, source='query'): + if not key in data or data[key] in [None, 'None']: + if default == _HandlerError: + raise _HandlerError(406, 'Missing %s key %s' % (source, key)) + return default + return data[key] + + def data_get_id(self, data, key='id', default=_HandlerError, + source='query'): + return self.data_get_string(data, key, default, source) + + def data_get_boolean(self, data, key, default=False, source='query'): + val = self.data_get_string(data, key, default, source) + if val == 'True': + return True + elif val == 'False': + return False + return val + + def is_head(self, environ): + return environ['REQUEST_METHOD'] == 'HEAD' + + +class AdminApp (WSGI_AppObject): + """WSGI middleware for managing users (changing passwords, + usernames, etc.). + """ + def __init__(self, app, users=None, url=r'^admin/?', *args, **kwargs): + WSGI_AppObject.__init__(self, *args, **kwargs) + self.app = app + self.users = users + self.url = url + + def __call__(self, environ, start_response): + if self.logger != None: + self.logger.log(logging.DEBUG, 'AdminApp') + path = environ.get('PATH_INFO', '').lstrip('/') + match = re.search(self.url, path) + if match is not None: + return self.admin(environ, start_response) + return self.app(environ, start_response) + + def admin(self, environ, start_response): + if not 'be-auth.user' in environ: + raise _Unauthenticated(realm=envirion.get('be-auth.realm')) + uname = environ.get('be-auth.user') + user = self.users[uname] + data = self.post_data(environ) + source = 'post' + name = self.data_get_string( + data, 'name', default=None, source=source) + if name != None: + self.users[uname].set_name(name) + password = self.data_get_string( + data, 'password', default=None, source=source) + if password != None: + self.users[uname].set_password(password) + self.users.save() + return self.ok_response(environ, start_response, None) + +class ServerApp (WSGI_AppObject): + """WSGI server for a BE Storage instance over HTTP. + + RESTful_ WSGI request handler for serving the + libbe.storage.http.HTTP backend with GET, POST, and HEAD commands. + For more information on authentication and REST, see John + Calcote's `Open Sourcery article`_ + + .. _RESTful: http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm + .. _Open Sourcery article: http://jcalcote.wordpress.com/2009/08/10/restful-authentication/ + + This serves files from a connected storage instance, usually + a VCS-based repository located on the local machine. + + Notes + ----- + + The GET and HEAD requests are identical except that the HEAD + request omits the actual content of the file. + """ + server_version = "BE-server/" + libbe.version.version() + + def __init__(self, storage, *args, **kwargs): + WSGI_AppObject.__init__(self, *args, **kwargs) + self.storage = storage + self.http_user_error = 418 + + self.urls = [ + (r'^add/?', self.add), + (r'^exists/?', self.exists), + (r'^remove/?', self.remove), + (r'^ancestors/?', self.ancestors), + (r'^children/?', self.children), + (r'^get/(.+)', self.get), + (r'^set/(.+)', self.set), + (r'^commit/?', self.commit), + (r'^revision-id/?', self.revision_id), + (r'^changed/?', self.changed), + (r'^version/?', self.version), + ] + + def __call__(self, environ, start_response): + """The main WSGI application. + + Dispatch the current request to the functions from above and + store the regular expression captures in the WSGI environment + as `be-server.url_args` so that the functions from above can + access the url placeholders. + + URL dispatcher from Armin Ronacher's "Getting Started with WSGI" + http://lucumr.pocoo.org/2007/5/21/getting-started-with-wsgi + """ + if self.logger != None: + self.logger.log(logging.DEBUG, 'ServerApp') + path = environ.get('PATH_INFO', '').lstrip('/') + try: + for regex, callback in self.urls: + match = re.search(regex, path) + if match is not None: + environ['be-server.url_args'] = match.groups() + try: + return callback(environ, start_response) + except libbe.storage.NotReadable, e: + raise _HandlerError(403, 'Read permission denied') + except libbe.storage.NotWriteable, e: + raise _HandlerError(403, 'Write permission denied') + except libbe.storage.InvalidID, e: + raise _HandlerError( + self.http_user_error, 'InvalidID %s' % e) + raise _HandlerError(404, 'Not Found') + except _HandlerError, e: + return self.error(environ, start_response, + e.code, e.msg, e.headers) + + # handlers + def add(self, environ, start_response): + self.check_login(environ) + data = self.post_data(environ) + source = 'post' + id = self.data_get_id(data, source=source) + parent = self.data_get_string( + data, 'parent', default=None, source=source) + directory = self.data_get_boolean( + data, 'directory', default=False, source=source) + self.storage.add(id, parent=parent, directory=directory) + return self.ok_response(environ, start_response, None) + + def exists(self, environ, start_response): + self.check_login(environ) + data = self.query_data(environ) + source = 'query' + id = self.data_get_id(data, source=source) + revision = self.data_get_string( + data, 'revision', default=None, source=source) + content = str(self.storage.exists(id, revision)) + return self.ok_response(environ, start_response, content) + + def remove(self, environ, start_response): + self.check_login(environ) + data = self.post_data(environ) + source = 'post' + id = self.data_get_id(data, source=source) + recursive = self.data_get_boolean( + data, 'recursive', default=False, source=source) + if recursive == True: + self.storage.recursive_remove(id) + else: + self.storage.remove(id) + return self.ok_response(environ, start_response, None) + + def ancestors(self, environ, start_response): + self.check_login(environ) + data = self.query_data(environ) + source = 'query' + id = self.data_get_id(data, source=source) + revision = self.data_get_string( + data, 'revision', default=None, source=source) + content = '\n'.join(self.storage.ancestors(id, revision))+'\n' + return self.ok_response(environ, start_response, content) + + def children(self, environ, start_response): + self.check_login(environ) + data = self.query_data(environ) + source = 'query' + id = self.data_get_id(data, default=None, source=source) + revision = self.data_get_string( + data, 'revision', default=None, source=source) + content = '\n'.join(self.storage.children(id, revision)) + return self.ok_response(environ, start_response, content) + + def get(self, environ, start_response): + self.check_login(environ) + data = self.query_data(environ) + source = 'query' + try: + id = environ['be-server.url_args'][0] + except: + raise _HandlerError(404, 'Not Found') + revision = self.data_get_string( + data, 'revision', default=None, source=source) + content = self.storage.get(id, revision=revision) + be_version = self.storage.storage_version(revision) + return self.ok_response(environ, start_response, content, + headers=[('X-BE-Version', be_version)]) + + def set(self, environ, start_response): + self.check_login(environ) + data = self.post_data(environ) + try: + id = environ['be-server.url_args'][0] + except: + raise _HandlerError(404, 'Not Found') + if not 'value' in data: + raise _HandlerError(406, 'Missing query key value') + value = data['value'] + self.storage.set(id, value) + return self.ok_response(environ, start_response, None) + + def commit(self, environ, start_response): + self.check_login(environ) + data = self.post_data(environ) + if not 'summary' in data: + raise _HandlerError(406, 'Missing query key summary') + summary = data['summary'] + if not 'body' in data or data['body'] == 'None': + data['body'] = None + body = data['body'] + if not 'allow_empty' in data \ + or data['allow_empty'] == 'True': + allow_empty = True + else: + allow_empty = False + try: + revision = self.storage.commit(summary, body, allow_empty) + except libbe.storage.EmptyCommit, e: + raise _HandlerError(self.http_user_error, 'EmptyCommit') + return self.ok_response(environ, start_response, revision) + + def revision_id(self, environ, start_response): + self.check_login(environ) + data = self.query_data(environ) + source = 'query' + index = int(self.data_get_string( + data, 'index', default=_HandlerError, source=source)) + content = self.storage.revision_id(index) + return self.ok_response(environ, start_response, content) + + def changed(self, environ, start_response): + self.check_login(environ) + data = self.query_data(environ) + source = 'query' + revision = self.data_get_string( + data, 'revision', default=None, source=source) + add,mod,rem = self.storage.changed(revision) + content = '\n\n'.join(['\n'.join(p) for p in (add,mod,rem)]) + return self.ok_response(environ, start_response, content) + + def version(self, environ, start_response): + self.check_login(environ) + data = self.query_data(environ) + source = 'query' + revision = self.data_get_string( + data, 'revision', default=None, source=source) + content = self.storage.storage_version(revision) + return self.ok_response(environ, start_response, content) + + # handler utility functions + def check_login(self, environ): + user = environ.get('be-auth.user', None) + if user != None: # we're running under AuthenticationApp + if environ['REQUEST_METHOD'] == 'POST': + if user == 'guest' or self.storage.is_writeable() == False: + raise _Unauthorized() # only non-guests allowed to write + # allow read-only commands for all users + + +class Serve (libbe.command.Command): + """:class:`~libbe.command.base.Command` wrapper around + :class:`ServerApp`. + """ + + name = 'serve' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='port', + help='Bind server to port (%default)', + arg=libbe.command.Argument( + name='port', metavar='INT', type='int', default=8000)), + libbe.command.Option(name='host', + help='Set host string (blank for localhost, %default)', + arg=libbe.command.Argument( + name='host', metavar='HOST', default='')), + libbe.command.Option(name='read-only', short_name='r', + help='Dissable operations that require writing'), + libbe.command.Option(name='ssl', short_name='s', + help='Use CherryPy to serve HTTPS (HTTP over SSL/TLS)'), + libbe.command.Option(name='auth', short_name='a', + help='Require authentication. FILE should be a file containing colon-separated UNAME:USER:sha1(PASSWORD) lines, for example: "jdoe:John Doe <jdoe@example.com>:read:d99f8e5a4b02dc25f49da2ea67c0034f61779e72"', + arg=libbe.command.Argument( + name='auth', metavar='FILE', default=None, + completion_callback=libbe.command.util.complete_path)), + ]) + + def _run(self, **params): + self._setup_logging() + storage = self._get_storage() + if params['read-only'] == True: + writeable = storage.writeable + storage.writeable = False + if params['host'] == '': + params['host'] = 'localhost' + if params['auth'] != None: + self._check_restricted_access(storage, params['auth']) + users = Users(params['auth']) + users.load() + app = ServerApp(storage=storage, logger=self.logger) + if params['auth'] != None: + app = AdminApp(app, users=users, logger=self.logger) + app = AuthenticationApp(app, realm=storage.repo, + users=users, logger=self.logger) + app = UppercaseHeaderApp(app, logger=self.logger) + server,details = self._get_server(params, app) + details['repo'] = storage.repo + try: + self._start_server(params, server, details) + except KeyboardInterrupt: + pass + self._stop_server(params, server) + if params['read-only'] == True: + storage.writeable = writeable + + def _setup_logging(self, log_level=logging.INFO): + self.logger = logging.getLogger('be-serve') + self.log_level = logging.INFO + console = logging.StreamHandler(self.stdout) + console.setFormatter(logging.Formatter('%(message)s')) + self.logger.addHandler(console) + self.logger.propagate = False + if log_level is not None: + console.setLevel(log_level) + self.logger.setLevel(log_level) + + def _get_server(self, params, app): + details = {'port':params['port']} + if params['ssl'] == True: + details['protocol'] = 'HTTPS' + if cherrypy == None: + raise libbe.command.UserError, \ + '--ssl requires the cherrypy module' + app = ExceptionApp(app, logger=self.logger) + server = cherrypy.wsgiserver.CherryPyWSGIServer( + (params['host'], params['port']), app) + #server.throw_errors = True + #server.show_tracebacks = True + private_key,certificate = get_cert_filenames( + 'be-server', logger=self.logger) + if cherrypy.wsgiserver.ssl_builtin == None: + server.ssl_module = 'builtin' + server.ssl_private_key = private_key + server.ssl_certificate = certificate + else: + server.ssl_adapter = \ + cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter( + certificate=certificate, private_key=private_key) + details['socket-name'] = params['host'] + else: + details['protocol'] = 'HTTP' + server = wsgiref.simple_server.make_server( + params['host'], params['port'], app) + details['socket-name'] = server.socket.getsockname()[0] + return (server, details) + + def _start_server(self, params, server, details): + self.logger.log(self.log_level, + 'Serving %(protocol)s on %(socket-name)s port %(port)s ...' \ + % details) + self.logger.log(self.log_level, + 'BE repository %(repo)s' % details) + if params['ssl'] == True: + server.start() + else: + server.serve_forever() + + def _stop_server(self, params, server): + self.logger.log(self.log_level, 'Clossing server') + if params['ssl'] == True: + server.stop() + else: + server.server_close() + + def _long_help(self): + return """ +Example usage:: + + $ be serve + +And in another terminal (or after backgrounding the server):: + + $ be --repo http://localhost:8000/ list + +If you bind your server to a public interface, take a look at the +``--read-only`` option or the combined ``--ssl --auth FILE`` +options so other people can't mess with your repository. If you do use +authentication, you'll need to send in your username and password with, +for example:: + + $ be --repo http://username:password@localhost:8000/ list +""" + +def random_string(length=256): + if os.path.exists(os.path.join('dev', 'urandom')): + return open("/dev/urandom").read(length) + else: + import array + from random import randint + d = array.array('B') + for i in xrange(1000000): + d.append(randint(0,255)) + return d.tostring() + +if libbe.TESTING == True: + class WSGITestCase (unittest.TestCase): + def setUp(self): + self.logstream = StringIO.StringIO() + self.logger = logging.getLogger('be-serve-test') + console = logging.StreamHandler(self.logstream) + console.setFormatter(logging.Formatter('%(message)s')) + self.logger.addHandler(console) + self.logger.propagate = False + console.setLevel(logging.INFO) + self.logger.setLevel(logging.INFO) + self.default_environ = { # required by PEP 333 + 'REQUEST_METHOD': 'GET', # 'POST', 'HEAD' + 'SCRIPT_NAME':'', + 'PATH_INFO': '', + #'QUERY_STRING':'', # may be empty or absent + #'CONTENT_TYPE':'', # may be empty or absent + #'CONTENT_LENGTH':'', # may be empty or absent + 'SERVER_NAME':'example.com', + 'SERVER_PORT':'80', + 'SERVER_PROTOCOL':'HTTP/1.1', + 'wsgi.version':(1,0), + 'wsgi.url_scheme':'http', + 'wsgi.input':StringIO.StringIO(), + 'wsgi.errors':StringIO.StringIO(), + 'wsgi.multithread':False, + 'wsgi.multiprocess':False, + 'wsgi.run_once':False, + } + def getURL(self, app, path='/', method='GET', data=None, + scheme='http', environ={}): + env = copy.copy(self.default_environ) + env['PATH_INFO'] = path + env['REQUEST_METHOD'] = method + env['scheme'] = scheme + if data != None: + enc_data = urllib.urlencode(data) + if method == 'POST': + env['CONTENT_LENGTH'] = len(enc_data) + env['wsgi.input'] = StringIO.StringIO(enc_data) + else: + assert method in ['GET', 'HEAD'], method + env['QUERY_STRING'] = enc_data + for key,value in environ.items(): + env[key] = value + return ''.join(app(env, self.start_response)) + def start_response(self, status, response_headers, exc_info=None): + self.status = status + self.response_headers = response_headers + self.exc_info = exc_info + + class WSGI_ObjectTestCase (WSGITestCase): + def setUp(self): + WSGITestCase.setUp(self) + self.app = WSGI_Object(self.logger) + def test_error(self): + contents = self.app.error( + environ=self.default_environ, + start_response=self.start_response, + error=123, + message='Dummy Error', + headers=[('X-Dummy-Header','Dummy Value')]) + self.failUnless(contents == ['Dummy Error'], contents) + self.failUnless(self.status == '123 Dummy Error', self.status) + self.failUnless(self.response_headers == [ + ('Content-Type','text/plain'), + ('X-Dummy-Header','Dummy Value')], + self.response_headers) + self.failUnless(self.exc_info == None, self.exc_info) + def test_log_request(self): + self.app.log_request( + environ=self.default_environ, status='-1 OK', bytes=123) + log = self.logstream.getvalue() + self.failUnless(log.startswith('- -'), log) + + class ExceptionAppTestCase (WSGITestCase): + def setUp(self): + WSGITestCase.setUp(self) + def child_app(environ, start_response): + raise ValueError('Dummy Error') + self.app = ExceptionApp(child_app, self.logger) + def test_traceback(self): + try: + self.getURL(self.app) + except ValueError, e: + pass + log = self.logstream.getvalue() + self.failUnless(log.startswith('Traceback'), log) + self.failUnless('child_app' in log, log) + self.failUnless('ValueError: Dummy Error' in log, log) + + class AdminAppTestCase (WSGITestCase): + def setUp(self): + WSGITestCase.setUp(self) + self.users = Users() + self.users.add_user( + User('Aladdin', 'Big Al', password='open sesame')) + self.users.add_user( + User('guest', 'Guest', password='guestpass')) + def child_app(environ, start_response): + pass + self.app = AdminApp( + child_app, users=self.users, logger=self.logger) + self.app = AuthenticationApp( + self.app, realm='Dummy Realm', users=self.users, + logger=self.logger) + self.app = UppercaseHeaderApp(self.app, logger=self.logger) + def basic_auth(self, uname, password): + """HTTP basic authorization string""" + return 'Basic %s' % \ + ('%s:%s' % (uname, password)).encode('base64') + def test_new_name(self): + self.getURL( + self.app, '/admin/', method='POST', + data={'name':'Prince Al'}, + environ={'HTTP_Authorization': + self.basic_auth('Aladdin', 'open sesame')}) + self.failUnless(self.status == '200 OK', self.status) + self.failUnless(self.response_headers == [], + self.response_headers) + self.failUnless(self.exc_info == None, self.exc_info) + self.failUnless(self.users['Aladdin'].name == 'Prince Al', + self.users['Aladdin'].name) + self.failUnless(self.users.changed == True, + self.users.changed) + def test_new_password(self): + self.getURL( + self.app, '/admin/', method='POST', + data={'password':'New Pass'}, + environ={'HTTP_Authorization': + self.basic_auth('Aladdin', 'open sesame')}) + self.failUnless(self.status == '200 OK', self.status) + self.failUnless(self.response_headers == [], + self.response_headers) + self.failUnless(self.exc_info == None, self.exc_info) + self.failUnless(self.users['Aladdin'].passhash == \ + self.users['Aladdin'].hash('New Pass'), + self.users['Aladdin'].passhash) + self.failUnless(self.users.changed == True, + self.users.changed) + def test_guest_name(self): + self.getURL( + self.app, '/admin/', method='POST', + data={'name':'SPAM'}, + environ={'HTTP_Authorization': + self.basic_auth('guest', 'guestpass')}) + self.failUnless(self.status.startswith('403 '), self.status) + self.failUnless(self.response_headers == [ + ('Content-Type', 'text/plain')], + self.response_headers) + self.failUnless(self.exc_info == None, self.exc_info) + self.failUnless(self.users['guest'].name == 'Guest', + self.users['guest'].name) + self.failUnless(self.users.changed == False, + self.users.changed) + def test_guest_password(self): + self.getURL( + self.app, '/admin/', method='POST', + data={'password':'SPAM'}, + environ={'HTTP_Authorization': + self.basic_auth('guest', 'guestpass')}) + self.failUnless(self.status.startswith('403 '), self.status) + self.failUnless(self.response_headers == [ + ('Content-Type', 'text/plain')], + self.response_headers) + self.failUnless(self.exc_info == None, self.exc_info) + self.failUnless(self.users['guest'].name == 'Guest', + self.users['guest'].name) + self.failUnless(self.users.changed == False, + self.users.changed) + + class ServerAppTestCase (WSGITestCase): + def setUp(self): + WSGITestCase.setUp(self) + self.bd = libbe.bugdir.SimpleBugDir(memory=False) + self.app = ServerApp(self.bd.storage, logger=self.logger) + def tearDown(self): + self.bd.cleanup() + WSGITestCase.tearDown(self) + def test_add_get(self): + self.getURL(self.app, '/add/', method='GET') + self.failUnless(self.status.startswith('404 '), self.status) + self.failUnless(self.response_headers == [ + ('Content-Type', 'text/plain')], + self.response_headers) + self.failUnless(self.exc_info == None, self.exc_info) + def test_add_post(self): + self.getURL(self.app, '/add/', method='POST', + data={'id':'123456', 'parent':'abc123', + 'directory':'True'}) + self.failUnless(self.status == '200 OK', self.status) + self.failUnless(self.response_headers == [], + self.response_headers) + self.failUnless(self.exc_info == None, self.exc_info) + # Note: other methods tested in libbe.storage.http + + # TODO: integration tests on Serve? + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) + + +# The following certificate-creation code is adapted From pyOpenSSL's +# examples. + +def get_cert_filenames(server_name, autogenerate=True, logger=None): + """ + Generate private key and certification filenames. + get_cert_filenames(server_name) -> (pkey_filename, cert_filename) + """ + pkey_file = '%s.pkey' % server_name + cert_file = '%s.cert' % server_name + if autogenerate == True: + for file in [pkey_file, cert_file]: + if not os.path.exists(file): + make_certs(server_name, logger) + return (pkey_file, cert_file) + +def createKeyPair(type, bits): + """Create a public/private key pair. + + Returns the public/private key pair in a PKey object. + + Parameters + ---------- + type : TYPE_RSA or TYPE_DSA + Key type. + bits : int + Number of bits to use in the key. + """ + pkey = OpenSSL.crypto.PKey() + pkey.generate_key(type, bits) + return pkey + +def createCertRequest(pkey, digest="md5", **name): + """Create a certificate request. + + Returns the certificate request in an X509Req object. + + Parameters + ---------- + pkey : PKey + The key to associate with the request. + digest : "md5" or ? + Digestion method to use for signing, default is "md5", + `**name` : + The name of the subject of the request, possible. + Arguments are: + + ============ ======================== + C Country name + ST State or province name + L Locality name + O Organization name + OU Organizational unit name + CN Common name + emailAddress E-mail address + ============ ======================== + """ + req = OpenSSL.crypto.X509Req() + subj = req.get_subject() + + for (key,value) in name.items(): + setattr(subj, key, value) + + req.set_pubkey(pkey) + req.sign(pkey, digest) + return req + +def createCertificate(req, (issuerCert, issuerKey), serial, (notBefore, notAfter), digest="md5"): + """Generate a certificate given a certificate request. + + Returns the signed certificate in an X509 object. + + Parameters + ---------- + req : + Certificate reqeust to use + issuerCert : + The certificate of the issuer + issuerKey : + The private key of the issuer + serial : + Serial number for the certificate + notBefore : + Timestamp (relative to now) when the certificate + starts being valid + notAfter : + Timestamp (relative to now) when the certificate + stops being valid + digest : + Digest method to use for signing, default is md5 + """ + cert = OpenSSL.crypto.X509() + cert.set_serial_number(serial) + cert.gmtime_adj_notBefore(notBefore) + cert.gmtime_adj_notAfter(notAfter) + cert.set_issuer(issuerCert.get_subject()) + cert.set_subject(req.get_subject()) + cert.set_pubkey(req.get_pubkey()) + cert.sign(issuerKey, digest) + return cert + +def make_certs(server_name, logger=None) : + """Generate private key and certification files. + + `mk_certs(server_name) -> (pkey_filename, cert_filename)` + """ + if OpenSSL == None: + raise libbe.command.UserError, \ + 'SSL certificate generation requires the OpenSSL module' + pkey_file,cert_file = get_cert_filenames( + server_name, autogenerate=False) + if logger != None: + logger.log(logger._server_level, + 'Generating certificates', pkey_file, cert_file) + cakey = createKeyPair(OpenSSL.crypto.TYPE_RSA, 1024) + careq = createCertRequest(cakey, CN='Certificate Authority') + cacert = createCertificate( + careq, (careq, cakey), 0, (0, 60*60*24*365*5)) # five years + open(pkey_file, 'w').write(OpenSSL.crypto.dump_privatekey( + OpenSSL.crypto.FILETYPE_PEM, cakey)) + open(cert_file, 'w').write(OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_PEM, cacert)) diff --git a/libbe/command/set.py b/libbe/command/set.py new file mode 100644 index 0000000..720dd0f --- /dev/null +++ b/libbe/command/set.py @@ -0,0 +1,144 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# Marien Zwart <marienz@gentoo.org> +# Thomas Gerigk <tgerigk@gmx.de> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +import textwrap + +import libbe +import libbe.bugdir +import libbe.command +import libbe.command.util +from libbe.storage.util.settings_object import EMPTY + + +class Set (libbe.command.Command): + """Change bug directory settings + + >>> import sys + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_storage(bd.storage) + >>> cmd = Set(ui=ui) + + >>> ret = ui.run(cmd, args=['target']) + None + >>> ret = ui.run(cmd, args=['target', 'abcdefg']) + >>> ret = ui.run(cmd, args=['target']) + abcdefg + >>> ret = ui.run(cmd, args=['target', 'none']) + >>> ret = ui.run(cmd, args=['target']) + None + >>> ui.cleanup() + >>> bd.cleanup() + """ + name = 'set' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.args.extend([ + libbe.command.Argument( + name='setting', metavar='SETTING', optional=True, + completion_callback=complete_bugdir_settings), + libbe.command.Argument( + name='value', metavar='VALUE', optional=True) + ]) + + def _run(self, **params): + bugdir = self._get_bugdir() + if params['setting'] == None: + keys = bugdir.settings_properties + keys.sort() + for key in keys: + print >> self.stdout, \ + '%16s: %s' % (key, _value_string(bugdir, key)) + return 0 + if params['setting'] not in bugdir.settings_properties: + msg = 'Invalid setting %s\n' % params['setting'] + msg += 'Allowed settings:\n ' + msg += '\n '.join(bugdir.settings_properties) + raise libbe.command.UserError(msg) + if params['value'] == None: + print _value_string(bugdir, params['setting']) + else: + if params['value'] == 'none': + params['value'] = EMPTY + old_setting = bugdir.settings.get(params['setting']) + attr = bugdir._setting_name_to_attr_name(params['setting']) + setattr(bugdir, attr, params['value']) + return 0 + + def _long_help(self): + return """ +Show or change per-tree settings. + +If name and value are supplied, the name is set to a new value. +If no value is specified, the current value is printed. +If no arguments are provided, all names and values are listed. + +To unset a setting, set it to "none". + +Allowed settings are: + +%s""" % ('\n'.join(get_bugdir_settings()),) + +def get_bugdir_settings(): + settings = [] + for s in libbe.bugdir.BugDir.settings_properties: + settings.append(s) + settings.sort() + documented_settings = [] + for s in settings: + set = getattr(libbe.bugdir.BugDir, s) + dstr = set.__doc__.strip() + # per-setting comment adjustments + if s == 'vcs_name': + lines = dstr.split('\n') + while lines[0].startswith('This property defaults to') == False: + lines.pop(0) + assert len(lines) != None, \ + 'Unexpected vcs_name docstring:\n "%s"' % dstr + lines.insert( + 0, 'The name of the revision control system to use.\n') + dstr = '\n'.join(lines) + doc = textwrap.wrap(dstr, width=70, initial_indent=' ', + subsequent_indent=' ') + documented_settings.append('%s\n%s' % (s, '\n'.join(doc))) + return documented_settings + +def _value_string(bugdir, setting): + val = bugdir.settings.get(setting, EMPTY) + if val == EMPTY: + default = getattr(bugdir, bugdir._setting_name_to_attr_name(setting)) + if default not in [None, EMPTY]: + val = 'None (%s)' % default + else: + val = None + return str(val) + +def complete_bugdir_settings(command, argument, fragment=None): + """ + List possible command completions for fragment. + + Neither the command nor argument arguments are used. + """ + return libbe.bugdir.BugDir.settings_properties diff --git a/libbe/command/severity.py b/libbe/command/severity.py new file mode 100644 index 0000000..27898f7 --- /dev/null +++ b/libbe/command/severity.py @@ -0,0 +1,98 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# Marien Zwart <marienz@gentoo.org> +# Thomas Gerigk <tgerigk@gmx.de> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import libbe +import libbe.bug +import libbe.command +import libbe.command.util + + +class Severity (libbe.command.Command): + """Change a bug's severity level + + >>> import sys + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_bugdir(bd) + >>> cmd = Severity(ui=ui) + + >>> bd.bug_from_uuid('a').severity + 'minor' + >>> ret = ui.run(cmd, args=['wishlist', '/a']) + >>> bd.flush_reload() + >>> bd.bug_from_uuid('a').severity + 'wishlist' + >>> ret = ui.run(cmd, args=['none', '/a']) + Traceback (most recent call last): + UserError: Invalid severity level: none + >>> ui.cleanup() + >>> bd.cleanup() + """ + name = 'severity' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.args.extend([ + libbe.command.Argument( + name='severity', metavar='SEVERITY', default=None, + completion_callback=libbe.command.util.complete_severity), + libbe.command.Argument( + name='bug-id', metavar='BUG-ID', default=None, + repeatable=True, + completion_callback=libbe.command.util.complete_bug_id), + ]) + + def _run(self, **params): + bugdir = self._get_bugdir() + for bug_id in params['bug-id']: + bug,dummy_comment = \ + libbe.command.util.bug_comment_from_user_id(bugdir, bug_id) + if bug.severity != params['severity']: + try: + bug.severity = params['severity'] + except ValueError, e: + if e.name != 'severity': + raise e + raise libbe.command.UserError( + 'Invalid severity level: %s' % e.value) + return 0 + + def _long_help(self): + ret = [""" +Show or change a bug's severity level. + +If no severity is specified, the current value is printed. If a severity level +is specified, it will be assigned to the bug. + +Severity levels are: +"""] + try: # See if there are any per-tree severity configurations + bd = self._get_bugdir() + except NotImplementedError: + pass # No tree, just show the defaults + longest_severity_len = max([len(s) for s in libbe.bug.severity_values]) + for severity in libbe.bug.severity_values : + description = libbe.bug.severity_description[severity] + ret.append('%*s : %s\n' \ + % (longest_severity_len, severity, description)) + return ''.join(ret) diff --git a/libbe/command/show.py b/libbe/command/show.py new file mode 100644 index 0000000..ab3be73 --- /dev/null +++ b/libbe/command/show.py @@ -0,0 +1,207 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# Thomas Gerigk <tgerigk@gmx.de> +# Thomas Habets <thomas@habets.pp.se> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import sys + +import libbe +import libbe.command +import libbe.command.util +import libbe.util.id +import libbe.version +import libbe._version + + +class Show (libbe.command.Command): + """Show a particular bug, comment, or combination of both. + + >>> import sys + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> io.stdout.encoding = 'ascii' + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_bugdir(bd) + >>> cmd = Show(ui=ui) + + >>> ret = ui.run(cmd, args=['/a',]) # doctest: +ELLIPSIS + ID : a + Short name : abc/a + Severity : minor + Status : open + Assigned : + Reporter : + Creator : John Doe <jdoe@example.com> + Created : ... + Bug A + <BLANKLINE> + + >>> ret = ui.run(cmd, {'xml':True}, ['/a']) # doctest: +ELLIPSIS + <?xml version="1.0" encoding="..." ?> + <be-xml> + <version> + <tag>...</tag> + <branch-nick>...</branch-nick> + <revno>...</revno> + <revision-id>...</revision-id> + </version> + <bug> + <uuid>a</uuid> + <short-name>abc/a</short-name> + <severity>minor</severity> + <status>open</status> + <creator>John Doe <jdoe@example.com></creator> + <created>Thu, 01 Jan 1970 00:00:00 +0000</created> + <summary>Bug A</summary> + </bug> + </be-xml> + >>> ui.cleanup() + >>> bd.cleanup() + """ + name = 'show' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='xml', short_name='x', + help='Dump as XML'), + libbe.command.Option(name='only-raw-body', + help="When printing only a single comment, just print it's" + " body. This allows extraction of non-text content types."), + libbe.command.Option(name='no-comments', short_name='c', + help="Disable comment output. This is useful if you just " + "want more details on a bug's current status."), + ]) + self.args.extend([ + libbe.command.Argument( + name='id', metavar='ID', default=None, + optional=True, repeatable=True, + completion_callback=libbe.command.util.complete_bug_comment_id), + ]) + + def _run(self, **params): + bugdir = self._get_bugdir() + if params['only-raw-body'] == True: + if len(params['id']) != 1: + raise libbe.command.UsageError( + 'only one ID accepted with --only-raw-body') + bug,comment = libbe.command.util.bug_comment_from_user_id( + bugdir, params['id'][0]) + if comment == bug.comment_root: + raise libbe.command.UsageError( + "--only-raw-body requires a comment ID, not '%s'" + % params['id'][0]) + sys.__stdout__.write(comment.body) + return 0 + print >> self.stdout, \ + output(bugdir, params['id'], encoding=self.stdout.encoding, + as_xml=params['xml'], + with_comments=not params['no-comments']) + return 0 + + def _long_help(self): + return """ +Show all information about the bugs or comments whose IDs are given. +If no IDs are given, show the entire repository. + +Without the --xml flag set, it's probably not a good idea to mix bug +and comment IDs in a single call, but you're free to do so if you +like. With the --xml flag set, there will never be any root comments, +so mix and match away (the bug listings for directly requested +comments will be restricted to the bug uuid and the requested +comment(s)). + +Directly requested comments will be grouped by their parent bug and +placed at the end of the output, so the ordering may not match the +order of the listed IDs. +""" + +def _sort_ids(bugdir, ids, with_comments=True): + bugs = [] + root_comments = {} + for id in ids: + p = libbe.util.id.parse_user(bugdir, id) + if p['type'] == 'bug': + bugs.append(p['bug']) + elif with_comments == True: + if p['bug'] not in root_comments: + root_comments[p['bug']] = [p['comment']] + else: + root_comments[p['bug']].append(p['comment']) + for bugname in root_comments.keys(): + assert bugname not in bugs, \ + 'specifically requested both #/%s/%s# and #/%s#' \ + % (bugname, root_comments[bugname][0], bugname) + return (bugs, root_comments) + +def _xml_header(encoding): + lines = ['<?xml version="1.0" encoding="%s" ?>' % encoding, + '<be-xml>', + ' <version>', + ' <tag>%s</tag>' % libbe.version.version()] + for tag in ['branch-nick', 'revno', 'revision-id']: + value = libbe._version.version_info[tag.replace('-', '_')] + lines.append(' <%s>%s</%s>' % (tag, value, tag)) + lines.append(' </version>') + return lines + +def _xml_footer(): + return ['</be-xml>'] + +def output(bd, ids, encoding, as_xml=True, with_comments=True): + if ids == None or len(ids) == 0: + bd.load_all_bugs() + ids = [bug.id.user() for bug in bd] + bugs,root_comments = _sort_ids(bd, ids, with_comments) + lines = [] + if as_xml: + lines.extend(_xml_header(encoding)) + else: + spaces_left = len(ids) - 1 + for bugname in bugs: + bug = bd.bug_from_uuid(bugname) + if as_xml: + lines.append(bug.xml(indent=2, show_comments=with_comments)) + else: + lines.append(bug.string(show_comments=with_comments)) + if spaces_left > 0: + spaces_left -= 1 + lines.append('') # add a blank line between bugs/comments + for bugname,comments in root_comments.items(): + bug = bd.bug_from_uuid(bugname) + if as_xml: + lines.extend([' <bug>', ' <uuid>%s</uuid>' % bug.uuid]) + for commname in comments: + try: + comment = bug.comment_root.comment_from_uuid(commname) + except KeyError, e: + raise libbe.command.UserError(e.message) + if as_xml: + lines.append(comment.xml(indent=4)) + else: + lines.append(comment.string()) + if spaces_left > 0: + spaces_left -= 1 + lines.append('') # add a blank line between bugs/comments + if as_xml: + lines.append('</bug>') + if as_xml: + lines.extend(_xml_footer()) + return '\n'.join(lines) diff --git a/libbe/command/status.py b/libbe/command/status.py new file mode 100644 index 0000000..1659f75 --- /dev/null +++ b/libbe/command/status.py @@ -0,0 +1,108 @@ +# Copyright (C) 2008-2010 Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import libbe +import libbe.bug +import libbe.command +import libbe.command.util + + +class Status (libbe.command.Command): + """Change a bug's status level + + >>> import sys + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_bugdir(bd) + >>> cmd = Status(ui=ui) + >>> cmd._storage = bd.storage + + >>> bd.bug_from_uuid('a').status + 'open' + >>> ret = ui.run(cmd, args=['closed', '/a']) + >>> bd.flush_reload() + >>> bd.bug_from_uuid('a').status + 'closed' + >>> ret = ui.run(cmd, args=['none', '/a']) + Traceback (most recent call last): + UserError: Invalid status level: none + >>> ui.cleanup() + >>> bd.cleanup() + """ + name = 'status' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.args.extend([ + libbe.command.Argument( + name='status', metavar='STATUS', default=None, + completion_callback=libbe.command.util.complete_status), + libbe.command.Argument( + name='bug-id', metavar='BUG-ID', default=None, + repeatable=True, + completion_callback=libbe.command.util.complete_bug_id), + ]) + + def _run(self, **params): + bugdir = self._get_bugdir() + for bug_id in params['bug-id']: + bug,dummy_comment = \ + libbe.command.util.bug_comment_from_user_id(bugdir, bug_id) + if bug.status != params['status']: + try: + bug.status = params['status'] + except ValueError, e: + if e.name != 'status': + raise e + raise libbe.command.UserError( + 'Invalid status level: %s' % e.value) + return 0 + + def _long_help(self): + longest_status_len = max([len(s) for s in libbe.bug.status_values]) + active_statuses = [] + for status in libbe.bug.active_status_values : + description = libbe.bug.status_description[status] + s = '%*s : %s' % (longest_status_len, status, description) + active_statuses.append(s) + inactive_statuses = [] + for status in libbe.bug.inactive_status_values : + description = libbe.bug.status_description[status] + s = '%*s : %s' % (longest_status_len, status, description) + inactive_statuses.append(s) + ret = """ +Show or change a bug's status. + +If no status is specified, the current value is printed. If a status +is specified, it will be assigned to the bug. + +There are two classes of statuses, active and inactive, which are only +important for commands like "be list" that show only active bugs by +default. + +Active status levels are: + %s +Inactive status levels are: + %s + +You can overide the list of allowed statuses on a per-repository basis. +See "be set --help" for more details. +""" % ('\n '.join(active_statuses), '\n '.join(inactive_statuses)) + return ret diff --git a/libbe/command/subscribe.py b/libbe/command/subscribe.py new file mode 100644 index 0000000..d1cf72e --- /dev/null +++ b/libbe/command/subscribe.py @@ -0,0 +1,385 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import copy +import os + +import libbe +import libbe.bug +import libbe.command +import libbe.diff +import libbe.command.util +import libbe.util.id +import libbe.util.tree + + +TAG="SUBSCRIBE:" + + +class Subscribe (libbe.command.Command): + """(Un)subscribe to change notification + + >>> import sys + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_bugdir(bd) + >>> cmd = Subscribe(ui=ui) + + >>> a = bd.bug_from_uuid('a') + >>> print a.extra_strings + [] + >>> ret = ui.run(cmd, {'subscriber':'John Doe <j@doe.com>'}, ['/a']) # doctest: +NORMALIZE_WHITESPACE + Subscriptions for abc/a: + John Doe <j@doe.com> all * + >>> bd.flush_reload() + >>> a = bd.bug_from_uuid('a') + >>> print a.extra_strings + ['SUBSCRIBE:John Doe <j@doe.com>\\tall\\t*'] + >>> ret = ui.run(cmd, {'subscriber':'Jane Doe <J@doe.com>', 'servers':'a.com,b.net'}, ['/a']) # doctest: +NORMALIZE_WHITESPACE + Subscriptions for abc/a: + Jane Doe <J@doe.com> all a.com,b.net + John Doe <j@doe.com> all * + >>> ret = ui.run(cmd, {'subscriber':'Jane Doe <J@doe.com>', 'servers':'a.edu'}, ['/a']) # doctest: +NORMALIZE_WHITESPACE + Subscriptions for abc/a: + Jane Doe <J@doe.com> all a.com,a.edu,b.net + John Doe <j@doe.com> all * + >>> ret = ui.run(cmd, {'unsubscribe':True, 'subscriber':'Jane Doe <J@doe.com>', 'servers':'a.com'}, ['/a']) # doctest: +NORMALIZE_WHITESPACE + Subscriptions for abc/a: + Jane Doe <J@doe.com> all a.edu,b.net + John Doe <j@doe.com> all * + >>> ret = ui.run(cmd, {'subscriber':'Jane Doe <J@doe.com>', 'servers':'*'}, ['/a']) # doctest: +NORMALIZE_WHITESPACE + Subscriptions for abc/a: + Jane Doe <J@doe.com> all * + John Doe <j@doe.com> all * + >>> ret = ui.run(cmd, {'unsubscribe':True, 'subscriber':'Jane Doe <J@doe.com>'}, ['/a']) # doctest: +NORMALIZE_WHITESPACE + Subscriptions for abc/a: + John Doe <j@doe.com> all * + >>> ret = ui.run(cmd, {'unsubscribe':True, 'subscriber':'John Doe <j@doe.com>'}, ['/a']) + >>> ret = ui.run(cmd, {'subscriber':'Jane Doe <J@doe.com>', 'types':'new'}, ['DIR']) # doctest: +NORMALIZE_WHITESPACE + Subscriptions for bug directory: + Jane Doe <J@doe.com> new * + >>> ret = ui.run(cmd, {'subscriber':'Jane Doe <J@doe.com>'}, ['DIR']) # doctest: +NORMALIZE_WHITESPACE + Subscriptions for bug directory: + Jane Doe <J@doe.com> all * + >>> ui.cleanup() + >>> bd.cleanup() + """ + name = 'subscribe' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='unsubscribe', short_name='u', + help='Unsubscribe instead of subscribing'), + libbe.command.Option(name='list-all', short_name='a', + help='List all subscribers (no ID argument, read only action)'), + libbe.command.Option(name='list', short_name='l', + help='List subscribers (read only action).'), + libbe.command.Option(name='subscriber', short_name='s', + help='Email address of the subscriber (defaults to your user id).', + arg=libbe.command.Argument( + name='subscriber', metavar='EMAIL')), + libbe.command.Option(name='servers', short_name='S', + help='Servers from which you want notification.', + arg=libbe.command.Argument( + name='servers', metavar='STRING')), + libbe.command.Option(name='types', short_name='t', + help='Types of changes you wish to be notified about.', + arg=libbe.command.Argument( + name='types', metavar='STRING')), + ]) + self.args.extend([ + libbe.command.Argument( + name='id', metavar='ID', default=tuple(), + optional=True, repeatable=True, + completion_callback=libbe.command.util.complete_bug_comment_id), + ]) + + def _run(self, **params): + bugdir = self._get_bugdir() + if params['list-all'] == True or params['list'] == True: + writeable = bugdir.storage.writeable + bugdir.storage.writeable = False + if params['list-all'] == True: + assert len(params['id']) == 0, params['id'] + subscriber = params['subscriber'] + if subscriber == None: + subscriber = self._get_user_id() + if params['unsubscribe'] == True: + if params['servers'] == None: + params['servers'] = 'INVALID' + if params['types'] == None: + params['types'] = 'INVALID' + else: + if params['servers'] == None: + params['servers'] = '*' + if params['types'] == None: + params['types'] = 'all' + servers = params['servers'].split(',') + types = params['types'].split(',') + + if len(params['id']) == 0: + params['id'] = [libbe.diff.BUGDIR_ID] + for _id in params['id']: + if _id == libbe.diff.BUGDIR_ID: # directory-wide subscriptions + type_root = libbe.diff.BUGDIR_TYPE_ALL + entity = bugdir + entity_name = 'bug directory' + else: # bug-specific subscriptions + type_root = libbe.diff.BUG_TYPE_ALL + bug,dummy_comment = libbe.command.util.bug_comment_from_user_id( + bugdir, _id) + entity = bug + entity_name = bug.id.user() + if params['list-all'] == True: + entity_name = 'anything in the bug directory' + types = [libbe.diff.type_from_name(name, type_root, default=libbe.diff.INVALID_TYPE, + default_ok=params['unsubscribe']) + for name in types] + estrs = entity.extra_strings + if params['list'] == True or params['list-all'] == True: + pass + else: # alter subscriptions + if params['unsubscribe'] == True: + estrs = unsubscribe(estrs, subscriber, types, servers, type_root) + else: # add the tag + estrs = subscribe(estrs, subscriber, types, servers, type_root) + entity.extra_strings = estrs # reassign to notice change + + if params['list-all'] == True: + bugdir.load_all_bugs() + subscriptions = get_bugdir_subscribers(bugdir, servers[0]) + else: + subscriptions = [] + for estr in entity.extra_strings: + if estr.startswith(TAG): + subscriptions.append(estr[len(TAG):]) + + if len(subscriptions) > 0: + print >> self.stdout, 'Subscriptions for %s:' % entity_name + print >> self.stdout, '\n'.join(subscriptions) + if params['list-all'] == True or params['list'] == True: + bugdir.storage.writeable = writeable + return 0 + + def _long_help(self): + return """ +ID can be either a bug id, or blank/"DIR", in which case it refers to the +whole bug directory. + +SERVERS specifies the servers from which you would like to receive +notification. Multiple severs may be specified in a comma-separated +list, or you can use "*" to match all servers (the default). If you +have not selected a server, it should politely refrain from notifying +you of changes, although there is no way to guarantee this behavior. + +Available TYPES: + For bugs: +%s + For %s: +%s + +For unsubscription, any listed SERVERS and TYPES are removed from your +subscription. Either the catch-all server "*" or type "%s" will +remove SUBSCRIBER entirely from the specified ID. + +This command is intended for use primarily by public interfaces, since +if you're just hacking away on your private repository, you'll known +what's changed ;). This command just (un)sets the appropriate +subscriptions, and leaves it up to each interface to perform the +notification. +""" % (libbe.diff.BUG_TYPE_ALL.string_tree(6), libbe.diff.BUGDIR_ID, + libbe.diff.BUGDIR_TYPE_ALL.string_tree(6), + libbe.diff.BUGDIR_TYPE_ALL) + + +# internal helper functions + +def _generate_string(subscriber, types, servers): + types = sorted([str(t) for t in types]) + servers = sorted(servers) + return "%s%s\t%s\t%s" % (TAG,subscriber,",".join(types),",".join(servers)) + +def _parse_string(string, type_root): + assert string.startswith(TAG), string + string = string[len(TAG):] + subscriber,types,servers = string.split("\t") + types = [libbe.diff.type_from_name(name, type_root) for name in types.split(",")] + return (subscriber,types,servers.split(",")) + +def _get_subscriber(extra_strings, subscriber, type_root): + for i,string in enumerate(extra_strings): + if string.startswith(TAG): + s,ts,srvs = _parse_string(string, type_root) + if s == subscriber: + return i,s,ts,srvs # match! + return None # no match + +# functions exposed to other modules + +def subscribe(extra_strings, subscriber, types, servers, type_root): + args = _get_subscriber(extra_strings, subscriber, type_root) + if args == None: # no match + extra_strings.append(_generate_string(subscriber, types, servers)) + return extra_strings + # Alter matched string + i,s,ts,srvs = args + for t in types: + if t not in ts: + ts.append(t) + # remove descendant types + all_ts = copy.copy(ts) + for t in all_ts: + for tt in all_ts: + if tt in ts and t.has_descendant(tt): + ts.remove(tt) + if "*" in servers+srvs: + srvs = ["*"] + else: + srvs = list(set(servers+srvs)) + extra_strings[i] = _generate_string(subscriber, ts, srvs) + return extra_strings + +def unsubscribe(extra_strings, subscriber, types, servers, type_root): + args = _get_subscriber(extra_strings, subscriber, type_root) + if args == None: # no match + return extra_strings # pass + # Remove matched string + i,s,ts,srvs = args + all_ts = copy.copy(ts) + for t in types: + for tt in all_ts: + if tt in ts and t.has_descendant(tt): + ts.remove(tt) + if "*" in servers+srvs: + srvs = [] + else: + for srv in servers: + if srv in srvs: + srvs.remove(srv) + if len(ts) == 0 or len(srvs) == 0: + extra_strings.pop(i) + else: + extra_strings[i] = _generate_string(subscriber, ts, srvs) + return extra_strings + +def get_subscribers(extra_strings, type, server, type_root, + match_ancestor_types=False, + match_descendant_types=False): + """ + Set match_ancestor_types=True if you want to find eveyone who + cares about your particular type. + + Set match_descendant_types=True if you want to find subscribers + who may only care about some subset of your type. This is useful + for generating lists of all the subscribers in a given set of + extra_strings. + + >>> def sgs(*args, **kwargs): + ... return sorted(get_subscribers(*args, **kwargs)) + >>> es = [] + >>> es = subscribe(es, "John Doe <j@doe.com>", [libbe.diff.BUGDIR_TYPE_ALL], + ... ["a.com"], libbe.diff.BUGDIR_TYPE_ALL) + >>> es = subscribe(es, "Jane Doe <J@doe.com>", [libbe.diff.BUGDIR_TYPE_NEW], + ... ["*"], libbe.diff.BUGDIR_TYPE_ALL) + >>> sgs(es, libbe.diff.BUGDIR_TYPE_ALL, "a.com", libbe.diff.BUGDIR_TYPE_ALL) + ['John Doe <j@doe.com>'] + >>> sgs(es, libbe.diff.BUGDIR_TYPE_ALL, "a.com", libbe.diff.BUGDIR_TYPE_ALL, + ... match_descendant_types=True) + ['Jane Doe <J@doe.com>', 'John Doe <j@doe.com>'] + >>> sgs(es, libbe.diff.BUGDIR_TYPE_ALL, "b.net", libbe.diff.BUGDIR_TYPE_ALL, + ... match_descendant_types=True) + ['Jane Doe <J@doe.com>'] + >>> sgs(es, libbe.diff.BUGDIR_TYPE_NEW, "a.com", libbe.diff.BUGDIR_TYPE_ALL) + ['Jane Doe <J@doe.com>'] + >>> sgs(es, libbe.diff.BUGDIR_TYPE_NEW, "a.com", libbe.diff.BUGDIR_TYPE_ALL, + ... match_ancestor_types=True) + ['Jane Doe <J@doe.com>', 'John Doe <j@doe.com>'] + """ + for string in extra_strings: + if not string.startswith(TAG): + continue + subscriber,types,servers = _parse_string(string, type_root) + type_match = False + if type in types: + type_match = True + if type_match == False and match_ancestor_types == True: + for t in types: + if t.has_descendant(type): + type_match = True + break + if type_match == False and match_descendant_types == True: + for t in types: + if type.has_descendant(t): + type_match = True + break + server_match = False + if server in servers or servers == ["*"] or server == "*": + server_match = True + if type_match == True and server_match == True: + yield subscriber + +def get_bugdir_subscribers(bugdir, server): + """ + I have a bugdir. Who cares about it, and what do they care about? + Returns a dict of dicts: + subscribers[user][id] = types + where id is either a bug.uuid (in the case of a bug subscription) + or "%(bugdir_id)s" (in the case of a bugdir subscription). + + Only checks bugs that are currently in memory, so you might want + to call bugdir.load_all_bugs() first. + + >>> bd = bugdir.SimpleBugDir(sync_with_disk=False) + >>> a = bd.bug_from_shortname("a") + >>> bd.extra_strings = subscribe(bd.extra_strings, "John Doe <j@doe.com>", + ... [libbe.diff.BUGDIR_TYPE_ALL], ["a.com"], libbe.diff.BUGDIR_TYPE_ALL) + >>> bd.extra_strings = subscribe(bd.extra_strings, "Jane Doe <J@doe.com>", + ... [libbe.diff.BUGDIR_TYPE_NEW], ["*"], libbe.diff.BUGDIR_TYPE_ALL) + >>> a.extra_strings = subscribe(a.extra_strings, "John Doe <j@doe.com>", + ... [libbe.diff.BUG_TYPE_ALL], ["a.com"], libbe.diff.BUG_TYPE_ALL) + >>> subscribers = get_bugdir_subscribers(bd, "a.com") + >>> subscribers["Jane Doe <J@doe.com>"]["%(bugdir_id)s"] + [<SubscriptionType: new>] + >>> subscribers["John Doe <j@doe.com>"]["%(bugdir_id)s"] + [<SubscriptionType: all>] + >>> subscribers["John Doe <j@doe.com>"]["a"] + [<SubscriptionType: all>] + >>> get_bugdir_subscribers(bd, "b.net") + {'Jane Doe <J@doe.com>': {'%(bugdir_id)s': [<SubscriptionType: new>]}} + >>> bd.cleanup() + """ % {'bugdir_id':libbe.diff.BUGDIR_ID} + subscribers = {} + for sub in get_subscribers(bugdir.extra_strings, libbe.diff.BUGDIR_TYPE_ALL, + server, libbe.diff.BUGDIR_TYPE_ALL, + match_descendant_types=True): + i,s,ts,srvs = _get_subscriber(bugdir.extra_strings, sub, + libbe.diff.BUGDIR_TYPE_ALL) + subscribers[sub] = {"DIR":ts} + for bug in bugdir: + for sub in get_subscribers(bug.extra_strings, libbe.diff.BUG_TYPE_ALL, + server, libbe.diff.BUG_TYPE_ALL, + match_descendant_types=True): + i,s,ts,srvs = _get_subscriber(bug.extra_strings, sub, + libbe.diff.BUG_TYPE_ALL) + if sub in subscribers: + subscribers[sub][bug.uuid] = ts + else: + subscribers[sub] = {bug.uuid:ts} + return subscribers diff --git a/libbe/command/tag.py b/libbe/command/tag.py new file mode 100644 index 0000000..f4dc3ba --- /dev/null +++ b/libbe/command/tag.py @@ -0,0 +1,152 @@ +# Copyright (C) 2009-2010 Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import libbe +import libbe.command +import libbe.command.util + + +TAG_TAG = 'TAG:' + + +class Tag (libbe.command.Command): + __doc__ = """Tag a bug, or search bugs for tags + + >>> import sys + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_bugdir(bd) + >>> cmd = Tag(ui=ui) + + >>> a = bd.bug_from_uuid('a') + >>> print a.extra_strings + [] + >>> ret = ui.run(cmd, args=['/a', 'GUI']) + Tags for abc/a: + GUI + >>> bd.flush_reload() + >>> a = bd.bug_from_uuid('a') + >>> print a.extra_strings + ['%(tag_tag)sGUI'] + >>> ret = ui.run(cmd, args=['/a', 'later']) + Tags for abc/a: + GUI + later + >>> ret = ui.run(cmd, args=['/a']) + Tags for abc/a: + GUI + later + >>> ret = ui.run(cmd, {'list':True}) + GUI + later + >>> ret = ui.run(cmd, args=['/a', 'Alphabetically first']) + Tags for abc/a: + Alphabetically first + GUI + later + >>> bd.flush_reload() + >>> a = bd.bug_from_uuid('a') + >>> print a.extra_strings + ['%(tag_tag)sAlphabetically first', '%(tag_tag)sGUI', '%(tag_tag)slater'] + >>> a.extra_strings = [] + >>> print a.extra_strings + [] + >>> ret = ui.run(cmd, args=['/a']) + >>> bd.flush_reload() + >>> a = bd.bug_from_uuid('a') + >>> print a.extra_strings + [] + >>> ret = ui.run(cmd, args=['/a', 'Alphabetically first']) + Tags for abc/a: + Alphabetically first + >>> ret = ui.run(cmd, {'remove':True}, ['/a', 'Alphabetically first']) + >>> ui.cleanup() + >>> bd.cleanup() + """ % {'tag_tag':TAG_TAG} + name = 'tag' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='remove', short_name='r', + help='Remove TAG (instead of adding it)'), + libbe.command.Option(name='list', short_name='l', + help='List all available tags and exit'), + ]) + self.args.extend([ + libbe.command.Argument( + name='id', metavar='BUG-ID', optional=True, + completion_callback=libbe.command.util.complete_bug_id), + libbe.command.Argument( + name='tag', metavar='TAG', default=tuple(), + optional=True, repeatable=True), + ]) + + def _run(self, **params): + if params['id'] == None and params['list'] == False: + raise libbe.command.UserError('Please specify a bug id.') + if params['id'] != None and params['list'] == True: + raise libbe.command.UserError( + 'Do not specify a bug id with the --list option.') + bugdir = self._get_bugdir() + if params['list'] == True: + bugdir.load_all_bugs() + tags = [] + for bug in bugdir: + for estr in bug.extra_strings: + if estr.startswith(TAG_TAG): + tag = estr[len(TAG_TAG):] + if tag not in tags: + tags.append(tag) + tags.sort() + if len(tags) > 0: + print >> self.stdout, '\n'.join(tags) + return 0 + + bug,dummy_comment = libbe.command.util.bug_comment_from_user_id( + bugdir, params['id']) + if len(params['tag']) > 0: + estrs = bug.extra_strings + for tag in params['tag']: + tag_string = '%s%s' % (TAG_TAG, tag) + if params['remove'] == True: + estrs.remove(tag_string) + else: # add the tag + estrs.append(tag_string) + bug.extra_strings = estrs # reassign to notice change + + tags = [] + for estr in bug.extra_strings: + if estr.startswith(TAG_TAG): + tags.append(estr[len(TAG_TAG):]) + + if len(tags) > 0: + print "Tags for %s:" % bug.id.user() + print '\n'.join(tags) + return 0 + + def _long_help(self): + return """ +If TAG is given, add TAG to BUG-ID. If it is not specified, just +print the tags for BUG-ID. + +To search for bugs with a particular tag, try + $ be list --extra-strings %s<your-tag> +""" % TAG_TAG diff --git a/libbe/command/target.py b/libbe/command/target.py new file mode 100644 index 0000000..f8a956b --- /dev/null +++ b/libbe/command/target.py @@ -0,0 +1,209 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Chris Ball <cjb@laptop.org> +# Gianluca Montecchi <gian@grys.it> +# Marien Zwart <marienz@gentoo.org> +# Thomas Gerigk <tgerigk@gmx.de> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import libbe +import libbe.command +import libbe.command.util +import libbe.command.depend + + +class Target (libbe.command.Command): + """Assorted bug target manipulations and queries + + >>> import os, StringIO, sys + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir(memory=False) + >>> io = libbe.command.StringInputOutput() + >>> io.stdout = sys.stdout + >>> ui = libbe.command.UserInterface(io=io) + >>> ui.storage_callbacks.set_storage(bd.storage) + >>> cmd = Target(ui=ui) + + >>> ret = ui.run(cmd, args=['/a']) + No target assigned. + >>> ret = ui.run(cmd, args=['/a', 'tomorrow']) + >>> ret = ui.run(cmd, args=['/a']) + tomorrow + + >>> ui.io.stdout = StringIO.StringIO() + >>> ret = ui.run(cmd, {'resolve':True}, ['tomorrow']) + >>> output = ui.io.get_stdout().strip() + >>> bd.flush_reload() + >>> target = bd.bug_from_uuid(output) + >>> print target.summary + tomorrow + >>> print target.severity + target + + >>> ui.io.stdout = sys.stdout + >>> ret = ui.run(cmd, args=['/a', 'none']) + >>> ret = ui.run(cmd, args=['/a']) + No target assigned. + >>> ui.cleanup() + >>> bd.cleanup() + """ + name = 'target' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='resolve', short_name='r', + help="Print the UUID for the target bug whose summary " + "matches TARGET. If TARGET is not given, print the UUID " + "of the current bugdir target."), + ]) + self.args.extend([ + libbe.command.Argument( + name='id', metavar='BUG-ID', optional=True, + completion_callback=libbe.command.util.complete_bug_id), + libbe.command.Argument( + name='target', metavar='TARGET', optional=True, + completion_callback=complete_target), + ]) + + def _run(self, **params): + if params['resolve'] == False: + if params['id'] == None: + raise libbe.command.UserError('Please specify a bug id.') + else: + if params['target'] != None: + raise libbe.command.UserError('Too many arguments') + params['target'] = params.pop('id') + bugdir = self._get_bugdir() + if params['resolve'] == True: + bug = bug_from_target_summary(bugdir, params['target']) + if bug == None: + print >> self.stdout, 'No target assigned.' + else: + print >> self.stdout, bug.uuid + return 0 + bug,dummy_comment = libbe.command.util.bug_comment_from_user_id( + bugdir, params['id']) + if params['target'] == None: + target = bug_target(bugdir, bug) + if target == None: + print >> self.stdout, 'No target assigned.' + else: + print >> self.stdout, target.summary + else: + if params['target'] == 'none': + target = remove_target(bugdir, bug) + else: + target = add_target(bugdir, bug, params['target']) + return 0 + + def usage(self): + return 'usage: be %(name)s BUG-ID [TARGET]\nor: be %(name)s --resolve [TARGET]' \ + % vars(self.__class__) + + def _long_help(self): + return """ +Assorted bug target manipulations and queries. + +If no target is specified, the bug's current target is printed. If +TARGET is specified, it will be assigned to the bug, creating a new +target bug if necessary. + +Targets are free-form; any text may be specified. They will generally +be milestone names or release numbers. The value "none" can be used +to unset the target. + +In the alternative `be target --resolve TARGET` form, print the UUID +of the target-bug with summary TARGET. If target is not given, return +use the bugdir's current target (see `be set`). + +If you want to list all bugs blocking the current target, try + $ be depend --status -closed,fixed,wontfix --severity -target \ + $(be target --resolve) + +If you want to set the current bugdir target by summary (rather than +by UUID), try + $ be set target $(be target --resolve SUMMARY) +""" + +def bug_from_target_summary(bugdir, summary=None): + if summary == None: + if bugdir.target == None: + return None + else: + return bugdir.bug_from_uuid(bugdir.target) + matched = [] + for uuid in bugdir.uuids(): + bug = bugdir.bug_from_uuid(uuid) + if bug.severity == 'target' and bug.summary == summary: + matched.append(bug) + if len(matched) == 0: + return None + if len(matched) > 1: + raise Exception('Several targets with same summary: %s' + % '\n '.join([bug.uuid for bug in matched])) + return matched[0] + +def bug_target(bugdir, bug): + if bug.severity == 'target': + return bug + matched = [] + for blocked in libbe.command.depend.get_blocks(bugdir, bug): + if blocked.severity == 'target': + matched.append(blocked) + if len(matched) == 0: + return None + if len(matched) > 1: + raise Exception('This bug (%s) blocks several targets: %s' + % (bug.uuid, + '\n '.join([b.uuid for b in matched]))) + return matched[0] + +def remove_target(bugdir, bug): + target = bug_target(bugdir, bug) + libbe.command.depend.remove_block(target, bug) + return target + +def add_target(bugdir, bug, summary): + target = bug_from_target_summary(bugdir, summary) + if target == None: + target = bugdir.new_bug(summary=summary) + target.severity = 'target' + libbe.command.depend.add_block(target, bug) + return target + +def targets(bugdir): + """Generate all possible target bug summaries.""" + bugdir.load_all_bugs() + for bug in bugdir: + if bug.severity == 'target': + yield bug.summary + +def target_dict(bugdir): + """ + Return a dict with bug UUID keys and bug summary values for all + target bugs. + """ + ret = {} + bugdir.load_all_bugs() + for bug in bugdir: + if bug.severity == 'target': + ret[bug.uuid] = bug.summary + return ret + +def complete_target(command, argument, fragment=None): + """List possible command completions for fragment.""" + return targets(command._get_bugdir()) diff --git a/libbe/command/util.py b/libbe/command/util.py new file mode 100644 index 0000000..6e8e36c --- /dev/null +++ b/libbe/command/util.py @@ -0,0 +1,203 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import glob +import os.path + +import libbe +import libbe.command + +class Completer (object): + def __init__(self, options): + self.options = options + def __call__(self, bugdir, fragment=None): + return [fragment] + +def complete_command(command, argument, fragment=None): + """ + List possible command completions for fragment. + + command argument is not used. + """ + return list(libbe.command.commands(command_names=True)) + +def comp_path(fragment=None): + """List possible path completions for fragment.""" + if fragment == None: + fragment = '.' + comps = glob.glob(fragment+'*') + glob.glob(fragment+'/*') + if len(comps) == 1 and os.path.isdir(comps[0]): + comps.extend(glob.glob(comps[0]+'/*')) + return comps + +def complete_path(command, argument, fragment=None): + """List possible path completions for fragment.""" + return comp_path(fragment) + +def complete_status(command, argument, fragment=None): + bd = command._get_bugdir() + import libbe.bug + return libbe.bug.status_values + +def complete_severity(command, argument, fragment=None): + bd = command._get_bugdir() + import libbe.bug + return libbe.bug.severity_values + +def assignees(bugdir): + bugdir.load_all_bugs() + return list(set([bug.assigned for bug in bugdir + if bug.assigned != None])) + +def complete_assigned(command, argument, fragment=None): + return assignees(command._get_bugdir()) + +def complete_extra_strings(command, argument, fragment=None): + if fragment == None: + return [] + return [fragment] + +def complete_bug_id(command, argument, fragment=None): + return complete_bug_comment_id(command, argument, fragment, + comments=False) + +def complete_bug_comment_id(command, argument, fragment=None, + active_only=True, comments=True): + import libbe.bugdir + import libbe.util.id + bd = command._get_bugdir() + if fragment == None or len(fragment) == 0: + fragment = '/' + try: + p = libbe.util.id.parse_user(bd, fragment) + matches = None + root,residual = (fragment, None) + if not root.endswith('/'): + root += '/' + except libbe.util.id.InvalidIDStructure, e: + return [] + except libbe.util.id.NoIDMatches: + return [] + except libbe.util.id.MultipleIDMatches, e: + if e.common == None: + # choose among bugdirs + return e.matches + common = e.common + matches = e.matches + root,residual = libbe.util.id.residual(common, fragment) + p = libbe.util.id.parse_user(bd, e.common) + bug = None + if matches == None: # fragment was complete, get a list of children uuids + if p['type'] == 'bugdir': + matches = bd.uuids() + common = bd.id.user() + elif p['type'] == 'bug': + if comments == False: + return [fragment] + bug = bd.bug_from_uuid(p['bug']) + matches = bug.uuids() + common = bug.id.user() + else: + assert p['type'] == 'comment', p + return [fragment] + if p['type'] == 'bugdir': + child_fn = bd.bug_from_uuid + elif p['type'] == 'bug': + if comments == False: + return[fragment] + if bug == None: + bug = bd.bug_from_uuid(p['bug']) + child_fn = bug.comment_from_uuid + elif p['type'] == 'comment': + assert matches == None, matches + return [fragment] + possible = [] + common += '/' + for m in matches: + child = child_fn(m) + id = child.id.user() + possible.append(id.replace(common, root)) + return possible + +def select_values(string, possible_values, name="unkown"): + """ + This function allows the user to select values from a list of + possible values. The default is to select all the values: + + >>> select_values(None, ['abc', 'def', 'hij']) + ['abc', 'def', 'hij'] + + The user selects values with a comma-separated limit_string. + Prepending a minus sign to such a list denotes blacklist mode: + + >>> select_values('-abc,hij', ['abc', 'def', 'hij']) + ['def'] + + Without the leading -, the selection is in whitelist mode: + + >>> select_values('abc,hij', ['abc', 'def', 'hij']) + ['abc', 'hij'] + + In either case, appropriate errors are raised if on of the + user-values is not in the list of possible values. The name + parameter lets you make the error message more clear: + + >>> select_values('-xyz,hij', ['abc', 'def', 'hij'], name="foobar") + Traceback (most recent call last): + ... + UserError: Invalid foobar xyz + ['abc', 'def', 'hij'] + >>> select_values('xyz,hij', ['abc', 'def', 'hij'], name="foobar") + Traceback (most recent call last): + ... + UserError: Invalid foobar xyz + ['abc', 'def', 'hij'] + """ + possible_values = list(possible_values) # don't alter the original + if string == None: + pass + elif string.startswith('-'): + blacklisted_values = set(string[1:].split(',')) + for value in blacklisted_values: + if value not in possible_values: + raise libbe.command.UserError('Invalid %s %s\n %s' + % (name, value, possible_values)) + possible_values.remove(value) + else: + whitelisted_values = string.split(',') + for value in whitelisted_values: + if value not in possible_values: + raise libbe.command.UserError( + 'Invalid %s %s\n %s' + % (name, value, possible_values)) + possible_values = whitelisted_values + return possible_values + +def bug_comment_from_user_id(bugdir, id): + p = libbe.util.id.parse_user(bugdir, id) + if not p['type'] in ['bug', 'comment']: + raise libbe.command.UserError( + '%s is a %s id, not a bug or comment id' % (id, p['type'])) + if p['bugdir'] != bugdir.uuid: + raise libbe.command.UserError( + "%s doesn't belong to this bugdir (%s)" + % (id, bugdir.uuid)) + bug = bugdir.bug_from_uuid(p['bug']) + if 'comment' in p: + comment = bug.comment_from_uuid(p['comment']) + else: + comment = bug.comment_root + return (bug, comment) diff --git a/libbe/comment.py b/libbe/comment.py new file mode 100644 index 0000000..d8632a4 --- /dev/null +++ b/libbe/comment.py @@ -0,0 +1,769 @@ +# Copyright (C) 2008-2010 Gianluca Montecchi <gian@grys.it> +# Thomas Habets <thomas@habets.pp.se> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Define the :class:`Comment` class for representing bug comments. +""" + +import base64 +import os +import os.path +import sys +import time +import types +try: + from email.mime.base import MIMEBase + from email.encoders import encode_base64 +except ImportError: + # adjust to old python 2.4 + from email.MIMEBase import MIMEBase + from email.Encoders import encode_base64 +try: # import core module, Python >= 2.5 + from xml.etree import ElementTree +except ImportError: # look for non-core module + from elementtree import ElementTree +import xml.sax.saxutils + +import libbe +import libbe.util.id +from libbe.storage.util.properties import Property, doc_property, \ + local_property, defaulting_property, checked_property, cached_property, \ + primed_property, change_hook_property, settings_property +import libbe.storage.util.settings_object as settings_object +import libbe.storage.util.mapfile as mapfile +from libbe.util.tree import Tree +import libbe.util.utility as utility + +if libbe.TESTING == True: + import doctest + + +class InvalidShortname(KeyError): + def __init__(self, shortname, shortnames): + msg = "Invalid shortname %s\n%s" % (shortname, shortnames) + KeyError.__init__(self, msg) + self.shortname = shortname + self.shortnames = shortnames + +class MissingReference(ValueError): + def __init__(self, comment): + msg = "Missing reference to %s" % (comment.in_reply_to) + ValueError.__init__(self, msg) + self.reference = comment.in_reply_to + self.comment = comment + +class DiskAccessRequired (Exception): + def __init__(self, goal): + msg = "Cannot %s without accessing the disk" % goal + Exception.__init__(self, msg) + +INVALID_UUID = "!!~~\n INVALID-UUID \n~~!!" + +def load_comments(bug, load_full=False): + """ + Set load_full=True when you want to load the comment completely + from disk *now*, rather than waiting and lazy loading as required. + """ + uuids = [] + for id in libbe.util.id.child_uuids( + bug.storage.children( + bug.id.storage())): + uuids.append(id) + comments = [] + for uuid in uuids: + comm = Comment(bug, uuid, from_storage=True) + if load_full == True: + comm.load_settings() + dummy = comm.body # force the body to load + comments.append(comm) + bug.comment_root = Comment(bug, uuid=INVALID_UUID) + bug.add_comments(comments, ignore_missing_references=True) + return bug.comment_root + +def save_comments(bug): + for comment in bug.comment_root.traverse(): + comment.save() + + +class Comment (Tree, settings_object.SavedSettingsObject): + """Comments are a notes that attach to :class:`~libbe.bug.Bug`\s in + threaded trees. In mailing-list terms, a comment is analogous to + a single part of an email. + + >>> c = Comment() + >>> c.uuid != None + True + >>> c.uuid = "some-UUID" + >>> print c.content_type + text/plain + """ + + settings_properties = [] + required_saved_properties = [] + _prop_save_settings = settings_object.prop_save_settings + _prop_load_settings = settings_object.prop_load_settings + def _versioned_property(settings_properties=settings_properties, + required_saved_properties=required_saved_properties, + **kwargs): + if "settings_properties" not in kwargs: + kwargs["settings_properties"] = settings_properties + if "required_saved_properties" not in kwargs: + kwargs["required_saved_properties"]=required_saved_properties + return settings_object.versioned_property(**kwargs) + + @_versioned_property(name="Alt-id", + doc="Alternate ID for linking imported comments. Internally comments are linked (via In-reply-to) to the parent's UUID. However, these UUIDs are generated internally, so Alt-id is provided as a user-controlled linking target.") + def alt_id(): return {} + + @_versioned_property(name="Author", + doc="The author of the comment") + def author(): return {} + + @_versioned_property(name="In-reply-to", + doc="UUID for parent comment or bug") + def in_reply_to(): return {} + + @_versioned_property(name="Content-type", + doc="Mime type for comment body", + default="text/plain", + require_save=True) + def content_type(): return {} + + @_versioned_property(name="Date", + doc="An RFC 2822 timestamp for comment creation") + def date(): return {} + + def _get_time(self): + if self.date == None: + return None + return utility.str_to_time(self.date) + def _set_time(self, value): + self.date = utility.time_to_str(value) + time = property(fget=_get_time, + fset=_set_time, + doc="An integer version of .date") + + def _get_comment_body(self): + if self.storage != None and self.storage.is_readable() \ + and self.uuid != INVALID_UUID: + return self.storage.get(self.id.storage("body"), + decode=self.content_type.startswith("text/")) + def _set_comment_body(self, old=None, new=None, force=False): + assert self.uuid != INVALID_UUID, self + if self.content_type.startswith('text/') \ + and self.bug != None and self.bug.bugdir != None: + new = libbe.util.id.short_to_long_text([self.bug.bugdir], new) + if (self.storage != None and self.storage.writeable == True) \ + or force==True: + assert new != None, "Can't save empty comment" + self.storage.set(self.id.storage("body"), new) + + @Property + @change_hook_property(hook=_set_comment_body) + @cached_property(generator=_get_comment_body) + @local_property("body") + @doc_property(doc="The meat of the comment") + def body(): return {} + + def _extra_strings_check_fn(value): + return utility.iterable_full_of_strings(value, \ + alternative=settings_object.EMPTY) + def _extra_strings_change_hook(self, old, new): + self.extra_strings.sort() # to make merging easier + self._prop_save_settings(old, new) + @_versioned_property(name="extra_strings", + doc="Space for an array of extra strings. Useful for storing state for functionality implemented purely in becommands/<some_function>.py.", + default=[], + check_fn=_extra_strings_check_fn, + change_hook=_extra_strings_change_hook, + mutable=True) + def extra_strings(): return {} + + def __init__(self, bug=None, uuid=None, from_storage=False, + in_reply_to=None, body=None, content_type=None): + """ + Set ``from_storage=True`` to load an old comment. + Set ``from_storage=False`` to create a new comment. + + The ``uuid`` option is required when ``from_storage==True``. + + The in_reply_to, body, and content_type options are only used + if ``from_storage==False`` (the default). When + ``from_storage==True``, they are loaded from the bug database. + ``content_type`` decides if the body should be run through + :func:`util.id.short_to_long_text` before saving. See + :meth:`_set_comment_body` for details. + + ``in_reply_to`` should be the uuid string of the parent comment. + """ + Tree.__init__(self) + settings_object.SavedSettingsObject.__init__(self) + self.bug = bug + self.storage = None + self.uuid = uuid + self.id = libbe.util.id.ID(self, 'comment') + if from_storage == False: + if uuid == None: + self.uuid = libbe.util.id.uuid_gen() + self.time = int(time.time()) # only save to second precision + self.in_reply_to = in_reply_to + if content_type != None: + self.content_type = content_type + self.body = body + if self.bug != None: + self.storage = self.bug.storage + if from_storage == False: + if self.storage != None and self.storage.is_writeable(): + self.save() + + def __cmp__(self, other): + return cmp_full(self, other) + + def __str__(self): + """ + >>> comm = Comment(bug=None, body="Some insightful remarks") + >>> comm.uuid = "com-1" + >>> comm.date = "Thu, 20 Nov 2008 15:55:11 +0000" + >>> comm.author = "Jane Doe <jdoe@example.com>" + >>> print comm + --------- Comment --------- + Name: //com + From: Jane Doe <jdoe@example.com> + Date: Thu, 20 Nov 2008 15:55:11 +0000 + <BLANKLINE> + Some insightful remarks + """ + return self.string() + + def traverse(self, *args, **kwargs): + """Avoid working with the possible dummy root comment""" + for comment in Tree.traverse(self, *args, **kwargs): + if comment.uuid == INVALID_UUID: + continue + yield comment + + # serializing methods + + def _setting_attr_string(self, setting): + value = getattr(self, setting) + if value == None: + return "" + if type(value) not in types.StringTypes: + return str(value) + return value + + def safe_in_reply_to(self): + """ + Return self.in_reply_to, except... + + * if no comment matches that id, in which case return None. + * if that id matches another comments .alt_id, in which case + return the matching comments .uuid. + """ + if self.in_reply_to == None: + return None + else: + try: + irt_comment = self.bug.comment_from_uuid( + self.in_reply_to, match_alt_id=True) + return irt_comment.uuid + except KeyError: + return None + + def xml(self, indent=0): + """ + >>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") + >>> comm.uuid = "0123" + >>> comm.date = "Thu, 01 Jan 1970 00:00:00 +0000" + >>> print comm.xml(indent=2) + <comment> + <uuid>0123</uuid> + <short-name>//012</short-name> + <author></author> + <date>Thu, 01 Jan 1970 00:00:00 +0000</date> + <content-type>text/plain</content-type> + <body>Some + insightful + remarks</body> + </comment> + >>> comm.content_type = 'image/png' + >>> print comm.xml() + <comment> + <uuid>0123</uuid> + <short-name>//012</short-name> + <author></author> + <date>Thu, 01 Jan 1970 00:00:00 +0000</date> + <content-type>image/png</content-type> + <body>U29tZQppbnNpZ2h0ZnVsCnJlbWFya3MK + </body> + </comment> + """ + if self.content_type.startswith('text/'): + body = (self.body or '').rstrip('\n') + else: + maintype,subtype = self.content_type.split('/',1) + msg = MIMEBase(maintype, subtype) + msg.set_payload(self.body or '') + encode_base64(msg) + body = base64.encodestring(self.body or '') + info = [('uuid', self.uuid), + ('alt-id', self.alt_id), + ('short-name', self.id.user()), + ('in-reply-to', self.safe_in_reply_to()), + ('author', self._setting_attr_string('author')), + ('date', self.date), + ('content-type', self.content_type), + ('body', body)] + lines = ['<comment>'] + for (k,v) in info: + if v != None: + lines.append(' <%s>%s</%s>' % (k,xml.sax.saxutils.escape(v),k)) + for estr in self.extra_strings: + lines.append(' <extra-string>%s</extra-string>' % estr) + lines.append('</comment>') + istring = ' '*indent + sep = '\n' + istring + return istring + sep.join(lines).rstrip('\n') + + def from_xml(self, xml_string, verbose=True): + u""" + Note: If alt-id is not given, translates any <uuid> fields to + <alt-id> fields. + >>> commA = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") + >>> commA.uuid = "0123" + >>> commA.date = "Thu, 01 Jan 1970 00:00:00 +0000" + >>> commA.author = u'Fran\xe7ois' + >>> commA.extra_strings += ['TAG: very helpful'] + >>> xml = commA.xml() + >>> commB = Comment() + >>> commB.from_xml(xml, verbose=True) + >>> commB.explicit_attrs + ['author', 'date', 'content_type', 'body', 'alt_id'] + >>> commB.xml() == xml + False + >>> commB.uuid = commB.alt_id + >>> commB.alt_id = None + >>> commB.xml() == xml + True + """ + if type(xml_string) == types.UnicodeType: + xml_string = xml_string.strip().encode('unicode_escape') + if hasattr(xml_string, 'getchildren'): # already an ElementTree Element + comment = xml_string + else: + comment = ElementTree.XML(xml_string) + if comment.tag != 'comment': + raise utility.InvalidXML( \ + 'comment', comment, 'root element must be <comment>') + tags=['uuid','alt-id','in-reply-to','author','date','content-type', + 'body','extra-string'] + self.explicit_attrs = [] + uuid = None + body = None + estrs = [] + for child in comment.getchildren(): + if child.tag == 'short-name': + pass + elif child.tag in tags: + if child.text == None or len(child.text) == 0: + text = settings_object.EMPTY + else: + text = xml.sax.saxutils.unescape(child.text) + text = text.decode('unicode_escape').strip() + if child.tag == 'uuid': + uuid = text + continue # don't set the comment's uuid tag. + elif child.tag == 'body': + body = text + self.explicit_attrs.append(child.tag) + continue # don't set the comment's body yet. + elif child.tag == 'extra-string': + estrs.append(text) + continue # don't set the comment's extra_string yet. + attr_name = child.tag.replace('-','_') + self.explicit_attrs.append(attr_name) + setattr(self, attr_name, text) + elif verbose == True: + print >> sys.stderr, 'Ignoring unknown tag %s in %s' \ + % (child.tag, comment.tag) + if uuid != self.uuid and self.alt_id == None: + self.explicit_attrs.append('alt_id') + self.alt_id = uuid + if body != None: + if self.content_type.startswith('text/'): + self.body = body+'\n' # restore trailing newline + else: + self.body = base64.decodestring(body) + self.extra_strings = estrs + + def merge(self, other, accept_changes=True, + accept_extra_strings=True, change_exception=False): + """ + Merge info from other into this comment. Overrides any + attributes in self that are listed in other.explicit_attrs. + + >>> commA = Comment(bug=None, body='Some insightful remarks') + >>> commA.uuid = '0123' + >>> commA.date = 'Thu, 01 Jan 1970 00:00:00 +0000' + >>> commA.author = 'Frank' + >>> commA.extra_strings += ['TAG: very helpful'] + >>> commA.extra_strings += ['TAG: favorite'] + >>> commB = Comment(bug=None, body='More insightful remarks') + >>> commB.uuid = '3210' + >>> commB.date = 'Fri, 02 Jan 1970 00:00:00 +0000' + >>> commB.author = 'John' + >>> commB.explicit_attrs = ['author', 'body'] + >>> commB.extra_strings += ['TAG: very helpful'] + >>> commB.extra_strings += ['TAG: useful'] + >>> commA.merge(commB, accept_changes=False, + ... accept_extra_strings=False, change_exception=False) + >>> commA.merge(commB, accept_changes=False, + ... accept_extra_strings=False, change_exception=True) + Traceback (most recent call last): + ... + ValueError: Merge would change author "Frank"->"John" for comment 0123 + >>> commA.merge(commB, accept_changes=True, + ... accept_extra_strings=False, change_exception=True) + Traceback (most recent call last): + ... + ValueError: Merge would add extra string "TAG: useful" to comment 0123 + >>> print commA.author + John + >>> print commA.extra_strings + ['TAG: favorite', 'TAG: very helpful'] + >>> commA.merge(commB, accept_changes=True, + ... accept_extra_strings=True, change_exception=True) + >>> print commA.extra_strings + ['TAG: favorite', 'TAG: useful', 'TAG: very helpful'] + >>> print commA.xml() + <comment> + <uuid>0123</uuid> + <short-name>//012</short-name> + <author>John</author> + <date>Thu, 01 Jan 1970 00:00:00 +0000</date> + <content-type>text/plain</content-type> + <body>More insightful remarks</body> + <extra-string>TAG: favorite</extra-string> + <extra-string>TAG: useful</extra-string> + <extra-string>TAG: very helpful</extra-string> + </comment> + """ + for attr in other.explicit_attrs: + old = getattr(self, attr) + new = getattr(other, attr) + if old != new: + if accept_changes == True: + setattr(self, attr, new) + elif change_exception == True: + raise ValueError, \ + 'Merge would change %s "%s"->"%s" for comment %s' \ + % (attr, old, new, self.uuid) + if self.alt_id == self.uuid: + self.alt_id = None + for estr in other.extra_strings: + if not estr in self.extra_strings: + if accept_extra_strings == True: + self.extra_strings.append(estr) + elif change_exception == True: + raise ValueError, \ + 'Merge would add extra string "%s" to comment %s' \ + % (estr, self.uuid) + + def string(self, indent=0): + """ + >>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") + >>> comm.uuid = 'abcdef' + >>> comm.date = "Thu, 01 Jan 1970 00:00:00 +0000" + >>> print comm.string(indent=2) + --------- Comment --------- + Name: //abc + From: + Date: Thu, 01 Jan 1970 00:00:00 +0000 + <BLANKLINE> + Some + insightful + remarks + """ + lines = [] + lines.append("--------- Comment ---------") + lines.append("Name: %s" % self.id.user()) + lines.append("From: %s" % (self._setting_attr_string("author"))) + lines.append("Date: %s" % self.date) + lines.append("") + if self.content_type.startswith("text/"): + body = (self.body or "") + if self.bug != None and self.bug.bugdir != None: + body = libbe.util.id.long_to_short_text([self.bug.bugdir], body) + lines.extend(body.splitlines()) + else: + lines.append("Content type %s not printable. Try XML output instead" % self.content_type) + + istring = ' '*indent + sep = '\n' + istring + return istring + sep.join(lines).rstrip('\n') + + def string_thread(self, string_method_name="string", + indent=0, flatten=True): + """ + Return a string displaying a thread of comments. + bug_shortname is only used if auto_name_map == True. + + string_method_name (defaults to "string") is the name of the + Comment method used to generate the output string for each + Comment in the thread. The method must take the arguments + indent and shortname. + + >>> a = Comment(bug=None, uuid="a", body="Insightful remarks") + >>> a.time = utility.str_to_time("Thu, 20 Nov 2008 01:00:00 +0000") + >>> b = a.new_reply("Critique original comment") + >>> b.uuid = "b" + >>> b.time = utility.str_to_time("Thu, 20 Nov 2008 02:00:00 +0000") + >>> c = b.new_reply("Begin flamewar :p") + >>> c.uuid = "c" + >>> c.time = utility.str_to_time("Thu, 20 Nov 2008 03:00:00 +0000") + >>> d = a.new_reply("Useful examples") + >>> d.uuid = "d" + >>> d.time = utility.str_to_time("Thu, 20 Nov 2008 04:00:00 +0000") + >>> a.sort(key=lambda comm : comm.time) + >>> print a.string_thread(flatten=True) + --------- Comment --------- + Name: //a + From: + Date: Thu, 20 Nov 2008 01:00:00 +0000 + <BLANKLINE> + Insightful remarks + --------- Comment --------- + Name: //b + From: + Date: Thu, 20 Nov 2008 02:00:00 +0000 + <BLANKLINE> + Critique original comment + --------- Comment --------- + Name: //c + From: + Date: Thu, 20 Nov 2008 03:00:00 +0000 + <BLANKLINE> + Begin flamewar :p + --------- Comment --------- + Name: //d + From: + Date: Thu, 20 Nov 2008 04:00:00 +0000 + <BLANKLINE> + Useful examples + >>> print a.string_thread() + --------- Comment --------- + Name: //a + From: + Date: Thu, 20 Nov 2008 01:00:00 +0000 + <BLANKLINE> + Insightful remarks + --------- Comment --------- + Name: //b + From: + Date: Thu, 20 Nov 2008 02:00:00 +0000 + <BLANKLINE> + Critique original comment + --------- Comment --------- + Name: //c + From: + Date: Thu, 20 Nov 2008 03:00:00 +0000 + <BLANKLINE> + Begin flamewar :p + --------- Comment --------- + Name: //d + From: + Date: Thu, 20 Nov 2008 04:00:00 +0000 + <BLANKLINE> + Useful examples + """ + stringlist = [] + for depth,comment in self.thread(flatten=flatten): + ind = 2*depth+indent + string_fn = getattr(comment, string_method_name) + stringlist.append(string_fn(indent=ind)) + return '\n'.join(stringlist) + + def xml_thread(self, indent=0): + return self.string_thread(string_method_name="xml", indent=indent) + + # methods for saving/loading/acessing settings and properties. + + def load_settings(self, settings_mapfile=None): + if self.uuid == INVALID_UUID: + return + if settings_mapfile == None: + settings_mapfile = \ + self.storage.get(self.id.storage("values"), default="\n") + try: + settings = mapfile.parse(settings_mapfile) + except mapfile.InvalidMapfileContents, e: + raise Exception('Invalid settings file for comment %s\n' + '(BE version missmatch?)' % self.id.user()) + self._setup_saved_settings(settings) + + def save_settings(self): + if self.uuid == INVALID_UUID: + return + mf = mapfile.generate(self._get_saved_settings()) + self.storage.set(self.id.storage("values"), mf) + + def save(self): + """ + Save any loaded contents to storage. + + However, if ``self.storage.is_writeable() == True``, then any + changes are automatically written to storage as soon as they + happen, so calling this method will just waste time (unless + something else has been messing with your stored files). + """ + if self.uuid == INVALID_UUID: + return + assert self.storage != None, "Can't save without storage" + assert self.body != None, "Can't save blank comment" + if self.bug != None: + parent = self.bug.id.storage() + else: + parent = None + self.storage.add(self.id.storage(), parent=parent, directory=True) + self.storage.add(self.id.storage('values'), parent=self.id.storage(), + directory=False) + self.storage.add(self.id.storage('body'), parent=self.id.storage(), + directory=False) + self.save_settings() + self._set_comment_body(new=self.body, force=True) + + def remove(self): + for comment in self: + comment.remove() + if self.uuid != INVALID_UUID: + self.storage.recursive_remove(self.id.storage()) + + def add_reply(self, reply, allow_time_inversion=False): + if self.uuid != INVALID_UUID: + reply.in_reply_to = self.uuid + self.append(reply) + + def new_reply(self, body=None, content_type=None): + """ + >>> comm = Comment(bug=None, body="Some insightful remarks") + >>> repA = comm.new_reply("Critique original comment") + >>> repB = repA.new_reply("Begin flamewar :p") + >>> repB.in_reply_to == repA.uuid + True + """ + reply = Comment(self.bug, body=body, content_type=content_type) + self.add_reply(reply) + return reply + + def comment_from_uuid(self, uuid, match_alt_id=True): + """Use a uuid to look up a comment. + + >>> a = Comment(bug=None, uuid="a") + >>> b = a.new_reply() + >>> b.uuid = "b" + >>> c = b.new_reply() + >>> c.uuid = "c" + >>> d = a.new_reply() + >>> d.uuid = "d" + >>> d.alt_id = "d-alt" + >>> comm = a.comment_from_uuid("d") + >>> id(comm) == id(d) + True + >>> comm = a.comment_from_uuid("d-alt") + >>> id(comm) == id(d) + True + >>> comm = a.comment_from_uuid(None, match_alt_id=False) + Traceback (most recent call last): + ... + KeyError: None + """ + for comment in self.traverse(): + if comment.uuid == uuid: + return comment + if match_alt_id == True and uuid != None \ + and comment.alt_id == uuid: + return comment + raise KeyError(uuid) + + # methods for id generation + + def sibling_uuids(self): + if self.bug != None: + return self.bug.uuids() + return [] + + +def cmp_attr(comment_1, comment_2, attr, invert=False): + """ + Compare a general attribute between two comments using the conventional + comparison rule for that attribute type. If invert == True, sort + *against* that convention. + + >>> attr="author" + >>> commentA = Comment() + >>> commentB = Comment() + >>> commentA.author = "John Doe" + >>> commentB.author = "Jane Doe" + >>> cmp_attr(commentA, commentB, attr) > 0 + True + >>> cmp_attr(commentA, commentB, attr, invert=True) < 0 + True + >>> commentB.author = "John Doe" + >>> cmp_attr(commentA, commentB, attr) == 0 + True + """ + if not hasattr(comment_2, attr) : + return 1 + val_1 = getattr(comment_1, attr) + val_2 = getattr(comment_2, attr) + if val_1 == None: val_1 = None + if val_2 == None: val_2 = None + + if invert == True : + return -cmp(val_1, val_2) + else : + return cmp(val_1, val_2) + +# alphabetical rankings (a < z) +cmp_uuid = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "uuid") +cmp_author = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "author") +cmp_in_reply_to = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "in_reply_to") +cmp_content_type = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "content_type") +cmp_body = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "body") +cmp_extra_strings = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "extra_strings") +# chronological rankings (newer < older) +cmp_time = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "time", invert=True) + + +DEFAULT_CMP_FULL_CMP_LIST = \ + (cmp_time, cmp_author, cmp_content_type, cmp_body, cmp_in_reply_to, + cmp_uuid, cmp_extra_strings) + +class CommentCompoundComparator (object): + def __init__(self, cmp_list=DEFAULT_CMP_FULL_CMP_LIST): + self.cmp_list = cmp_list + def __call__(self, comment_1, comment_2): + for comparison in self.cmp_list : + val = comparison(comment_1, comment_2) + if val != 0 : + return val + return 0 + +cmp_full = CommentCompoundComparator() + +if libbe.TESTING == True: + suite = doctest.DocTestSuite() diff --git a/libbe/diff.py b/libbe/diff.py new file mode 100644 index 0000000..dc13b61 --- /dev/null +++ b/libbe/diff.py @@ -0,0 +1,691 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Tools for comparing two :class:`libbe.bug.BugDir`\s. +""" + +import difflib +import types + +import libbe +import libbe.bugdir +import libbe.bug +import libbe.util.tree +from libbe.storage.util.settings_object import setting_name_to_attr_name +from libbe.util.utility import time_to_str + + +class SubscriptionType (libbe.util.tree.Tree): + """Trees of subscription types to allow users to select exactly what + notifications they want to subscribe to. + """ + def __init__(self, type_name, *args, **kwargs): + libbe.util.tree.Tree.__init__(self, *args, **kwargs) + self.type = type_name + def __str__(self): + return self.type + def __cmp__(self, other): + return cmp(self.type, other.type) + def __repr__(self): + return '<SubscriptionType: %s>' % str(self) + def string_tree(self, indent=0): + lines = [] + for depth,node in self.thread(): + lines.append('%s%s' % (' '*(indent+2*depth), node)) + return '\n'.join(lines) + +BUGDIR_ID = 'DIR' +BUGDIR_TYPE_NEW = SubscriptionType('new') +BUGDIR_TYPE_MOD = SubscriptionType('mod') +BUGDIR_TYPE_REM = SubscriptionType('rem') +BUGDIR_TYPE_ALL = SubscriptionType('all', + [BUGDIR_TYPE_NEW, BUGDIR_TYPE_MOD, BUGDIR_TYPE_REM]) + +# same name as BUGDIR_TYPE_ALL for consistency +BUG_TYPE_ALL = SubscriptionType(str(BUGDIR_TYPE_ALL)) + +INVALID_TYPE = SubscriptionType('INVALID') + +class InvalidType (ValueError): + def __init__(self, type_name, type_root): + msg = 'Invalid type %s for tree:\n%s' \ + % (type_name, type_root.string_tree(4)) + ValueError.__init__(self, msg) + self.type_name = type_name + self.type_root = type_root + +def type_from_name(name, type_root, default=None, default_ok=False): + if name == str(type_root): + return type_root + for t in type_root.traverse(): + if name == str(t): + return t + if default_ok: + return default + raise InvalidType(name, type_root) + +class Subscription (object): + """A user subscription. + + Examples + -------- + + >>> subscriptions = [Subscription('XYZ', 'all'), + ... Subscription('DIR', 'new'), + ... Subscription('ABC', BUG_TYPE_ALL),] + >>> print sorted(subscriptions) + [<Subscription: DIR (new)>, <Subscription: ABC (all)>, <Subscription: XYZ (all)>] + """ + def __init__(self, id, subscription_type, **kwargs): + if 'type_root' not in kwargs: + if id == BUGDIR_ID: + kwargs['type_root'] = BUGDIR_TYPE_ALL + else: + kwargs['type_root'] = BUG_TYPE_ALL + if type(subscription_type) in types.StringTypes: + subscription_type = type_from_name(subscription_type, **kwargs) + self.id = id + self.type = subscription_type + def __cmp__(self, other): + for attr in 'id', 'type': + value = cmp(getattr(self, attr), getattr(other, attr)) + if value != 0: + if self.id == BUGDIR_ID: + return -1 + elif other.id == BUGDIR_ID: + return 1 + return value + def __str__(self): + return str(self.type) + def __repr__(self): + return '<Subscription: %s (%s)>' % (self.id, self.type) + +def subscriptions_from_string(string=None, subscription_sep=',', id_sep=':'): + """Provide a simple way for non-Python interfaces to read in subscriptions. + + Examples + -------- + + >>> subscriptions_from_string(None) + [<Subscription: DIR (all)>] + >>> subscriptions_from_string('DIR:new,DIR:rem,ABC:all,XYZ:all') + [<Subscription: DIR (new)>, <Subscription: DIR (rem)>, <Subscription: ABC (all)>, <Subscription: XYZ (all)>] + >>> subscriptions_from_string('DIR::new') + Traceback (most recent call last): + ... + ValueError: Invalid subscription "DIR::new", should be ID:TYPE + """ + if string == None: + return [Subscription(BUGDIR_ID, BUGDIR_TYPE_ALL)] + subscriptions = [] + for subscription in string.split(','): + fields = subscription.split(':') + if len(fields) != 2: + raise ValueError('Invalid subscription "%s", should be ID:TYPE' + % subscription) + id,type = fields + subscriptions.append(Subscription(id, type)) + return subscriptions + +class DiffTree (libbe.util.tree.Tree): + """A tree holding difference data for easy report generation. + + Examples + -------- + + >>> bugdir = DiffTree('bugdir') + >>> bdsettings = DiffTree('settings', data='target: None -> 1.0') + >>> bugdir.append(bdsettings) + >>> bugs = DiffTree('bugs', 'bug-count: 5 -> 6') + >>> bugdir.append(bugs) + >>> new = DiffTree('new', 'new bugs: ABC, DEF') + >>> bugs.append(new) + >>> rem = DiffTree('rem', 'removed bugs: RST, UVW') + >>> bugs.append(rem) + >>> print bugdir.report_string() + target: None -> 1.0 + bug-count: 5 -> 6 + new bugs: ABC, DEF + removed bugs: RST, UVW + >>> print '\\n'.join(bugdir.paths()) + bugdir + bugdir/settings + bugdir/bugs + bugdir/bugs/new + bugdir/bugs/rem + >>> bugdir.child_by_path('/') == bugdir + True + >>> bugdir.child_by_path('/bugs') == bugs + True + >>> bugdir.child_by_path('/bugs/rem') == rem + True + >>> bugdir.child_by_path('bugdir') == bugdir + True + >>> bugdir.child_by_path('bugdir/') == bugdir + True + >>> bugdir.child_by_path('bugdir/bugs') == bugs + True + >>> bugdir.child_by_path('/bugs').masked = True + >>> print bugdir.report_string() + target: None -> 1.0 + """ + def __init__(self, name, data=None, data_part_fn=str, + requires_children=False, masked=False): + libbe.util.tree.Tree.__init__(self) + self.name = name + self.data = data + self.data_part_fn = data_part_fn + self.requires_children = requires_children + self.masked = masked + def paths(self, parent_path=None): + paths = [] + if parent_path == None: + path = self.name + else: + path = '%s/%s' % (parent_path, self.name) + paths.append(path) + for child in self: + paths.extend(child.paths(path)) + return paths + def child_by_path(self, path): + if hasattr(path, 'split'): # convert string path to a list of names + names = path.split('/') + if names[0] == '': + names[0] = self.name # replace root with self + if len(names) > 1 and names[-1] == '': + names = names[:-1] # strip empty tail + else: # it was already an array + names = path + assert len(names) > 0, path + if names[0] == self.name: + if len(names) == 1: + return self + for child in self: + if names[1] == child.name: + return child.child_by_path(names[1:]) + if len(names) == 1: + raise KeyError, "%s doesn't match '%s'" % (names, self.name) + raise KeyError, '%s points to child not in %s' % (names, [c.name for c in self]) + def report_string(self): + report = self.report() + if report == None: + return '' + return '\n'.join(report) + def report(self, root=None, parent=None, depth=0): + if root == None: + root = self.make_root() + if self.masked == True: + return root + data_part = self.data_part(depth) + if self.requires_children == True \ + and len([c for c in self if c.masked == False]) == 0: + pass + else: + self.join(root, parent, data_part) + if data_part != None: + depth += 1 + for child in self: + root = child.report(root, self, depth) + return root + def make_root(self): + return [] + def join(self, root, parent, data_part): + if data_part != None: + root.append(data_part) + def data_part(self, depth, indent=True): + if self.data == None: + return None + if hasattr(self, '_cached_data_part'): + return self._cached_data_part + data_part = self.data_part_fn(self.data) + if indent == True: + data_part_lines = data_part.splitlines() + indent = ' '*(depth) + line_sep = '\n'+indent + data_part = indent+line_sep.join(data_part_lines) + self._cached_data_part = data_part + return data_part + +class Diff (object): + """Difference tree generator for BugDirs. + + Examples + -------- + + >>> import copy + >>> bd = libbe.bugdir.SimpleBugDir(memory=True) + >>> bd_new = copy.deepcopy(bd) + >>> bd_new.target = '1.0' + >>> a = bd_new.bug_from_uuid('a') + >>> rep = a.comment_root.new_reply("I'm closing this bug") + >>> rep.uuid = 'acom' + >>> rep.author = 'John Doe <j@doe.com>' + >>> rep.date = 'Thu, 01 Jan 1970 00:00:00 +0000' + >>> a.status = 'closed' + >>> b = bd_new.bug_from_uuid('b') + >>> bd_new.remove_bug(b) + >>> c = bd_new.new_bug('Bug C', _uuid='c') + >>> d = Diff(bd, bd_new) + >>> r = d.report_tree() + >>> print '\\n'.join(r.paths()) + bugdir + bugdir/settings + bugdir/bugs + bugdir/bugs/new + bugdir/bugs/new/c + bugdir/bugs/rem + bugdir/bugs/rem/b + bugdir/bugs/mod + bugdir/bugs/mod/a + bugdir/bugs/mod/a/settings + bugdir/bugs/mod/a/comments + bugdir/bugs/mod/a/comments/new + bugdir/bugs/mod/a/comments/new/acom + bugdir/bugs/mod/a/comments/rem + bugdir/bugs/mod/a/comments/mod + >>> print r.report_string() + Changed bug directory settings: + target: None -> 1.0 + New bugs: + abc/c:om: Bug C + Removed bugs: + abc/b:cm: Bug B + Modified bugs: + abc/a:cm: Bug A + Changed bug settings: + status: open -> closed + New comments: + from John Doe <j@doe.com> on Thu, 01 Jan 1970 00:00:00 +0000 + I'm closing this bug... + + You can also limit the report generation by providing a list of + subscriptions. + + >>> subscriptions = [Subscription('DIR', BUGDIR_TYPE_NEW), + ... Subscription('b', BUG_TYPE_ALL)] + >>> r = d.report_tree(subscriptions) + >>> print r.report_string() + New bugs: + abc/c:om: Bug C + Removed bugs: + abc/b:cm: Bug B + + While sending subscriptions to report_tree() makes the report + generation more efficient (because you may not need to compare + _all_ the bugs, etc.), sometimes you will have several sets of + subscriptions. In that case, it's better to run full_report() + first, and then use report_tree() to avoid redundant comparisons. + + >>> d.full_report() + >>> print d.report_tree([subscriptions[0]]).report_string() + New bugs: + abc/c:om: Bug C + >>> print d.report_tree([subscriptions[1]]).report_string() + Removed bugs: + abc/b:cm: Bug B + + >>> bd.cleanup() + """ + def __init__(self, old_bugdir, new_bugdir): + self.old_bugdir = old_bugdir + self.new_bugdir = new_bugdir + + # data assembly methods + + def _changed_bugs(self, subscriptions): + """ + Search for differences in all bugs between .old_bugdir and + .new_bugdir. Returns + (added_bugs, modified_bugs, removed_bugs) + where added_bugs and removed_bugs are lists of added and + removed bugs respectively. modified_bugs is a list of + (old_bug,new_bug) pairs. + """ + bugdir_types = [s.type for s in subscriptions if s.id == BUGDIR_ID] + new_uuids = [] + old_uuids = [] + for bd_type in [BUGDIR_TYPE_ALL, BUGDIR_TYPE_NEW, BUGDIR_TYPE_MOD]: + if bd_type in bugdir_types: + new_uuids = list(self.new_bugdir.uuids()) + break + for bd_type in [BUGDIR_TYPE_ALL, BUGDIR_TYPE_REM]: + if bd_type in bugdir_types: + old_uuids = list(self.old_bugdir.uuids()) + break + subscribed_bugs = [] + for s in subscriptions: + if s.id != BUGDIR_ID: + try: + bug = self.new_bugdir.bug_from_uuid(s.id) + except libbe.bugdir.NoBugMatches: + bug = self.old_bugdir.bug_from_uuid(s.id) + subscribed_bugs.append(bug.uuid) + new_uuids.extend([s for s in subscribed_bugs + if self.new_bugdir.has_bug(s)]) + new_uuids = sorted(set(new_uuids)) + old_uuids.extend([s for s in subscribed_bugs + if self.old_bugdir.has_bug(s)]) + old_uuids = sorted(set(old_uuids)) + + added = [] + removed = [] + modified = [] + if hasattr(self.old_bugdir, 'changed'): + # take advantage of a RevisionedBugDir-style changed() method + new_ids,mod_ids,rem_ids = self.old_bugdir.changed() + for id in new_ids: + for a_id in self.new_bugdir.storage.ancestors(id): + if a_id.count('/') == 0: + if a_id in [b.id.storage() for b in added]: + break + try: + bug = self.new_bugdir.bug_from_uuid(a_id) + added.append(bug) + except libbe.bugdir.NoBugMatches: + pass + for id in rem_ids: + for a_id in self.old_bugdir.storage.ancestors(id): + if a_id.count('/') == 0: + if a_id in [b.id.storage() for b in removed]: + break + try: + bug = self.old_bugdir.bug_from_uuid(a_id) + removed.append(bug) + except libbe.bugdir.NoBugMatches: + pass + for id in mod_ids: + for a_id in self.new_bugdir.storage.ancestors(id): + if a_id.count('/') == 0: + if a_id in [b[0].id.storage() for b in modified]: + break + try: + new_bug = self.new_bugdir.bug_from_uuid(a_id) + old_bug = self.old_bugdir.bug_from_uuid(a_id) + modified.append((old_bug, new_bug)) + except libbe.bugdir.NoBugMatches: + pass + else: + for uuid in new_uuids: + new_bug = self.new_bugdir.bug_from_uuid(uuid) + try: + old_bug = self.old_bugdir.bug_from_uuid(uuid) + except KeyError: + if BUGDIR_TYPE_ALL in bugdir_types \ + or BUGDIR_TYPE_NEW in bugdir_types \ + or uuid in subscribed_bugs: + added.append(new_bug) + continue + if BUGDIR_TYPE_ALL in bugdir_types \ + or BUGDIR_TYPE_MOD in bugdir_types \ + or uuid in subscribed_bugs: + if old_bug.storage != None and old_bug.storage.is_readable(): + old_bug.load_comments() + if new_bug.storage != None and new_bug.storage.is_readable(): + new_bug.load_comments() + if old_bug != new_bug: + modified.append((old_bug, new_bug)) + for uuid in old_uuids: + if not self.new_bugdir.has_bug(uuid): + old_bug = self.old_bugdir.bug_from_uuid(uuid) + removed.append(old_bug) + added.sort() + removed.sort() + modified.sort(self._bug_modified_cmp) + return (added, modified, removed) + def _bug_modified_cmp(self, left, right): + return cmp(left[1], right[1]) + def _changed_comments(self, old, new): + """ + Search for differences in all loaded comments between the bugs + old and new. Returns + (added_comments, modified_comments, removed_comments) + analogous to ._changed_bugs. + """ + if hasattr(self, '__changed_comments'): + if new.uuid in self.__changed_comments: + return self.__changed_comments[new.uuid] + else: + self.__changed_comments = {} + added = [] + removed = [] + modified = [] + old.comment_root.sort(key=lambda comm : comm.time) + new.comment_root.sort(key=lambda comm : comm.time) + old_comment_ids = [c.uuid for c in old.comments()] + new_comment_ids = [c.uuid for c in new.comments()] + for uuid in new_comment_ids: + new_comment = new.comment_from_uuid(uuid) + try: + old_comment = old.comment_from_uuid(uuid) + except KeyError: + added.append(new_comment) + else: + if old_comment != new_comment: + modified.append((old_comment, new_comment)) + for uuid in old_comment_ids: + if uuid not in new_comment_ids: + old_comment = old.comment_from_uuid(uuid) + removed.append(old_comment) + self.__changed_comments[new.uuid] = (added, modified, removed) + return self.__changed_comments[new.uuid] + def _attribute_changes(self, old, new, attributes): + """ + Take two objects old and new, and compare the value of *.attr + for attr in the list attribute names. Returns a list of + (attr_name, old_value, new_value) + tuples. + """ + change_list = [] + for attr in attributes: + old_value = getattr(old, attr) + new_value = getattr(new, attr) + if old_value != new_value: + change_list.append((attr, old_value, new_value)) + if len(change_list) >= 0: + return change_list + return None + def _settings_properties_attribute_changes(self, old, new, + hidden_properties=[]): + properties = sorted(new.settings_properties) + for p in hidden_properties: + properties.remove(p) + attributes = [setting_name_to_attr_name(None, p) + for p in properties] + return self._attribute_changes(old, new, attributes) + def _bugdir_attribute_changes(self): + return self._settings_properties_attribute_changes( \ + self.old_bugdir, self.new_bugdir) + def _bug_attribute_changes(self, old, new): + return self._settings_properties_attribute_changes(old, new) + def _comment_attribute_changes(self, old, new): + return self._settings_properties_attribute_changes(old, new) + + # report generation methods + + def full_report(self, diff_tree=DiffTree): + """ + Generate a full report for efficiency if you'll be using + .report_tree() with several sets of subscriptions. + """ + self._cached_full_report = self.report_tree(diff_tree=diff_tree, + allow_cached=False) + self._cached_full_report_diff_tree = diff_tree + def _sub_report(self, subscriptions): + """ + Return ._cached_full_report masked for subscriptions. + """ + root = self._cached_full_report + bugdir_types = [s.type for s in subscriptions if s.id == BUGDIR_ID] + subscribed_bugs = [s.id for s in subscriptions + if BUG_TYPE_ALL.has_descendant( \ + s.type, match_self=True)] + selected_by_bug = [node.name + for node in root.child_by_path('bugdir/bugs')] + if BUGDIR_TYPE_ALL in bugdir_types: + for node in root.traverse(): + node.masked = False + selected_by_bug = [] + else: + try: + node = root.child_by_path('bugdir/settings') + node.masked = True + except KeyError: + pass + for name,type in (('new', BUGDIR_TYPE_NEW), + ('mod', BUGDIR_TYPE_MOD), + ('rem', BUGDIR_TYPE_REM)): + if type in bugdir_types: + bugs = root.child_by_path('bugdir/bugs/%s' % name) + for bug_node in bugs: + for node in bug_node.traverse(): + node.masked = False + selected_by_bug.remove(name) + for name in selected_by_bug: + bugs = root.child_by_path('bugdir/bugs/%s' % name) + for bug_node in bugs: + if bug_node.name in subscribed_bugs: + for node in bug_node.traverse(): + node.masked = False + else: + for node in bug_node.traverse(): + node.masked = True + return root + def report_tree(self, subscriptions=None, diff_tree=DiffTree, + allow_cached=True): + """ + Pretty bare to make it easy to adjust to specific cases. You + can pass in a DiffTree subclass via diff_tree to override the + default report assembly process. + """ + if allow_cached == True \ + and hasattr(self, '_cached_full_report') \ + and diff_tree == self._cached_full_report_diff_tree: + return self._sub_report(subscriptions) + if subscriptions == None: + subscriptions = [Subscription(BUGDIR_ID, BUGDIR_TYPE_ALL)] + bugdir_settings = sorted(self.new_bugdir.settings_properties) + root = diff_tree('bugdir') + bugdir_subscriptions = [s.type for s in subscriptions + if s.id == BUGDIR_ID] + if BUGDIR_TYPE_ALL in bugdir_subscriptions: + bugdir_attribute_changes = self._bugdir_attribute_changes() + if len(bugdir_attribute_changes) > 0: + bugdir = diff_tree('settings', bugdir_attribute_changes, + self.bugdir_attribute_change_string) + root.append(bugdir) + bug_root = diff_tree('bugs') + root.append(bug_root) + add,mod,rem = self._changed_bugs(subscriptions) + bnew = diff_tree('new', 'New bugs:', requires_children=True) + bug_root.append(bnew) + for bug in add: + b = diff_tree(bug.uuid, bug, self.bug_add_string) + bnew.append(b) + brem = diff_tree('rem', 'Removed bugs:', requires_children=True) + bug_root.append(brem) + for bug in rem: + b = diff_tree(bug.uuid, bug, self.bug_rem_string) + brem.append(b) + bmod = diff_tree('mod', 'Modified bugs:', requires_children=True) + bug_root.append(bmod) + for old,new in mod: + b = diff_tree(new.uuid, (old,new), self.bug_mod_string) + bmod.append(b) + bug_attribute_changes = self._bug_attribute_changes(old, new) + if len(bug_attribute_changes) > 0: + bset = diff_tree('settings', bug_attribute_changes, + self.bug_attribute_change_string) + b.append(bset) + if old.summary != new.summary: + data = (old.summary, new.summary) + bsum = diff_tree('summary', data, self.bug_summary_change_string) + b.append(bsum) + cr = diff_tree('comments') + b.append(cr) + a,m,d = self._changed_comments(old, new) + cnew = diff_tree('new', 'New comments:', requires_children=True) + for comment in a: + c = diff_tree(comment.uuid, comment, self.comment_add_string) + cnew.append(c) + crem = diff_tree('rem', 'Removed comments:',requires_children=True) + for comment in d: + c = diff_tree(comment.uuid, comment, self.comment_rem_string) + crem.append(c) + cmod = diff_tree('mod','Modified comments:',requires_children=True) + for o,n in m: + c = diff_tree(n.uuid, (o,n), self.comment_mod_string) + cmod.append(c) + comm_attribute_changes = self._comment_attribute_changes(o, n) + if len(comm_attribute_changes) > 0: + cset = diff_tree('settings', comm_attribute_changes, + self.comment_attribute_change_string) + if o.body != n.body: + data = (o.body, n.body) + cbody = diff_tree('cbody', data, + self.comment_body_change_string) + c.append(cbody) + cr.extend([cnew, crem, cmod]) + return root + + # change data -> string methods. + # Feel free to play with these in subclasses. + + def attribute_change_string(self, attribute_changes, indent=0): + indent_string = ' '*indent + change_strings = [u'%s: %s -> %s' % f for f in attribute_changes] + for i,change_string in enumerate(change_strings): + change_strings[i] = indent_string+change_string + return u'\n'.join(change_strings) + def bugdir_attribute_change_string(self, attribute_changes): + return 'Changed bug directory settings:\n%s' % \ + self.attribute_change_string(attribute_changes, indent=1) + def bug_attribute_change_string(self, attribute_changes): + return 'Changed bug settings:\n%s' % \ + self.attribute_change_string(attribute_changes, indent=1) + def comment_attribute_change_string(self, attribute_changes): + return 'Changed comment settings:\n%s' % \ + self.attribute_change_string(attribute_changes, indent=1) + def bug_add_string(self, bug): + return bug.string(shortlist=True) + def bug_rem_string(self, bug): + return bug.string(shortlist=True) + def bug_mod_string(self, bugs): + old_bug,new_bug = bugs + return new_bug.string(shortlist=True) + def bug_summary_change_string(self, summaries): + old_summary,new_summary = summaries + return 'summary changed:\n %s\n %s' % (old_summary, new_summary) + def _comment_summary_string(self, comment): + return 'from %s on %s' % (comment.author, time_to_str(comment.time)) + def comment_add_string(self, comment): + summary = self._comment_summary_string(comment) + first_line = comment.body.splitlines()[0] + return '%s\n %s...' % (summary, first_line) + def comment_rem_string(self, comment): + summary = self._comment_summary_string(comment) + first_line = comment.body.splitlines()[0] + return '%s\n %s...' % (summary, first_line) + def comment_mod_string(self, comments): + old_comment,new_comment = comments + return self._comment_summary_string(new_comment) + def comment_body_change_string(self, bodies): + old_body,new_body = bodies + return ''.join(difflib.unified_diff( + old_body.splitlines(True), + new_body.splitlines(True), + 'before', 'after')) diff --git a/libbe/error.py b/libbe/error.py new file mode 100644 index 0000000..798136e --- /dev/null +++ b/libbe/error.py @@ -0,0 +1,26 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +General error classes for Bugs-Everywhere. +""" + +class NotSupported (NotImplementedError): + def __init__(self, action, message): + msg = '%s not supported: %s' % (action, message) + NotImplementedError.__init__(self, msg) + self.action = action + self.message = message diff --git a/libbe/storage/__init__.py b/libbe/storage/__init__.py new file mode 100644 index 0000000..6bceac9 --- /dev/null +++ b/libbe/storage/__init__.py @@ -0,0 +1,74 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Define the :class:`~libbe.storage.base.Storage` and +:class:`~libbe.storage.base.VersionedStorage` classes for storing BE +data. + +Also define assorted implementations for the Storage classes: + +* :mod:`libbe.storage.vcs` +* :mod:`libbe.storage.http` + +Also define an assortment of storage-related tools and utilities: + +* :mod:`libbe.storage.util` +""" + +import base + +ConnectionError = base.ConnectionError +InvalidStorageVersion = base.InvalidStorageVersion +InvalidID = base.InvalidID +InvalidRevision = base.InvalidRevision +InvalidDirectory = base.InvalidDirectory +NotWriteable = base.NotWriteable +NotReadable = base.NotReadable +EmptyCommit = base.EmptyCommit + +# a list of all past versions +STORAGE_VERSIONS = ['Bugs Everywhere Tree 1 0', + 'Bugs Everywhere Directory v1.1', + 'Bugs Everywhere Directory v1.2', + 'Bugs Everywhere Directory v1.3', + 'Bugs Everywhere Directory v1.4', + ] + +# the current version +STORAGE_VERSION = STORAGE_VERSIONS[-1] + +def get_http_storage(location): + import http + return http.HTTP(location) + +def get_vcs_storage(location): + import vcs + s = vcs.detect_vcs(location) + s.repo = location + return s + +def get_storage(location): + """ + Return a Storage instance from a repo location string. + """ + if location.startswith('http://') or location.startswith('https://'): + return get_http_storage(location) + return get_vcs_storage(location) + +__all__ = [ConnectionError, InvalidStorageVersion, InvalidID, + InvalidRevision, InvalidDirectory, NotWriteable, NotReadable, + EmptyCommit, STORAGE_VERSIONS, STORAGE_VERSION, + get_storage] diff --git a/libbe/storage/base.py b/libbe/storage/base.py new file mode 100644 index 0000000..0ae9c53 --- /dev/null +++ b/libbe/storage/base.py @@ -0,0 +1,1070 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +Abstract bug repository data storage to easily support multiple backends. +""" + +import copy +import os +import pickle +import types + +from libbe.error import NotSupported +import libbe.storage +from libbe.util.tree import Tree +from libbe.util import InvalidObject +import libbe.version +from libbe import TESTING + +if TESTING == True: + import doctest + import os.path + import sys + import unittest + + from libbe.util.utility import Dir + +class ConnectionError (Exception): + pass + +class InvalidStorageVersion(ConnectionError): + def __init__(self, active_version, expected_version=None): + if expected_version == None: + expected_version = libbe.storage.STORAGE_VERSION + msg = 'Storage in "%s" not the expected "%s"' \ + % (active_version, expected_version) + Exception.__init__(self, msg) + self.active_version = active_version + self.expected_version = expected_version + +class InvalidID (KeyError): + def __init__(self, id=None, revision=None, msg=None): + KeyError.__init__(self, id) + self.msg = msg + self.id = id + self.revision = revision + def __str__(self): + if self.msg == None: + return '%s in revision %s' % (self.id, self.revision) + return self.msg + + +class InvalidRevision (KeyError): + pass + +class InvalidDirectory (Exception): + pass + +class DirectoryNotEmpty (InvalidDirectory): + pass + +class NotWriteable (NotSupported): + def __init__(self, msg): + NotSupported.__init__(self, 'write', msg) + +class NotReadable (NotSupported): + def __init__(self, msg): + NotSupported.__init__(self, 'read', msg) + +class EmptyCommit(Exception): + def __init__(self): + Exception.__init__(self, 'No changes to commit') + +class _EMPTY (object): + """Entry has been added but has no user-set value.""" + pass + +class Entry (Tree): + def __init__(self, id, value=_EMPTY, parent=None, directory=False, + children=None): + if children == None: + Tree.__init__(self) + else: + Tree.__init__(self, children) + self.id = id + self.value = value + self.parent = parent + if self.parent != None: + if self.parent.directory == False: + raise InvalidDirectory( + 'Non-directory %s cannot have children' % self.parent) + parent.append(self) + self.directory = directory + + def __str__(self): + return '<Entry %s: %s>' % (self.id, self.value) + + def __repr__(self): + return str(self) + + def __cmp__(self, other, local=False): + if other == None: + return cmp(1, None) + if cmp(self.id, other.id) != 0: + return cmp(self.id, other.id) + if cmp(self.value, other.value) != 0: + return cmp(self.value, other.value) + if local == False: + if self.parent == None: + if cmp(self.parent, other.parent) != 0: + return cmp(self.parent, other.parent) + elif self.parent.__cmp__(other.parent, local=True) != 0: + return self.parent.__cmp__(other.parent, local=True) + for sc,oc in zip(self, other): + if sc.__cmp__(oc, local=True) != 0: + return sc.__cmp__(oc, local=True) + return 0 + + def _objects_to_ids(self): + if self.parent != None: + self.parent = self.parent.id + for i,c in enumerate(self): + self[i] = c.id + return self + + def _ids_to_objects(self, dict): + if self.parent != None: + self.parent = dict[self.parent] + for i,c in enumerate(self): + self[i] = dict[c] + return self + +class Storage (object): + """ + This class declares all the methods required by a Storage + interface. This implementation just keeps the data in a + dictionary and uses pickle for persistent storage. + """ + name = 'Storage' + + def __init__(self, repo='/', encoding='utf-8', options=None): + self.repo = repo + self.encoding = encoding + self.options = options + self.readable = True # soft limit (user choice) + self._readable = True # hard limit (backend choice) + self.writeable = True # soft limit (user choice) + self._writeable = True # hard limit (backend choice) + self.versioned = False + self.can_init = True + self.connected = False + + def __str__(self): + return '<%s %s %s>' % (self.__class__.__name__, id(self), self.repo) + + def __repr__(self): + return str(self) + + def version(self): + """Return a version string for this backend.""" + return libbe.version.version() + + def storage_version(self, revision=None): + """Return the storage format for this backend.""" + return libbe.storage.STORAGE_VERSION + + def is_readable(self): + return self.readable and self._readable + + def is_writeable(self): + return self.writeable and self._writeable + + def init(self): + """Create a new storage repository.""" + if self.can_init == False: + raise NotSupported('init', + 'Cannot initialize this repository format.') + if self.is_writeable() == False: + raise NotWriteable('Cannot initialize unwriteable storage.') + return self._init() + + def _init(self): + f = open(os.path.join(self.repo, 'repo.pkl'), 'wb') + root = Entry(id='__ROOT__', directory=True) + d = {root.id:root} + pickle.dump(dict((k,v._objects_to_ids()) for k,v in d.items()), f, -1) + f.close() + + def destroy(self): + """Remove the storage repository.""" + if self.is_writeable() == False: + raise NotWriteable('Cannot destroy unwriteable storage.') + return self._destroy() + + def _destroy(self): + os.remove(os.path.join(self.repo, 'repo.pkl')) + + def connect(self): + """Open a connection to the repository.""" + if self.is_readable() == False: + raise NotReadable('Cannot connect to unreadable storage.') + self._connect() + self.connected = True + + def _connect(self): + try: + f = open(os.path.join(self.repo, 'repo.pkl'), 'rb') + except IOError: + raise ConnectionError(self) + d = pickle.load(f) + self._data = dict((k,v._ids_to_objects(d)) for k,v in d.items()) + f.close() + + def disconnect(self): + """Close the connection to the repository.""" + if self.is_writeable() == False: + return + if self.connected == False: + return + self._disconnect() + self.connected = False + + def _disconnect(self): + f = open(os.path.join(self.repo, 'repo.pkl'), 'wb') + pickle.dump(dict((k,v._objects_to_ids()) + for k,v in self._data.items()), f, -1) + f.close() + self._data = None + + def add(self, id, *args, **kwargs): + """Add an entry""" + if self.is_writeable() == False: + raise NotWriteable('Cannot add entry to unwriteable storage.') + if not self.exists(id): + self._add(id, *args, **kwargs) + + def _add(self, id, parent=None, directory=False): + if parent == None: + parent = '__ROOT__' + p = self._data[parent] + self._data[id] = Entry(id, parent=p, directory=directory) + + def exists(self, *args, **kwargs): + """Check an entry's existence""" + if self.is_readable() == False: + raise NotReadable('Cannot check entry existence in unreadable storage.') + return self._exists(*args, **kwargs) + + def _exists(self, id, revision=None): + return id in self._data + + def remove(self, *args, **kwargs): + """Remove an entry.""" + if self.is_writeable() == False: + raise NotSupported('write', + 'Cannot remove entry from unwriteable storage.') + self._remove(*args, **kwargs) + + def _remove(self, id): + if self._data[id].directory == True \ + and len(self.children(id)) > 0: + raise DirectoryNotEmpty(id) + e = self._data.pop(id) + e.parent.remove(e) + + def recursive_remove(self, *args, **kwargs): + """Remove an entry and all its decendents.""" + if self.is_writeable() == False: + raise NotSupported('write', + 'Cannot remove entries from unwriteable storage.') + self._recursive_remove(*args, **kwargs) + + def _recursive_remove(self, id): + for entry in reversed(list(self._data[id].traverse())): + self._remove(entry.id) + + def ancestors(self, *args, **kwargs): + """Return a list of the specified entry's ancestors' ids.""" + if self.is_readable() == False: + raise NotReadable('Cannot list parents with unreadable storage.') + return self._ancestors(*args, **kwargs) + + def _ancestors(self, id=None, revision=None): + if id == None: + return [] + ancestors = [] + stack = [id] + while len(stack) > 0: + id = stack.pop(0) + parent = self._data[id].parent + if parent != None and not parent.id.startswith('__'): + ancestor = parent.id + ancestors.append(ancestor) + stack.append(ancestor) + return ancestors + + def children(self, *args, **kwargs): + """Return a list of specified entry's children's ids.""" + if self.is_readable() == False: + raise NotReadable('Cannot list children with unreadable storage.') + return self._children(*args, **kwargs) + + def _children(self, id=None, revision=None): + if id == None: + id = '__ROOT__' + return [c.id for c in self._data[id] if not c.id.startswith('__')] + + def get(self, *args, **kwargs): + """ + Get contents of and entry as they were in a given revision. + revision==None specifies the current revision. + + If there is no id, return default, unless default is not + given, in which case raise InvalidID. + """ + if self.is_readable() == False: + raise NotReadable('Cannot get entry with unreadable storage.') + if 'decode' in kwargs: + decode = kwargs.pop('decode') + else: + decode = False + value = self._get(*args, **kwargs) + if value != None: + if decode == True and type(value) != types.UnicodeType: + return unicode(value, self.encoding) + elif decode == False and type(value) != types.StringType: + return value.encode(self.encoding) + return value + + def _get(self, id, default=InvalidObject, revision=None): + if id in self._data and self._data[id].value != _EMPTY: + return self._data[id].value + elif default == InvalidObject: + raise InvalidID(id) + return default + + def set(self, id, value, *args, **kwargs): + """ + Set the entry contents. + """ + if self.is_writeable() == False: + raise NotWriteable('Cannot set entry in unwriteable storage.') + if type(value) == types.UnicodeType: + value = value.encode(self.encoding) + self._set(id, value, *args, **kwargs) + + def _set(self, id, value): + if id not in self._data: + raise InvalidID(id) + if self._data[id].directory == True: + raise InvalidDirectory( + 'Directory %s cannot have data' % self.parent) + self._data[id].value = value + +class VersionedStorage (Storage): + """ + This class declares all the methods required by a Storage + interface that supports versioning. This implementation just + keeps the data in a list and uses pickle for persistent + storage. + """ + name = 'VersionedStorage' + + def __init__(self, *args, **kwargs): + Storage.__init__(self, *args, **kwargs) + self.versioned = True + + def _init(self): + f = open(os.path.join(self.repo, 'repo.pkl'), 'wb') + root = Entry(id='__ROOT__', directory=True) + summary = Entry(id='__COMMIT__SUMMARY__', value='Initial commit') + body = Entry(id='__COMMIT__BODY__') + initial_commit = {root.id:root, summary.id:summary, body.id:body} + d = dict((k,v._objects_to_ids()) for k,v in initial_commit.items()) + pickle.dump([d, copy.deepcopy(d)], f, -1) # [inital tree, working tree] + f.close() + + def _connect(self): + try: + f = open(os.path.join(self.repo, 'repo.pkl'), 'rb') + except IOError: + raise ConnectionError(self) + d = pickle.load(f) + self._data = [dict((k,v._ids_to_objects(t)) for k,v in t.items()) + for t in d] + f.close() + + def _disconnect(self): + f = open(os.path.join(self.repo, 'repo.pkl'), 'wb') + pickle.dump([dict((k,v._objects_to_ids()) + for k,v in t.items()) for t in self._data], f, -1) + f.close() + self._data = None + + def _add(self, id, parent=None, directory=False): + if parent == None: + parent = '__ROOT__' + p = self._data[-1][parent] + self._data[-1][id] = Entry(id, parent=p, directory=directory) + + def _exists(self, id, revision=None): + if revision == None: + revision = -1 + else: + revision = int(revision) + return id in self._data[revision] + + def _remove(self, id): + if self._data[-1][id].directory == True \ + and len(self.children(id)) > 0: + raise DirectoryNotEmpty(id) + e = self._data[-1].pop(id) + e.parent.remove(e) + + def _recursive_remove(self, id): + for entry in reversed(list(self._data[-1][id].traverse())): + self._remove(entry.id) + + def _ancestors(self, id=None, revision=None): + if id == None: + return [] + if revision == None: + revision = -1 + else: + revision = int(revision) + ancestors = [] + stack = [id] + while len(stack) > 0: + id = stack.pop(0) + parent = self._data[revision][id].parent + if parent != None and not parent.id.startswith('__'): + ancestor = parent.id + ancestors.append(ancestor) + stack.append(ancestor) + return ancestors + + def _children(self, id=None, revision=None): + if id == None: + id = '__ROOT__' + if revision == None: + revision = -1 + else: + revision = int(revision) + return [c.id for c in self._data[revision][id] + if not c.id.startswith('__')] + + def _get(self, id, default=InvalidObject, revision=None): + if revision == None: + revision = -1 + else: + revision = int(revision) + if id in self._data[revision] \ + and self._data[revision][id].value != _EMPTY: + return self._data[revision][id].value + elif default == InvalidObject: + raise InvalidID(id) + return default + + def _set(self, id, value): + if id not in self._data[-1]: + raise InvalidID(id) + self._data[-1][id].value = value + + def commit(self, *args, **kwargs): + """ + Commit the current repository, with a commit message string + summary and body. Return the name of the new revision. + + If allow_empty == False (the default), raise EmptyCommit if + there are no changes to commit. + """ + if self.is_writeable() == False: + raise NotWriteable('Cannot commit to unwriteable storage.') + return self._commit(*args, **kwargs) + + def _commit(self, summary, body=None, allow_empty=False): + if self._data[-1] == self._data[-2] and allow_empty == False: + raise EmptyCommit + self._data[-1]["__COMMIT__SUMMARY__"].value = summary + self._data[-1]["__COMMIT__BODY__"].value = body + rev = str(len(self._data)-1) + self._data.append(copy.deepcopy(self._data[-1])) + return rev + + def revision_id(self, index=None): + """ + Return the name of the <index>th revision. The choice of + which branch to follow when crossing branches/merges is not + defined. Revision indices start at 1; ID 0 is the blank + repository. + + Return None if index==None. + + If the specified revision does not exist, raise InvalidRevision. + """ + if index == None: + return None + try: + if int(index) != index: + raise InvalidRevision(index) + except ValueError: + raise InvalidRevision(index) + L = len(self._data) - 1 # -1 b/c of initial commit + if index >= -L and index <= L: + return str(index % L) + raise InvalidRevision(i) + + def changed(self, revision): + """Return a tuple of lists of ids `(new, modified, removed)` from the + specified revision to the current situation. + """ + new = [] + modified = [] + removed = [] + for id,value in self._data[int(revision)].items(): + if id.startswith('__'): + continue + if not id in self._data[-1]: + removed.append(id) + elif value.value != self._data[-1][id].value: + modified.append(id) + for id in self._data[-1]: + if not id in self._data[int(revision)]: + new.append(id) + return (new, modified, removed) + + +if TESTING == True: + class StorageTestCase (unittest.TestCase): + """Test cases for Storage class.""" + + Class = Storage + + def __init__(self, *args, **kwargs): + super(StorageTestCase, self).__init__(*args, **kwargs) + self.dirname = None + + # this class will be the basis of tests for several classes, + # so make sure we print the name of the class we're dealing with. + def _classname(self): + version = '?' + try: + if hasattr(self, 's'): + version = self.s.version() + except: + pass + return '%s:%s' % (self.Class.__name__, version) + + def fail(self, msg=None): + """Fail immediately, with the given message.""" + raise self.failureException, \ + '(%s) %s' % (self._classname(), msg) + + def failIf(self, expr, msg=None): + "Fail the test if the expression is true." + if expr: raise self.failureException, \ + '(%s) %s' % (self._classname(), msg) + + def failUnless(self, expr, msg=None): + """Fail the test unless the expression is true.""" + if not expr: raise self.failureException, \ + '(%s) %s' % (self._classname(), msg) + + def setUp(self): + """Set up test fixtures for Storage test case.""" + super(StorageTestCase, self).setUp() + self.dir = Dir() + self.dirname = self.dir.path + self.s = self.Class(repo=self.dirname) + self.assert_failed_connect() + self.s.init() + self.s.connect() + + def tearDown(self): + super(StorageTestCase, self).tearDown() + self.s.disconnect() + self.s.destroy() + self.assert_failed_connect() + self.dir.cleanup() + + def assert_failed_connect(self): + try: + self.s.connect() + self.fail( + "Connected to %(name)s repository before initialising" + % vars(self.Class)) + except ConnectionError: + pass + + class Storage_init_TestCase (StorageTestCase): + """Test cases for Storage.init method.""" + + def test_connect_should_succeed_after_init(self): + """Should connect after initialization.""" + self.s.connect() + + class Storage_connect_disconnect_TestCase (StorageTestCase): + """Test cases for Storage.connect and .disconnect methods.""" + + def test_multiple_disconnects(self): + """Should be able to call .disconnect multiple times.""" + self.s.disconnect() + self.s.disconnect() + + class Storage_add_remove_TestCase (StorageTestCase): + """Test cases for Storage.add, .remove, and .recursive_remove methods.""" + + def test_initially_empty(self): + """New repository should be empty.""" + self.failUnless(len(self.s.children()) == 0, self.s.children()) + + def test_add_identical_rooted(self): + """Adding entries with the same ID should not increase the number of children. + """ + for i in range(10): + self.s.add('some id', directory=False) + s = sorted(self.s.children()) + self.failUnless(s == ['some id'], s) + + def test_add_rooted(self): + """Adding entries should increase the number of children (rooted). + """ + ids = [] + for i in range(10): + ids.append(str(i)) + self.s.add(ids[-1], directory=(i % 2 == 0)) + s = sorted(self.s.children()) + self.failUnless(s == ids, '\n %s\n !=\n %s' % (s, ids)) + + def test_add_nonrooted(self): + """Adding entries should increase the number of children (nonrooted). + """ + self.s.add('parent', directory=True) + ids = [] + for i in range(10): + ids.append(str(i)) + self.s.add(ids[-1], 'parent', directory=(i % 2 == 0)) + s = sorted(self.s.children('parent')) + self.failUnless(s == ids, '\n %s\n !=\n %s' % (s, ids)) + s = self.s.children() + self.failUnless(s == ['parent'], s) + + def test_ancestors(self): + """Check ancestors lists. + """ + self.s.add('parent', directory=True) + for i in range(10): + i_id = str(i) + self.s.add(i_id, 'parent', directory=True) + for j in range(10): # add some grandkids + j_id = str(20*(i+1)+j) + self.s.add(j_id, i_id, directory=(i%2 == 0)) + ancestors = sorted(self.s.ancestors(j_id)) + self.failUnless(ancestors == [i_id, 'parent'], + 'Unexpected ancestors for %s/%s, "%s"' + % (i_id, j_id, ancestors)) + + def test_children(self): + """Non-UUID ids should be returned as such. + """ + self.s.add('parent', directory=True) + ids = [] + for i in range(10): + ids.append('parent/%s' % str(i)) + self.s.add(ids[-1], 'parent', directory=(i % 2 == 0)) + s = sorted(self.s.children('parent')) + self.failUnless(s == ids, '\n %s\n !=\n %s' % (s, ids)) + + def test_add_invalid_directory(self): + """Should not be able to add children to non-directories. + """ + self.s.add('parent', directory=False) + try: + self.s.add('child', 'parent', directory=False) + self.fail( + '%s.add() succeeded instead of raising InvalidDirectory' + % (vars(self.Class)['name'])) + except InvalidDirectory: + pass + try: + self.s.add('child', 'parent', directory=True) + self.fail( + '%s.add() succeeded instead of raising InvalidDirectory' + % (vars(self.Class)['name'])) + except InvalidDirectory: + pass + self.failUnless(len(self.s.children('parent')) == 0, + self.s.children('parent')) + + def test_remove_rooted(self): + """Removing entries should decrease the number of children (rooted). + """ + ids = [] + for i in range(10): + ids.append(str(i)) + self.s.add(ids[-1], directory=(i % 2 == 0)) + for i in range(10): + self.s.remove(ids.pop()) + s = sorted(self.s.children()) + self.failUnless(s == ids, '\n %s\n !=\n %s' % (s, ids)) + + def test_remove_nonrooted(self): + """Removing entries should decrease the number of children (nonrooted). + """ + self.s.add('parent', directory=True) + ids = [] + for i in range(10): + ids.append(str(i)) + self.s.add(ids[-1], 'parent', directory=False)#(i % 2 == 0)) + for i in range(10): + self.s.remove(ids.pop()) + s = sorted(self.s.children('parent')) + self.failUnless(s == ids, '\n %s\n !=\n %s' % (s, ids)) + if len(s) > 0: + s = self.s.children() + self.failUnless(s == ['parent'], s) + + def test_remove_directory_not_empty(self): + """Removing a non-empty directory entry should raise exception. + """ + self.s.add('parent', directory=True) + ids = [] + for i in range(10): + ids.append(str(i)) + self.s.add(ids[-1], 'parent', directory=(i % 2 == 0)) + self.s.remove(ids.pop()) # empty directory removal succeeds + try: + self.s.remove('parent') # empty directory removal succeeds + self.fail( + "%s.remove() didn't raise DirectoryNotEmpty" + % (vars(self.Class)['name'])) + except DirectoryNotEmpty: + pass + + def test_recursive_remove(self): + """Recursive remove should empty the tree.""" + self.s.add('parent', directory=True) + ids = [] + for i in range(10): + ids.append(str(i)) + self.s.add(ids[-1], 'parent', directory=True) + for j in range(10): # add some grandkids + self.s.add(str(20*(i+1)+j), ids[-1], directory=(i%2 == 0)) + self.s.recursive_remove('parent') + s = sorted(self.s.children()) + self.failUnless(s == [], s) + + class Storage_get_set_TestCase (StorageTestCase): + """Test cases for Storage.get and .set methods.""" + + id = 'unlikely id' + val = 'unlikely value' + + def test_get_default(self): + """Get should return specified default if id not in Storage. + """ + ret = self.s.get(self.id, default=self.val) + self.failUnless(ret == self.val, + "%s.get() returned %s not %s" + % (vars(self.Class)['name'], ret, self.val)) + + def test_get_default_exception(self): + """Get should raise exception if id not in Storage and no default. + """ + try: + ret = self.s.get(self.id) + self.fail( + "%s.get() returned %s instead of raising InvalidID" + % (vars(self.Class)['name'], ret)) + except InvalidID: + pass + + def test_get_initial_value(self): + """Data value should be default before any value has been set. + """ + self.s.add(self.id, directory=False) + val = 'UNLIKELY DEFAULT' + ret = self.s.get(self.id, default=val) + self.failUnless(ret == val, + "%s.get() returned %s not %s" + % (vars(self.Class)['name'], ret, val)) + + def test_set_exception(self): + """Set should raise exception if id not in Storage. + """ + try: + self.s.set(self.id, self.val) + self.fail( + "%(name)s.set() did not raise InvalidID" + % vars(self.Class)) + except InvalidID: + pass + + def test_set(self): + """Set should define the value returned by get. + """ + self.s.add(self.id, directory=False) + self.s.set(self.id, self.val) + ret = self.s.get(self.id) + self.failUnless(ret == self.val, + "%s.get() returned %s not %s" + % (vars(self.Class)['name'], ret, self.val)) + + def test_unicode_set(self): + """Set should define the value returned by get. + """ + val = u'Fran\xe7ois' + self.s.add(self.id, directory=False) + self.s.set(self.id, val) + ret = self.s.get(self.id, decode=True) + self.failUnless(type(ret) == types.UnicodeType, + "%s.get() returned %s not UnicodeType" + % (vars(self.Class)['name'], type(ret))) + self.failUnless(ret == val, + "%s.get() returned %s not %s" + % (vars(self.Class)['name'], ret, self.val)) + ret = self.s.get(self.id) + self.failUnless(type(ret) == types.StringType, + "%s.get() returned %s not StringType" + % (vars(self.Class)['name'], type(ret))) + s = unicode(ret, self.s.encoding) + self.failUnless(s == val, + "%s.get() returned %s not %s" + % (vars(self.Class)['name'], s, self.val)) + + + class Storage_persistence_TestCase (StorageTestCase): + """Test cases for Storage.disconnect and .connect methods.""" + + id = 'unlikely id' + val = 'unlikely value' + + def test_get_set_persistence(self): + """Set should define the value returned by get after reconnect. + """ + self.s.add(self.id, directory=False) + self.s.set(self.id, self.val) + self.s.disconnect() + self.s.connect() + ret = self.s.get(self.id) + self.failUnless(ret == self.val, + "%s.get() returned %s not %s" + % (vars(self.Class)['name'], ret, self.val)) + + def test_empty_get_set_persistence(self): + """After empty set, get may return either an empty string or default. + """ + self.s.add(self.id, directory=False) + self.s.set(self.id, '') + self.s.disconnect() + self.s.connect() + default = 'UNLIKELY DEFAULT' + ret = self.s.get(self.id, default=default) + self.failUnless(ret in ['', default], + "%s.get() returned %s not in %s" + % (vars(self.Class)['name'], ret, ['', default])) + + def test_add_nonrooted_persistence(self): + """Adding entries should increase the number of children after reconnect. + """ + self.s.add('parent', directory=True) + ids = [] + for i in range(10): + ids.append(str(i)) + self.s.add(ids[-1], 'parent', directory=(i % 2 == 0)) + self.s.disconnect() + self.s.connect() + s = sorted(self.s.children('parent')) + self.failUnless(s == ids, '\n %s\n !=\n %s' % (s, ids)) + s = self.s.children() + self.failUnless(s == ['parent'], s) + + class VersionedStorageTestCase (StorageTestCase): + """Test cases for VersionedStorage methods.""" + + Class = VersionedStorage + + class VersionedStorage_commit_TestCase (VersionedStorageTestCase): + """Test cases for VersionedStorage.commit and revision_ids methods.""" + + id = 'unlikely id' + val = 'Some value' + commit_msg = 'Committing something interesting' + commit_body = 'Some\nlonger\ndescription\n' + + def _setup_for_empty_commit(self): + """ + Initialization might add some files to version control, so + commit those first, before testing the empty commit + functionality. + """ + try: + self.s.commit('Added initialization files') + except EmptyCommit: + pass + + def test_revision_id_exception(self): + """Invalid revision id should raise InvalidRevision. + """ + try: + rev = self.s.revision_id('highly unlikely revision id') + self.fail( + "%s.revision_id() didn't raise InvalidRevision, returned %s." + % (vars(self.Class)['name'], rev)) + except InvalidRevision: + pass + + def test_empty_commit_raises_exception(self): + """Empty commit should raise exception. + """ + self._setup_for_empty_commit() + try: + self.s.commit(self.commit_msg, self.commit_body) + self.fail( + "Empty %(name)s.commit() didn't raise EmptyCommit." + % vars(self.Class)) + except EmptyCommit: + pass + + def test_empty_commit_allowed(self): + """Empty commit should _not_ raise exception if allow_empty=True. + """ + self._setup_for_empty_commit() + self.s.commit(self.commit_msg, self.commit_body, + allow_empty=True) + + def test_commit_revision_ids(self): + """Commit / revision_id should agree on revision ids. + """ + def val(i): + return '%s:%d' % (self.val, i+1) + self.s.add(self.id, directory=False) + revs = [] + for i in range(10): + self.s.set(self.id, val(i)) + revs.append(self.s.commit('%s: %d' % (self.commit_msg, i), + self.commit_body)) + for i in range(10): + rev = self.s.revision_id(i+1) + self.failUnless(rev == revs[i], + "%s.revision_id(%d) returned %s not %s" + % (vars(self.Class)['name'], i+1, rev, revs[i])) + for i in range(-1, -9, -1): + rev = self.s.revision_id(i) + self.failUnless(rev == revs[i], + "%s.revision_id(%d) returned %s not %s" + % (vars(self.Class)['name'], i, rev, revs[i])) + + def test_get_previous_version(self): + """Get should be able to return the previous version. + """ + def val(i): + return '%s:%d' % (self.val, i+1) + self.s.add(self.id, directory=False) + revs = [] + for i in range(10): + self.s.set(self.id, val(i)) + revs.append(self.s.commit('%s: %d' % (self.commit_msg, i), + self.commit_body)) + for i in range(10): + ret = self.s.get(self.id, revision=revs[i]) + self.failUnless(ret == val(i), + "%s.get() returned %s not %s for revision %s" + % (vars(self.Class)['name'], ret, val(i), revs[i])) + + def test_get_previous_children(self): + """Children list should be revision dependent. + """ + self.s.add('parent', directory=True) + revs = [] + cur_children = [] + children = [] + for i in range(10): + new_child = str(i) + self.s.add(new_child, 'parent') + self.s.set(new_child, self.val) + revs.append(self.s.commit('%s: %d' % (self.commit_msg, i), + self.commit_body)) + cur_children.append(new_child) + children.append(list(cur_children)) + for i in range(10): + ret = sorted(self.s.children('parent', revision=revs[i])) + self.failUnless(ret == children[i], + "%s.get() returned %s not %s for revision %s" + % (vars(self.Class)['name'], ret, + children[i], revs[i])) + + class VersionedStorage_changed_TestCase (VersionedStorageTestCase): + """Test cases for VersionedStorage.changed() method.""" + + def test_changed(self): + """Changed lists should reflect past activity""" + self.s.add('dir', directory=True) + self.s.add('modified', parent='dir') + self.s.set('modified', 'some value to be modified') + self.s.add('moved', parent='dir') + self.s.set('moved', 'this entry will be moved') + self.s.add('removed', parent='dir') + self.s.set('removed', 'this entry will be deleted') + revA = self.s.commit('Initial state') + self.s.add('new', parent='dir') + self.s.set('new', 'this entry is new') + self.s.set('modified', 'a new value') + self.s.remove('moved') + self.s.add('moved2', parent='dir') + self.s.set('moved2', 'this entry will be moved') + self.s.remove('removed') + revB = self.s.commit('Final state') + new,mod,rem = self.s.changed(revA) + self.failUnless(sorted(new) == ['moved2', 'new'], + 'Unexpected new: %s' % new) + self.failUnless(mod == ['modified'], + 'Unexpected modified: %s' % mod) + self.failUnless(sorted(rem) == ['moved', 'removed'], + 'Unexpected removed: %s' % rem) + + def make_storage_testcase_subclasses(storage_class, namespace): + """Make StorageTestCase subclasses for storage_class in namespace.""" + storage_testcase_classes = [ + c for c in ( + ob for ob in globals().values() if isinstance(ob, type)) + if issubclass(c, StorageTestCase) \ + and c.Class == Storage] + + for base_class in storage_testcase_classes: + testcase_class_name = storage_class.__name__ + base_class.__name__ + testcase_class_bases = (base_class,) + testcase_class_dict = dict(base_class.__dict__) + testcase_class_dict['Class'] = storage_class + testcase_class = type( + testcase_class_name, testcase_class_bases, testcase_class_dict) + setattr(namespace, testcase_class_name, testcase_class) + + def make_versioned_storage_testcase_subclasses(storage_class, namespace): + """Make VersionedStorageTestCase subclasses for storage_class in namespace.""" + storage_testcase_classes = [ + c for c in ( + ob for ob in globals().values() if isinstance(ob, type)) + if ((issubclass(c, StorageTestCase) \ + and c.Class == Storage) + or + (issubclass(c, VersionedStorageTestCase) \ + and c.Class == VersionedStorage))] + + for base_class in storage_testcase_classes: + testcase_class_name = storage_class.__name__ + base_class.__name__ + testcase_class_bases = (base_class,) + testcase_class_dict = dict(base_class.__dict__) + testcase_class_dict['Class'] = storage_class + testcase_class = type( + testcase_class_name, testcase_class_bases, testcase_class_dict) + setattr(namespace, testcase_class_name, testcase_class) + + make_storage_testcase_subclasses(VersionedStorage, sys.modules[__name__]) + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/storage/http.py b/libbe/storage/http.py new file mode 100644 index 0000000..7ec9f54 --- /dev/null +++ b/libbe/storage/http.py @@ -0,0 +1,446 @@ +# Copyright (C) 2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# For urllib2 information, see +# urllib2, from urllib2 - The Missing Manual +# http://www.voidspace.org.uk/python/articles/urllib2.shtml +# +# A dictionary of response codes is available in +# httplib.responses + +"""Define an HTTP-based :class:`~libbe.storage.base.VersionedStorage` +implementation. + +See Also +-------- +:mod:`libbe.command.serve` : the associated server + +""" + +import sys +import urllib +import urllib2 +import urlparse + +import libbe +import libbe.version +import base +from libbe import TESTING + +if TESTING == True: + import copy + import doctest + import StringIO + import unittest + + import libbe.bugdir + import libbe.command.serve + + +USER_AGENT = 'BE-HTTP-Storage' +HTTP_OK = 200 +HTTP_FOUND = 302 +HTTP_TEMP_REDIRECT = 307 +HTTP_USER_ERROR = 418 +"""Status returned to indicate exceptions on the server side. + +A BE-specific extension to the HTTP/1.1 protocol (See `RFC 2616`_). + +.. _RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1 +""" + +HTTP_VALID = [HTTP_OK, HTTP_FOUND, HTTP_TEMP_REDIRECT, HTTP_USER_ERROR] + +class InvalidURL (Exception): + def __init__(self, error=None, url=None, msg=None): + Exception.__init__(self, msg) + self.url = url + self.error = error + self.msg = msg + def __str__(self): + if self.msg == None: + if self.error == None: + return "Unknown URL error: %s" % self.url + return self.error.__str__() + return self.msg + +def get_post_url(url, get=True, data_dict=None, headers=[]): + """Execute a GET or POST transaction. + + Parameters + ---------- + url : str + The base URL (query portion added internally, if necessary). + get : bool + Use GET if True, otherwise use POST. + data_dict : dict + Data to send, either by URL query (if GET) or by POST (if POST). + headers : list + Extra HTTP headers to add to the request. + """ + if data_dict == None: + data_dict = {} + if get == True: + if data_dict != {}: + # encode get parameters in the url + param_string = urllib.urlencode(data_dict) + url = "%s?%s" % (url, param_string) + data = None + else: + data = urllib.urlencode(data_dict) + headers = dict(headers) + headers['User-Agent'] = USER_AGENT + req = urllib2.Request(url, data=data, headers=headers) + try: + response = urllib2.urlopen(req) + except urllib2.HTTPError, e: + if hasattr(e, 'reason'): + msg = 'We failed to reach a server.\nURL: %s\nReason: %s' \ + % (url, e.reason) + elif hasattr(e, 'code'): + msg = "The server couldn't fulfill the request.\nURL: %s\nError code: %s" \ + % (url, e.code) + raise InvalidURL(error=e, url=url, msg=msg) + page = response.read() + final_url = response.geturl() + info = response.info() + response.close() + return (page, final_url, info) + + +class HTTP (base.VersionedStorage): + """:class:`~libbe.storage.base.VersionedStorage` implementation over + HTTP. + + Uses GET to retrieve information and POST to set information. + """ + name = 'HTTP' + + def __init__(self, repo, *args, **kwargs): + repo,self.uname,self.password = self.parse_repo(repo) + base.VersionedStorage.__init__(self, repo, *args, **kwargs) + + def parse_repo(self, repo): + """Grab username and password (if any) from the repo URL. + + Examples + -------- + + >>> s = HTTP('http://host.com/path/to/repo') + >>> s.repo + 'http://host.com/path/to/repo' + >>> s.uname == None + True + >>> s.password == None + True + >>> s.parse_repo('http://joe:secret@host.com/path/to/repo') + ('http://host.com/path/to/repo', 'joe', 'secret') + """ + scheme,netloc,path,params,query,fragment = urlparse.urlparse(repo) + parts = netloc.split('@', 1) + if len(parts) == 2: + uname,password = parts[0].split(':') + repo = urlparse.urlunparse( + (scheme, parts[1], path, params, query, fragment)) + else: + uname,password = (None, None) + return (repo, uname, password) + + def get_post_url(self, url, get=True, data_dict=None, headers=[]): + if self.uname != None and self.password != None: + headers.append(('Authorization','Basic %s' % \ + ('%s:%s' % (self.uname, self.password)).encode('base64'))) + return get_post_url(url, get, data_dict, headers) + + def storage_version(self, revision=None): + """Return the storage format for this backend.""" + return libbe.storage.STORAGE_VERSION + + def _init(self): + """Create a new storage repository.""" + raise base.NotSupported( + 'init', 'Cannot initialize this repository format.') + + def _destroy(self): + """Remove the storage repository.""" + raise base.NotSupported( + 'destroy', 'Cannot destroy this repository format.') + + def _connect(self): + self.check_storage_version() + + def _disconnect(self): + pass + + def _add(self, id, parent=None, directory=False): + url = urlparse.urljoin(self.repo, 'add') + page,final_url,info = self.get_post_url( + url, get=False, + data_dict={'id':id, 'parent':parent, 'directory':directory}) + + def _exists(self, id, revision=None): + url = urlparse.urljoin(self.repo, 'exists') + page,final_url,info = self.get_post_url( + url, get=True, + data_dict={'id':id, 'revision':revision}) + if page == 'True': + return True + return False + + def _remove(self, id): + url = urlparse.urljoin(self.repo, 'remove') + page,final_url,info = self.get_post_url( + url, get=False, + data_dict={'id':id, 'recursive':False}) + + def _recursive_remove(self, id): + url = urlparse.urljoin(self.repo, 'remove') + page,final_url,info = self.get_post_url( + url, get=False, + data_dict={'id':id, 'recursive':True}) + + def _ancestors(self, id=None, revision=None): + url = urlparse.urljoin(self.repo, 'ancestors') + page,final_url,info = self.get_post_url( + url, get=True, + data_dict={'id':id, 'revision':revision}) + return page.strip('\n').splitlines() + + def _children(self, id=None, revision=None): + url = urlparse.urljoin(self.repo, 'children') + page,final_url,info = self.get_post_url( + url, get=True, + data_dict={'id':id, 'revision':revision}) + return page.strip('\n').splitlines() + + def _get(self, id, default=base.InvalidObject, revision=None): + url = urlparse.urljoin(self.repo, '/'.join(['get', id])) + try: + page,final_url,info = self.get_post_url( + url, get=True, + data_dict={'revision':revision}) + except InvalidURL, e: + if not (hasattr(e.error, 'code') and e.error.code in HTTP_VALID): + raise + elif default == base.InvalidObject: + raise base.InvalidID(id) + return default + version = info['X-BE-Version'] + if version != libbe.storage.STORAGE_VERSION: + raise base.InvalidStorageVersion( + version, libbe.storage.STORAGE_VERSION) + return page + + def _set(self, id, value): + url = urlparse.urljoin(self.repo, '/'.join(['set', id])) + try: + page,final_url,info = self.get_post_url( + url, get=False, + data_dict={'value':value}) + except InvalidURL, e: + if not (hasattr(e.error, 'code') and e.error.code in HTTP_VALID): + raise + if e.error.code == HTTP_USER_ERROR \ + and not 'InvalidID' in str(e.error): + raise base.InvalidDirectory( + 'Directory %s cannot have data' % id) + raise base.InvalidID(id) + + def _commit(self, summary, body=None, allow_empty=False): + url = urlparse.urljoin(self.repo, 'commit') + try: + page,final_url,info = self.get_post_url( + url, get=False, + data_dict={'summary':summary, 'body':body, + 'allow_empty':allow_empty}) + except InvalidURL, e: + if not (hasattr(e.error, 'code') and e.error.code in HTTP_VALID): + raise + if e.error.code == HTTP_USER_ERROR: + raise base.EmptyCommit + raise base.InvalidID(id) + return page.rstrip('\n') + + def revision_id(self, index=None): + """Return the name of the <index>th revision. + + The choice of which branch to follow when crossing + branches/merges is not defined. Revision indices start at 1; + ID 0 is the blank repository. + + Return None if index==None. + + Raises + ------ + InvalidRevision + If the specified revision does not exist. + """ + if index == None: + return None + try: + if int(index) != index: + raise base.InvalidRevision(index) + except ValueError: + raise base.InvalidRevision(index) + url = urlparse.urljoin(self.repo, 'revision-id') + try: + page,final_url,info = self.get_post_url( + url, get=True, + data_dict={'index':index}) + except InvalidURL, e: + if not (hasattr(e.error, 'code') and e.error.code in HTTP_VALID): + raise + if e.error.code == HTTP_USER_ERROR: + raise base.InvalidRevision(index) + raise base.InvalidID(id) + return page.rstrip('\n') + + def changed(self, revision=None): + url = urlparse.urljoin(self.repo, 'changed') + page,final_url,info = self.get_post_url( + url, get=True, + data_dict={'revision':revision}) + lines = page.strip('\n') + new,mod,rem = [p.splitlines() for p in page.split('\n\n')] + return (new, mod, rem) + + def check_storage_version(self): + version = self.storage_version() + if version != libbe.storage.STORAGE_VERSION: + raise base.InvalidStorageVersion( + version, libbe.storage.STORAGE_VERSION) + + def storage_version(self, revision=None): + url = urlparse.urljoin(self.repo, 'version') + page,final_url,info = self.get_post_url( + url, get=True, data_dict={'revision':revision}) + return page.rstrip('\n') + +if TESTING == True: + class GetPostUrlTestCase (unittest.TestCase): + """Test cases for get_post_url()""" + def test_get(self): + url = 'http://bugseverywhere.org/be/show/HomePage' + page,final_url,info = get_post_url(url=url) + self.failUnless(final_url == url, + 'Redirect?\n Expected: "%s"\n Got: "%s"' + % (url, final_url)) + def test_get_redirect(self): + url = 'http://bugseverywhere.org' + expected = 'http://bugseverywhere.org/be/show/HomePage' + page,final_url,info = get_post_url(url=url) + self.failUnless(final_url == expected, + 'Redirect?\n Expected: "%s"\n Got: "%s"' + % (expected, final_url)) + + class TestingHTTP (HTTP): + name = 'TestingHTTP' + def __init__(self, repo, *args, **kwargs): + self._storage_backend = base.VersionedStorage(repo) + self.app = libbe.command.serve.ServerApp( + storage=self._storage_backend) + HTTP.__init__(self, repo='http://localhost:8000/', *args, **kwargs) + self.intitialized = False + # duplicated from libbe.storage.serve.WSGITestCase + self.default_environ = { + 'REQUEST_METHOD': 'GET', # 'POST', 'HEAD' + 'SCRIPT_NAME':'', + 'PATH_INFO': '', + #'QUERY_STRING':'', # may be empty or absent + #'CONTENT_TYPE':'', # may be empty or absent + #'CONTENT_LENGTH':'', # may be empty or absent + 'SERVER_NAME':'example.com', + 'SERVER_PORT':'80', + 'SERVER_PROTOCOL':'HTTP/1.1', + 'wsgi.version':(1,0), + 'wsgi.url_scheme':'http', + 'wsgi.input':StringIO.StringIO(), + 'wsgi.errors':StringIO.StringIO(), + 'wsgi.multithread':False, + 'wsgi.multiprocess':False, + 'wsgi.run_once':False, + } + def getURL(self, app, path='/', method='GET', data=None, + scheme='http', environ={}): + # duplicated from libbe.storage.serve.WSGITestCase + env = copy.copy(self.default_environ) + env['PATH_INFO'] = path + env['REQUEST_METHOD'] = method + env['scheme'] = scheme + if data != None: + enc_data = urllib.urlencode(data) + if method == 'POST': + env['CONTENT_LENGTH'] = len(enc_data) + env['wsgi.input'] = StringIO.StringIO(enc_data) + else: + assert method in ['GET', 'HEAD'], method + env['QUERY_STRING'] = enc_data + for key,value in environ.items(): + env[key] = value + return ''.join(app(env, self.start_response)) + def start_response(self, status, response_headers, exc_info=None): + self.status = status + self.response_headers = response_headers + self.exc_info = exc_info + def get_post_url(self, url, get=True, data_dict=None, headers=[]): + if get == True: + method = 'GET' + else: + method = 'POST' + scheme,netloc,path,params,query,fragment = urlparse.urlparse(url) + environ = {} + for header_name,header_value in headers: + environ['HTTP_%s' % header_name] = header_value + output = self.getURL( + self.app, path, method, data_dict, scheme, environ) + if self.status != '200 OK': + class __estr (object): + def __init__(self, string): + self.string = string + self.code = int(string.split()[0]) + def __str__(self): + return self.string + error = __estr(self.status) + raise InvalidURL(error=error, url=url, msg=output) + info = dict(self.response_headers) + return (output, url, info) + def _init(self): + try: + HTTP._init(self) + raise AssertionError + except base.NotSupported: + pass + self._storage_backend._init() + def _destroy(self): + try: + HTTP._destroy(self) + raise AssertionError + except base.NotSupported: + pass + self._storage_backend._destroy() + def _connect(self): + self._storage_backend._connect() + HTTP._connect(self) + def _disconnect(self): + HTTP._disconnect(self) + self._storage_backend._disconnect() + + + base.make_versioned_storage_testcase_subclasses( + TestingHTTP, sys.modules[__name__]) + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/storage/util/__init__.py b/libbe/storage/util/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/libbe/storage/util/__init__.py diff --git a/libbe/storage/util/config.py b/libbe/storage/util/config.py new file mode 100644 index 0000000..724d2d3 --- /dev/null +++ b/libbe/storage/util/config.py @@ -0,0 +1,114 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Create, save, and load the per-user config file at :func:`path`. +""" + +import ConfigParser +import codecs +import os.path + +import libbe +import libbe.util.encoding +if libbe.TESTING == True: + import doctest + + +default_encoding = libbe.util.encoding.get_filesystem_encoding() +"""Default filesystem encoding. + +Initialized with :func:`libbe.util.encoding.get_filesystem_encoding`. +""" + +def path(): + """Return the path to the per-user config file. + """ + return os.path.expanduser("~/.bugs_everywhere") + +def set_val(name, value, section="DEFAULT", encoding=None): + """Set a value in the per-user config file. + + Parameters + ---------- + name : str + The name of the value to set. + value : str or None + The new value to set (or None to delete the value). + section : str + The section to store the name/value in. + encoding : str + The config file's encoding, defaults to :data:`default_encoding`. + """ + if encoding == None: + encoding = default_encoding + config = ConfigParser.ConfigParser() + if os.path.exists(path()) == False: # touch file or config + open(path(), 'w').close() # read chokes on missing file + f = codecs.open(path(), 'r', encoding) + config.readfp(f, path()) + f.close() + if value is not None: + config.set(section, name, value) + else: + config.remove_option(section, name) + f = codecs.open(path(), 'w', encoding) + config.write(f) + f.close() + +def get_val(name, section="DEFAULT", default=None, encoding=None): + """Get a value from the per-user config file + + Parameters + ---------- + name : str + The name of the value to set. + section : str + The section to store the name/value in. + default : + The value to return if `name` is not set. + encoding : str + The config file's encoding, defaults to :data:`default_encoding`. + + Examples + -------- + + >>> get_val("junk") is None + True + >>> set_val("junk", "random") + >>> get_val("junk") + u'random' + >>> set_val("junk", None) + >>> get_val("junk") is None + True + """ + if os.path.exists(path()): + if encoding == None: + encoding = default_encoding + config = ConfigParser.ConfigParser() + f = codecs.open(path(), 'r', encoding) + config.readfp(f, path()) + f.close() + try: + return config.get(section, name) + except ConfigParser.NoOptionError: + return default + else: + return default + +if libbe.TESTING == True: + suite = doctest.DocTestSuite() diff --git a/libbe/storage/util/mapfile.py b/libbe/storage/util/mapfile.py new file mode 100644 index 0000000..55863d7 --- /dev/null +++ b/libbe/storage/util/mapfile.py @@ -0,0 +1,146 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Serializing and deserializing dictionaries of parameters. + +The serialized "mapfiles" should be clear, flat-text strings, and allow +easy merging of independent/conflicting changes. +""" + +import errno +import os.path +import types +import yaml + +import libbe +if libbe.TESTING == True: + import doctest + + +class IllegalKey(Exception): + def __init__(self, key): + Exception.__init__(self, 'Illegal key "%s"' % key) + self.key = key + +class IllegalValue(Exception): + def __init__(self, value): + Exception.__init__(self, 'Illegal value "%s"' % value) + self.value = value + +class InvalidMapfileContents(Exception): + def __init__(self, contents): + Exception.__init__(self, 'Invalid YAML contents') + self.contents = contents + +def generate(map): + """Generate a YAML mapfile content string. + + Examples + -------- + + >>> generate({'q':'p'}) + 'q: p\\n\\n' + >>> generate({'q':u'Fran\u00e7ais'}) + 'q: Fran\\xc3\\xa7ais\\n\\n' + >>> generate({'q':u'hello'}) + 'q: hello\\n\\n' + >>> generate({'q=':'p'}) + Traceback (most recent call last): + IllegalKey: Illegal key "q=" + >>> generate({'q:':'p'}) + Traceback (most recent call last): + IllegalKey: Illegal key "q:" + >>> generate({'q\\n':'p'}) + Traceback (most recent call last): + IllegalKey: Illegal key "q\\n" + >>> generate({'':'p'}) + Traceback (most recent call last): + IllegalKey: Illegal key "" + >>> generate({'>q':'p'}) + Traceback (most recent call last): + IllegalKey: Illegal key ">q" + >>> generate({'q':'p\\n'}) + Traceback (most recent call last): + IllegalValue: Illegal value "p\\n" + + See Also + -------- + parse : inverse + """ + keys = map.keys() + keys.sort() + for key in keys: + try: + assert not key.startswith('>') + assert('\n' not in key) + assert('=' not in key) + assert(':' not in key) + assert(len(key) > 0) + except AssertionError: + raise IllegalKey(unicode(key).encode('unicode_escape')) + if '\n' in map[key]: + raise IllegalValue(unicode(map[key]).encode('unicode_escape')) + + lines = [] + for key in keys: + lines.append(yaml.safe_dump({key: map[key]}, + default_flow_style=False, + allow_unicode=True)) + lines.append('') + return '\n'.join(lines) + +def parse(contents): + """Parse a YAML mapfile string. + + Examples + -------- + + >>> parse('q: p\\n\\n')['q'] + 'p' + >>> parse('q: \\'p\\'\\n\\n')['q'] + 'p' + >>> contents = generate({'a':'b', 'c':'d', 'e':'f'}) + >>> dict = parse(contents) + >>> dict['a'] + 'b' + >>> dict['c'] + 'd' + >>> dict['e'] + 'f' + >>> contents = generate({'q':u'Fran\u00e7ais'}) + >>> dict = parse(contents) + >>> dict['q'] + u'Fran\\xe7ais' + >>> dict = parse('a!') + Traceback (most recent call last): + ... + InvalidMapfileContents: Invalid YAML contents + + See Also + -------- + generate : inverse + + """ + c = yaml.load(contents) + if type(c) == types.StringType: + raise InvalidMapfileContents( + 'Unable to parse YAML (BE format missmatch?):\n\n%s' % contents) + return c or {} + +if libbe.TESTING == True: + suite = doctest.DocTestSuite() diff --git a/libbe/storage/util/properties.py b/libbe/storage/util/properties.py new file mode 100644 index 0000000..b5681b1 --- /dev/null +++ b/libbe/storage/util/properties.py @@ -0,0 +1,666 @@ +# Bugs Everywhere - a distributed bugtracker +# Copyright (C) 2008-2010 Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Provides a series of useful decorators for defining various types +of properties. + +For example usage, consider the unittests at the end of the module. + +Notes +----- + +See `PEP 318` and Michele Simionato's `decorator documentation` for +more information on decorators. + +.. _PEP 318: http://www.python.org/dev/peps/pep-0318/ +.. _decorator documentation: http://www.phyast.pitt.edu/~micheles/python/documentation.html + +See Also +-------- +:mod:`libbe.storage.util.settings_object` : bundle properties into a convenient package + +""" + +import copy +import types + +import libbe +if libbe.TESTING == True: + import unittest + + +class ValueCheckError (ValueError): + def __init__(self, name, value, allowed): + action = "in" # some list of allowed values + if type(allowed) == types.FunctionType: + action = "allowed by" # some allowed-value check function + msg = "%s not %s %s for %s" % (value, action, allowed, name) + ValueError.__init__(self, msg) + self.name = name + self.value = value + self.allowed = allowed + +def Property(funcs): + """ + End a chain of property decorators, returning a property. + """ + args = {} + args["fget"] = funcs.get("fget", None) + args["fset"] = funcs.get("fset", None) + args["fdel"] = funcs.get("fdel", None) + args["doc"] = funcs.get("doc", None) + + #print "Creating a property with" + #for key, val in args.items(): print key, value + return property(**args) + +def doc_property(doc=None): + """ + Add a docstring to a chain of property decorators. + """ + def decorator(funcs=None): + """ + Takes either a dict of funcs {"fget":fnX, "fset":fnY, ...} + or a function fn() returning such a dict. + """ + if hasattr(funcs, "__call__"): + funcs = funcs() # convert from function-arg to dict + funcs["doc"] = doc + return funcs + return decorator + +def local_property(name, null=None, mutable_null=False): + """ + Define get/set access to per-parent-instance local storage. Uses + ._<name>_value to store the value for a particular owner instance. + If the ._<name>_value attribute does not exist, returns null. + + If mutable_null == True, we only release deepcopies of the null to + the outside world. + """ + def decorator(funcs): + if hasattr(funcs, "__call__"): + funcs = funcs() + fget = funcs.get("fget", None) + fset = funcs.get("fset", None) + def _fget(self): + if fget is not None: + fget(self) + if mutable_null == True: + ret_null = copy.deepcopy(null) + else: + ret_null = null + value = getattr(self, "_%s_value" % name, ret_null) + return value + def _fset(self, value): + setattr(self, "_%s_value" % name, value) + if fset is not None: + fset(self, value) + funcs["fget"] = _fget + funcs["fset"] = _fset + funcs["name"] = name + return funcs + return decorator + +def settings_property(name, null=None): + """ + Similar to local_property, except where local_property stores the + value in instance._<name>_value, settings_property stores the + value in instance.settings[name]. + """ + def decorator(funcs): + if hasattr(funcs, "__call__"): + funcs = funcs() + fget = funcs.get("fget", None) + fset = funcs.get("fset", None) + def _fget(self): + if fget is not None: + fget(self) + value = self.settings.get(name, null) + return value + def _fset(self, value): + self.settings[name] = value + if fset is not None: + fset(self, value) + funcs["fget"] = _fget + funcs["fset"] = _fset + funcs["name"] = name + return funcs + return decorator + + +# Allow comparison and caching with _original_ values for mutables, +# since +# +# >>> a = [] +# >>> b = a +# >>> b.append(1) +# >>> a +# [1] +# >>> a==b +# True +def _hash_mutable_value(value): + return repr(value) +def _init_mutable_property_cache(self): + if not hasattr(self, "_mutable_property_cache_hash"): + # first call to _fget for any mutable property + self._mutable_property_cache_hash = {} + self._mutable_property_cache_copy = {} +def _set_cached_mutable_property(self, cacher_name, property_name, value): + _init_mutable_property_cache(self) + self._mutable_property_cache_hash[(cacher_name, property_name)] = \ + _hash_mutable_value(value) + self._mutable_property_cache_copy[(cacher_name, property_name)] = \ + copy.deepcopy(value) +def _get_cached_mutable_property(self, cacher_name, property_name, default=None): + _init_mutable_property_cache(self) + if (cacher_name, property_name) not in self._mutable_property_cache_copy: + return default + return self._mutable_property_cache_copy[(cacher_name, property_name)] +def _cmp_cached_mutable_property(self, cacher_name, property_name, value, default=None): + _init_mutable_property_cache(self) + if (cacher_name, property_name) not in self._mutable_property_cache_hash: + _set_cached_mutable_property(self, cacher_name, property_name, default) + old_hash = self._mutable_property_cache_hash[(cacher_name, property_name)] + return cmp(_hash_mutable_value(value), old_hash) + + +def defaulting_property(default=None, null=None, + mutable_default=False): + """ + Define a default value for get access to a property. + If the stored value is null, then default is returned. + + If mutable_default == True, we only release deepcopies of the + default to the outside world. + + null should never escape to the outside world, so don't worry + about it being a mutable. + """ + def decorator(funcs): + if hasattr(funcs, "__call__"): + funcs = funcs() + fget = funcs.get("fget") + fset = funcs.get("fset") + name = funcs.get("name", "<unknown>") + def _fget(self): + value = fget(self) + if value == null: + if mutable_default == True: + return copy.deepcopy(default) + else: + return default + return value + def _fset(self, value): + if value == default: + value = null + fset(self, value) + funcs["fget"] = _fget + funcs["fset"] = _fset + return funcs + return decorator + +def fn_checked_property(value_allowed_fn): + """ + Define allowed values for get/set access to a property. + """ + def decorator(funcs): + if hasattr(funcs, "__call__"): + funcs = funcs() + fget = funcs.get("fget") + fset = funcs.get("fset") + name = funcs.get("name", "<unknown>") + def _fget(self): + value = fget(self) + if value_allowed_fn(value) != True: + raise ValueCheckError(name, value, value_allowed_fn) + return value + def _fset(self, value): + if value_allowed_fn(value) != True: + raise ValueCheckError(name, value, value_allowed_fn) + fset(self, value) + funcs["fget"] = _fget + funcs["fset"] = _fset + return funcs + return decorator + +def checked_property(allowed=[]): + """ + Define allowed values for get/set access to a property. + """ + def decorator(funcs): + if hasattr(funcs, "__call__"): + funcs = funcs() + fget = funcs.get("fget") + fset = funcs.get("fset") + name = funcs.get("name", "<unknown>") + def _fget(self): + value = fget(self) + if value not in allowed: + raise ValueCheckError(name, value, allowed) + return value + def _fset(self, value): + if value not in allowed: + raise ValueCheckError(name, value, allowed) + fset(self, value) + funcs["fget"] = _fget + funcs["fset"] = _fset + return funcs + return decorator + +def cached_property(generator, initVal=None, mutable=False): + """ + Allow caching of values generated by generator(instance), where + instance is the instance to which this property belongs. Uses + ._<name>_cache to store a cache flag for a particular owner + instance. + + When the cache flag is True or missing and the stored value is + initVal, the first fget call triggers the generator function, + whose output is stored in _<name>_cached_value. That and + subsequent calls to fget will return this cached value. + + If the input value is no longer initVal (e.g. a value has been + loaded from disk or set with fset), that value overrides any + cached value, and this property has no effect. + + When the cache flag is False and the stored value is initVal, the + generator is not cached, but is called on every fget. + + The cache flag is missing on initialization. Particular instances + may override by setting their own flag. + + In the case that mutable == True, all caching is disabled and the + generator is called whenever the cached value would otherwise be + used. + """ + def decorator(funcs): + if hasattr(funcs, "__call__"): + funcs = funcs() + fget = funcs.get("fget") + name = funcs.get("name", "<unknown>") + def _fget(self): + cache = getattr(self, "_%s_cache" % name, True) + value = fget(self) + if value == initVal: + if cache == True and mutable == False: + if hasattr(self, "_%s_cached_value" % name): + value = getattr(self, "_%s_cached_value" % name) + else: + value = generator(self) + setattr(self, "_%s_cached_value" % name, value) + else: + value = generator(self) + return value + funcs["fget"] = _fget + return funcs + return decorator + +def primed_property(primer, initVal=None, unprimeableVal=None): + """ + Just like a cached_property, except that instead of returning a + new value and running fset to cache it, the primer attempts some + background manipulation (e.g. loads data into instance.settings) + such that a _second_ pass through fget succeeds. If the second + pass doesn't succeed (e.g. no readable storage), we give up and + return unprimeableVal. + + The 'cache' flag becomes a 'prime' flag, with priming taking place + whenever ._<name>_prime is True, or is False or missing and + value == initVal. + """ + def decorator(funcs): + if hasattr(funcs, "__call__"): + funcs = funcs() + fget = funcs.get("fget") + name = funcs.get("name", "<unknown>") + def _fget(self): + prime = getattr(self, "_%s_prime" % name, False) + if prime == False: + value = fget(self) + if prime == True or (prime == False and value == initVal): + primer(self) + value = fget(self) + if prime == False and value == initVal: + return unprimeableVal + return value + funcs["fget"] = _fget + return funcs + return decorator + +def change_hook_property(hook, mutable=False, default=None): + """Call the function `hook` whenever a value different from the + current value is set. + + This is useful for saving changes to disk, etc. This function is + called *after* the new value has been stored, allowing you to + change the stored value if you want. + + In the case of mutables, things are slightly trickier. Because + the property-owning class has no way of knowing when the value + changes. We work around this by caching a private deepcopy of the + mutable value, and checking for changes whenever the property is + set (obviously) or retrieved (to check for external changes). So + long as you're conscientious about accessing the property after + making external modifications, mutability won't be a problem:: + + t.x.append(5) # external modification + t.x # dummy access notices change and triggers hook + + See :class:`testChangeHookMutableProperty` for an example of the + expected behavior. + + Parameters + ---------- + hook : fn + `hook(instance, old_value, new_value)`, where `instance` is a + reference to the class instance to which this property belongs. + """ + def decorator(funcs): + if hasattr(funcs, "__call__"): + funcs = funcs() + fget = funcs.get("fget") + fset = funcs.get("fset") + name = funcs.get("name", "<unknown>") + def _fget(self, new_value=None, from_fset=False): # only used if mutable == True + if from_fset == True: + value = new_value # compare new value with cached + else: + value = fget(self) # compare current value with cached + if _cmp_cached_mutable_property(self, "change hook property", name, value, default) != 0: + # there has been a change, cache new value + old_value = _get_cached_mutable_property(self, "change hook property", name, default) + _set_cached_mutable_property(self, "change hook property", name, value) + if from_fset == True: # return previously cached value + value = old_value + else: # the value changed while we weren't looking + hook(self, old_value, value) + return value + def _fset(self, value): + if mutable == True: # get cached previous value + old_value = _fget(self, new_value=value, from_fset=True) + else: + old_value = fget(self) + fset(self, value) + if value != old_value: + hook(self, old_value, value) + if mutable == True: + funcs["fget"] = _fget + funcs["fset"] = _fset + return funcs + return decorator + +if libbe.TESTING == True: + class DecoratorTests(unittest.TestCase): + def testLocalDoc(self): + class Test(object): + @Property + @doc_property("A fancy property") + def x(): + return {} + self.failUnless(Test.x.__doc__ == "A fancy property", + Test.x.__doc__) + def testLocalProperty(self): + class Test(object): + @Property + @local_property(name="LOCAL") + def x(): + return {} + t = Test() + self.failUnless(t.x == None, str(t.x)) + t.x = 'z' # the first set initializes ._LOCAL_value + self.failUnless(t.x == 'z', str(t.x)) + self.failUnless("_LOCAL_value" in dir(t), dir(t)) + self.failUnless(t._LOCAL_value == 'z', t._LOCAL_value) + def testSettingsProperty(self): + class Test(object): + @Property + @settings_property(name="attr") + def x(): + return {} + def __init__(self): + self.settings = {} + t = Test() + self.failUnless(t.x == None, str(t.x)) + t.x = 'z' # the first set initializes ._LOCAL_value + self.failUnless(t.x == 'z', str(t.x)) + self.failUnless("attr" in t.settings, t.settings) + self.failUnless(t.settings["attr"] == 'z', t.settings["attr"]) + def testDefaultingLocalProperty(self): + class Test(object): + @Property + @defaulting_property(default='y', null='x') + @local_property(name="DEFAULT", null=5) + def x(): return {} + t = Test() + self.failUnless(t.x == 5, str(t.x)) + t.x = 'x' + self.failUnless(t.x == 'y', str(t.x)) + t.x = 'y' + self.failUnless(t.x == 'y', str(t.x)) + t.x = 'z' + self.failUnless(t.x == 'z', str(t.x)) + t.x = 5 + self.failUnless(t.x == 5, str(t.x)) + def testCheckedLocalProperty(self): + class Test(object): + @Property + @checked_property(allowed=['x', 'y', 'z']) + @local_property(name="CHECKED") + def x(): return {} + def __init__(self): + self._CHECKED_value = 'x' + t = Test() + self.failUnless(t.x == 'x', str(t.x)) + try: + t.x = None + e = None + except ValueCheckError, e: + pass + self.failUnless(type(e) == ValueCheckError, type(e)) + def testTwoCheckedLocalProperties(self): + class Test(object): + @Property + @checked_property(allowed=['x', 'y', 'z']) + @local_property(name="X") + def x(): return {} + + @Property + @checked_property(allowed=['a', 'b', 'c']) + @local_property(name="A") + def a(): return {} + def __init__(self): + self._A_value = 'a' + self._X_value = 'x' + t = Test() + try: + t.x = 'a' + e = None + except ValueCheckError, e: + pass + self.failUnless(type(e) == ValueCheckError, type(e)) + t.x = 'x' + t.x = 'y' + t.x = 'z' + try: + t.a = 'x' + e = None + except ValueCheckError, e: + pass + self.failUnless(type(e) == ValueCheckError, type(e)) + t.a = 'a' + t.a = 'b' + t.a = 'c' + def testFnCheckedLocalProperty(self): + class Test(object): + @Property + @fn_checked_property(lambda v : v in ['x', 'y', 'z']) + @local_property(name="CHECKED") + def x(): return {} + def __init__(self): + self._CHECKED_value = 'x' + t = Test() + self.failUnless(t.x == 'x', str(t.x)) + try: + t.x = None + e = None + except ValueCheckError, e: + pass + self.failUnless(type(e) == ValueCheckError, type(e)) + def testCachedLocalProperty(self): + class Gen(object): + def __init__(self): + self.i = 0 + def __call__(self, owner): + self.i += 1 + return self.i + class Test(object): + @Property + @cached_property(generator=Gen(), initVal=None) + @local_property(name="CACHED") + def x(): return {} + t = Test() + self.failIf("_CACHED_cache" in dir(t), + getattr(t, "_CACHED_cache", None)) + self.failUnless(t.x == 1, t.x) + self.failUnless(t.x == 1, t.x) + self.failUnless(t.x == 1, t.x) + t.x = 8 + self.failUnless(t.x == 8, t.x) + self.failUnless(t.x == 8, t.x) + t._CACHED_cache = False # Caching is off, but the stored value + val = t.x # is 8, not the initVal (None), so we + self.failUnless(val == 8, val) # get 8. + t._CACHED_value = None # Now we've set the stored value to None + val = t.x # so future calls to fget (like this) + self.failUnless(val == 2, val) # will call the generator every time... + val = t.x + self.failUnless(val == 3, val) + val = t.x + self.failUnless(val == 4, val) + t._CACHED_cache = True # We turn caching back on, and get + self.failUnless(t.x == 1, str(t.x)) # the original cached value. + del t._CACHED_cached_value # Removing that value forces a + self.failUnless(t.x == 5, str(t.x)) # single cache-regenerating call + self.failUnless(t.x == 5, str(t.x)) # to the genenerator, after which + self.failUnless(t.x == 5, str(t.x)) # we get the new cached value. + def testPrimedLocalProperty(self): + class Test(object): + def prime(self): + self.settings["PRIMED"] = self.primeVal + @Property + @primed_property(primer=prime, initVal=None, unprimeableVal=2) + @settings_property(name="PRIMED") + def x(): return {} + def __init__(self): + self.settings={} + self.primeVal = "initialized" + t = Test() + self.failIf("_PRIMED_prime" in dir(t), + getattr(t, "_PRIMED_prime", None)) + self.failUnless(t.x == "initialized", t.x) + t.x = 1 + self.failUnless(t.x == 1, t.x) + t.x = None + self.failUnless(t.x == "initialized", t.x) + t._PRIMED_prime = True + t.x = 3 + self.failUnless(t.x == "initialized", t.x) + t._PRIMED_prime = False + t.x = 3 + self.failUnless(t.x == 3, t.x) + # test unprimableVal + t.x = None + t.primeVal = None + self.failUnless(t.x == 2, t.x) + def testChangeHookLocalProperty(self): + class Test(object): + def _hook(self, old, new): + self.old = old + self.new = new + + @Property + @change_hook_property(_hook) + @local_property(name="HOOKED") + def x(): return {} + t = Test() + t.x = 1 + self.failUnless(t.old == None, t.old) + self.failUnless(t.new == 1, t.new) + t.x = 1 + self.failUnless(t.old == None, t.old) + self.failUnless(t.new == 1, t.new) + t.x = 2 + self.failUnless(t.old == 1, t.old) + self.failUnless(t.new == 2, t.new) + def testChangeHookMutableProperty(self): + class Test(object): + def _hook(self, old, new): + self.old = old + self.new = new + self.hook_calls += 1 + + @Property + @change_hook_property(_hook, mutable=True) + @local_property(name="HOOKED") + def x(): return {} + t = Test() + t.hook_calls = 0 + t.x = [] + self.failUnless(t.old == None, t.old) + self.failUnless(t.new == [], t.new) + self.failUnless(t.hook_calls == 1, t.hook_calls) + a = t.x + a.append(5) + t.x = a + self.failUnless(t.old == [], t.old) + self.failUnless(t.new == [5], t.new) + self.failUnless(t.hook_calls == 2, t.hook_calls) + t.x = [] + self.failUnless(t.old == [5], t.old) + self.failUnless(t.new == [], t.new) + self.failUnless(t.hook_calls == 3, t.hook_calls) + # now append without reassigning. this doesn't trigger the + # change, since we don't ever set t.x, only get it and mess + # with it. It does, however, update our t.new, since t.new = + # t.x and is not a static copy. + t.x.append(5) + self.failUnless(t.old == [5], t.old) + self.failUnless(t.new == [5], t.new) + self.failUnless(t.hook_calls == 3, t.hook_calls) + # however, the next t.x get _will_ notice the change... + a = t.x + self.failUnless(t.old == [], t.old) + self.failUnless(t.new == [5], t.new) + self.failUnless(t.hook_calls == 4, t.hook_calls) + t.x.append(6) # this append(6) is not noticed yet + self.failUnless(t.old == [], t.old) + self.failUnless(t.new == [5,6], t.new) + self.failUnless(t.hook_calls == 4, t.hook_calls) + # this append(7) is not noticed, but the t.x get causes the + # append(6) to be noticed + t.x.append(7) + self.failUnless(t.old == [5], t.old) + self.failUnless(t.new == [5,6,7], t.new) + self.failUnless(t.hook_calls == 5, t.hook_calls) + a = t.x # now the append(7) is noticed + self.failUnless(t.old == [5,6], t.old) + self.failUnless(t.new == [5,6,7], t.new) + self.failUnless(t.hook_calls == 6, t.hook_calls) + + suite = unittest.TestLoader().loadTestsFromTestCase(DecoratorTests) diff --git a/libbe/storage/util/settings_object.py b/libbe/storage/util/settings_object.py new file mode 100644 index 0000000..6e4da55 --- /dev/null +++ b/libbe/storage/util/settings_object.py @@ -0,0 +1,617 @@ +# Bugs Everywhere - a distributed bugtracker +# Copyright (C) 2008-2010 Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Provides :class:`SavedSettingsObject` implementing settings-dict +based property storage. + +See Also +-------- +:mod:`libbe.storage.util.properties` : underlying property definitions +""" + +import libbe +from properties import Property, doc_property, local_property, \ + defaulting_property, checked_property, fn_checked_property, \ + cached_property, primed_property, change_hook_property, \ + settings_property +if libbe.TESTING == True: + import doctest + import unittest + +class _Token (object): + """`Control' value class for properties. + + We want values that only mean something to the `settings_object` + module. + """ + pass + +class UNPRIMED (_Token): + "Property has not been primed (loaded)." + pass + +class EMPTY (_Token): + """Property has been primed but has no user-set value, so use + default/generator value. + """ + pass + + +def prop_save_settings(self, old, new): + """The default action undertaken when a property changes. + """ + if self.storage != None and self.storage.is_writeable(): + self.save_settings() + +def prop_load_settings(self): + """The default action undertaken when an UNPRIMED property is + accessed. + + Attempt to run `.load_settings()`, which calls + `._setup_saved_settings()` internally. If `.storage` is + inaccessible, don't do anything. + """ + if self.storage != None and self.storage.is_readable(): + self.load_settings() + +# Some name-mangling routines for pretty printing setting names +def setting_name_to_attr_name(self, name): + """Convert keys to the `.settings` dict into their associated + SavedSettingsObject attribute names. + + Examples + -------- + + >>> print setting_name_to_attr_name(None,"User-id") + user_id + + See Also + -------- + attr_name_to_setting_name : inverse + """ + return name.lower().replace('-', '_') + +def attr_name_to_setting_name(self, name): + """Convert SavedSettingsObject attribute names to `.settings` dict + keys. + + Examples: + + >>> print attr_name_to_setting_name(None, "user_id") + User-id + + See Also + -------- + setting_name_to_attr_name : inverse + """ + return name.capitalize().replace('_', '-') + + +def versioned_property(name, doc, + default=None, generator=None, + change_hook=prop_save_settings, + mutable=False, + primer=prop_load_settings, + allowed=None, check_fn=None, + settings_properties=[], + required_saved_properties=[], + require_save=False): + """Combine the common decorators in a single function. + + Use zero or one (but not both) of default or generator, since a + working default will keep the generator from functioning. Use the + default if you know what you want the default value to be at + 'coding time'. Use the generator if you can write a function to + determine a valid default at run time. If both default and + generator are None, then the property will be a defaulting + property which defaults to None. + + allowed and check_fn have a similar relationship, although you can + use both of these if you want. allowed compares the proposed + value against a list determined at 'coding time' and check_fn + allows more flexible comparisons to take place at run time. + + Set require_save to True if you want to save the default/generated + value for a property, to protect against future changes. E.g., we + currently expect all comments to be 'text/plain' but in the future + we may want to default to 'text/html'. If we don't want the old + comments to be interpreted as 'text/html', we would require that + the content type be saved. + + change_hook, primer, settings_properties, and + required_saved_properties are only options to get their defaults + into our local scope. Don't mess with them. + + Set mutable=True if: + + * default is a mutable + * your generator function may return mutables + * you set change_hook and might have mutable property values + + See the docstrings in `libbe.properties` for details on how each of + these cases are handled. + + The value stored in `.settings[name]` will be + + * no value (or UNPRIMED) if the property has been neither set, + nor loaded as blank. + * EMPTY if the value has been loaded as blank. + * some value if the property has been either loaded or set. + """ + settings_properties.append(name) + if require_save == True: + required_saved_properties.append(name) + def decorator(funcs): + fulldoc = doc + if default != None or generator == None: + defaulting = defaulting_property(default=default, null=EMPTY, + mutable_default=mutable) + fulldoc += "\n\nThis property defaults to %s." % default + if generator != None: + cached = cached_property(generator=generator, initVal=EMPTY, + mutable=mutable) + fulldoc += "\n\nThis property is generated with %s." % generator + if check_fn != None: + fn_checked = fn_checked_property(value_allowed_fn=check_fn) + fulldoc += "\n\nThis property is checked with %s." % check_fn + if allowed != None: + checked = checked_property(allowed=allowed) + fulldoc += "\n\nThe allowed values for this property are: %s." \ + % (', '.join(allowed)) + hooked = change_hook_property(hook=change_hook, mutable=mutable, + default=EMPTY) + primed = primed_property(primer=primer, initVal=UNPRIMED, + unprimeableVal=EMPTY) + settings = settings_property(name=name, null=UNPRIMED) + docp = doc_property(doc=fulldoc) + deco = hooked(primed(settings(docp(funcs)))) + if default != None or generator == None: + deco = defaulting(deco) + if generator != None: + deco = cached(deco) + if check_fn != None: + deco = fn_checked(deco) + if allowed != None: + deco = checked(deco) + return Property(deco) + return decorator + +class SavedSettingsObject(object): + """Setup a framework for lazy saving and loading of `.settings` + properties. + + This is useful for BE objects with saved properties + (e.g. :class:`~libbe.bugdir.BugDir`, :class:`~libbe.bug.Bug`, + :class:`~libbe.comment.Comment`). For example usage, consider the + unittests at the end of the module. + + See Also + -------- + versioned_property, prop_save_settings, prop_load_settings + setting_name_to_attr_name, attr_name_to_setting_name + """ + # Keep a list of properties that may be stored in the .settings dict. + #settings_properties = [] + + # A list of properties that we save to disk, even if they were + # never set (in which case we save the default value). This + # protects against future changes in default values. + #required_saved_properties = [] + + _setting_name_to_attr_name = setting_name_to_attr_name + _attr_name_to_setting_name = attr_name_to_setting_name + + def __init__(self): + self.storage = None + self.settings = {} + + def load_settings(self): + """Load the settings from disk.""" + # Override. Must call ._setup_saved_settings({}) with + # from-storage settings. + self._setup_saved_settings({}) + + def _setup_saved_settings(self, settings=None): + """ + Sets up a settings dict loaded from storage. Fills in + all missing settings entries with EMPTY. + """ + if settings == None: + settings = {} + for property in self.settings_properties: + if property not in self.settings \ + or self.settings[property] == UNPRIMED: + if property in settings: + self.settings[property] = settings[property] + else: + self.settings[property] = EMPTY + + def save_settings(self): + """Save the settings to disk.""" + # Override. Should save the dict output of ._get_saved_settings() + settings = self._get_saved_settings() + pass # write settings to disk.... + + def _get_saved_settings(self): + """ + In order to avoid overwriting unread on-disk data, make sure + we've loaded anything sitting on the disk. In the current + implementation, all the settings are stored in a single file, + so we need to load _all_ the saved settings. Another approach + would be per-setting saves, in which case you could skip this + step, since any setting changes would have forced that setting + load already. + """ + settings = {} + for k in self.settings_properties: # force full load + if not k in self.settings or self.settings[k] == UNPRIMED: + value = getattr( + self, self._setting_name_to_attr_name(k)) + for k in self.settings_properties: + if k in self.settings and self.settings[k] != EMPTY: + settings[k] = self.settings[k] + elif k in self.required_saved_properties: + settings[k] = getattr( + self, self._setting_name_to_attr_name(k)) + return settings + + def clear_cached_setting(self, setting=None): + "If setting=None, clear *all* cached settings" + if setting != None: + if hasattr(self, "_%s_cached_value" % setting): + delattr(self, "_%s_cached_value" % setting) + else: + for setting in settings_properties: + self.clear_cached_setting(setting) + + +if libbe.TESTING == True: + import copy + + class TestStorage (list): + def __init__(self): + list.__init__(self) + self.readable = True + self.writeable = True + def is_readable(self): + return self.readable + def is_writeable(self): + return self.writeable + + class TestObject (SavedSettingsObject): + def load_settings(self): + self.load_count += 1 + if len(self.storage) == 0: + settings = {} + else: + settings = copy.deepcopy(self.storage[-1]) + self._setup_saved_settings(settings) + def save_settings(self): + settings = self._get_saved_settings() + self.storage.append(copy.deepcopy(settings)) + def __init__(self): + SavedSettingsObject.__init__(self) + self.load_count = 0 + self.storage = TestStorage() + + class SavedSettingsObjectTests(unittest.TestCase): + def testSimplePropertyDoc(self): + """Testing a minimal versioned property docstring""" + class Test (TestObject): + settings_properties = [] + required_saved_properties = [] + @versioned_property( + name="Content-type", + doc="A test property", + settings_properties=settings_properties, + required_saved_properties=required_saved_properties) + def content_type(): return {} + expected = "A test property\n\nThis property defaults to None." + self.failUnless(Test.content_type.__doc__ == expected, + Test.content_type.__doc__) + def testSimplePropertyFromMemory(self): + """Testing a minimal versioned property from memory""" + class Test (TestObject): + settings_properties = [] + required_saved_properties = [] + @versioned_property( + name="Content-type", + doc="A test property", + settings_properties=settings_properties, + required_saved_properties=required_saved_properties) + def content_type(): return {} + t = Test() + self.failUnless(len(t.settings) == 0, len(t.settings)) + # accessing t.content_type triggers the priming, but + # t.storage.is_readable() == False, so nothing happens. + t.storage.readable = False + self.failUnless(t.content_type == None, t.content_type) + self.failUnless(t.settings == {}, t.settings) + self.failUnless(len(t.settings) == 0, len(t.settings)) + self.failUnless(t.content_type == None, t.content_type) + # accessing t.content_type triggers the priming again, and + # now that t.storage.is_readable() == True, this fills out + # t.settings with EMPTY data. At this point there should + # be one load and no saves. + t.storage.readable = True + self.failUnless(t.content_type == None, t.content_type) + self.failUnless(len(t.settings) == 1, len(t.settings)) + self.failUnless(t.settings["Content-type"] == EMPTY, + t.settings["Content-type"]) + self.failUnless(t.content_type == None, t.content_type) + self.failUnless(t.load_count == 1, t.load_count) + self.failUnless(len(t.storage) == 0, len(t.storage)) + # an explicit call to load settings forces a reload, + # but nothing else changes. + t.load_settings() + self.failUnless(len(t.settings) == 1, len(t.settings)) + self.failUnless(t.settings["Content-type"] == EMPTY, + t.settings["Content-type"]) + self.failUnless(t.content_type == None, t.content_type) + self.failUnless(t.load_count == 2, t.load_count) + self.failUnless(len(t.storage) == 0, len(t.storage)) + # now we set a value + t.content_type = 5 + self.failUnless(t.settings["Content-type"] == 5, + t.settings["Content-type"]) + self.failUnless(t.load_count == 2, t.load_count) + self.failUnless(len(t.storage) == 1, len(t.storage)) + self.failUnless(t.storage == [{'Content-type':5}], t.storage) + # getting its value changes nothing + self.failUnless(t.content_type == 5, t.content_type) + self.failUnless(t.settings["Content-type"] == 5, + t.settings["Content-type"]) + self.failUnless(t.load_count == 2, t.load_count) + self.failUnless(len(t.storage) == 1, len(t.storage)) + self.failUnless(t.storage == [{'Content-type':5}], t.storage) + # now we set another value + t.content_type = "text/plain" + self.failUnless(t.content_type == "text/plain", t.content_type) + self.failUnless(t.settings["Content-type"] == "text/plain", + t.settings["Content-type"]) + self.failUnless(t.load_count == 2, t.load_count) + self.failUnless(len(t.storage) == 2, len(t.storage)) + self.failUnless(t.storage == [{'Content-type':5}, + {'Content-type':'text/plain'}], + t.storage) + # t._get_saved_settings() returns a dict of required or + # non-default values. + self.failUnless(t._get_saved_settings() == \ + {"Content-type":"text/plain"}, + t._get_saved_settings()) + # now we clear to the post-primed value + t.content_type = EMPTY + self.failUnless(t.settings["Content-type"] == EMPTY, + t.settings["Content-type"]) + self.failUnless(t.content_type == None, t.content_type) + self.failUnless(len(t.settings) == 1, len(t.settings)) + self.failUnless(t.settings["Content-type"] == EMPTY, + t.settings["Content-type"]) + self.failUnless(t._get_saved_settings() == {}, + t._get_saved_settings()) + self.failUnless(t.storage == [{'Content-type':5}, + {'Content-type':'text/plain'}, + {}], + t.storage) + def testSimplePropertyFromStorage(self): + """Testing a minimal versioned property from storage""" + class Test (TestObject): + settings_properties = [] + required_saved_properties = [] + @versioned_property( + name="prop-a", + doc="A test property", + settings_properties=settings_properties, + required_saved_properties=required_saved_properties) + def prop_a(): return {} + @versioned_property( + name="prop-b", + doc="Another test property", + settings_properties=settings_properties, + required_saved_properties=required_saved_properties) + def prop_b(): return {} + t = Test() + t.storage.append({'prop-a':'saved'}) + # setting prop-b forces a load (to check for changes), + # which also pulls in prop-a. + t.prop_b = 'new-b' + settings = {'prop-b':'new-b', 'prop-a':'saved'} + self.failUnless(t.settings == settings, t.settings) + self.failUnless(t._get_saved_settings() == settings, + t._get_saved_settings()) + # test that _get_saved_settings() works even when settings + # were _not_ loaded beforehand + t = Test() + t.storage.append({'prop-a':'saved'}) + settings ={'prop-a':'saved'} + self.failUnless(t.settings == {}, t.settings) + self.failUnless(t._get_saved_settings() == settings, + t._get_saved_settings()) + def testSimplePropertySetStorageSave(self): + """Set a property, then attach storage and save""" + class Test (TestObject): + settings_properties = [] + required_saved_properties = [] + @versioned_property( + name="prop-a", + doc="A test property", + settings_properties=settings_properties, + required_saved_properties=required_saved_properties) + def prop_a(): return {} + @versioned_property( + name="prop-b", + doc="Another test property", + settings_properties=settings_properties, + required_saved_properties=required_saved_properties) + def prop_b(): return {} + t = Test() + storage = t.storage + t.storage = None + t.prop_a = 'text/html' + t.storage = storage + t.save_settings() + self.failUnless(t.prop_a == 'text/html', t.prop_a) + self.failUnless(t.settings == {'prop-a':'text/html', + 'prop-b':EMPTY}, + t.settings) + self.failUnless(t.load_count == 1, t.load_count) + self.failUnless(len(t.storage) == 1, len(t.storage)) + self.failUnless(t.storage == [{'prop-a':'text/html'}], + t.storage) + def testDefaultingProperty(self): + """Testing a defaulting versioned property""" + class Test (TestObject): + settings_properties = [] + required_saved_properties = [] + @versioned_property( + name="Content-type", + doc="A test property", + default="text/plain", + settings_properties=settings_properties, + required_saved_properties=required_saved_properties) + def content_type(): return {} + t = Test() + self.failUnless(t.settings == {}, t.settings) + self.failUnless(t.content_type == "text/plain", t.content_type) + self.failUnless(t.settings == {"Content-type":EMPTY}, + t.settings) + self.failUnless(t.load_count == 1, t.load_count) + self.failUnless(len(t.storage) == 0, len(t.storage)) + self.failUnless(t._get_saved_settings() == {}, + t._get_saved_settings()) + t.content_type = "text/html" + self.failUnless(t.content_type == "text/html", + t.content_type) + self.failUnless(t.settings == {"Content-type":"text/html"}, + t.settings) + self.failUnless(t.load_count == 1, t.load_count) + self.failUnless(len(t.storage) == 1, len(t.storage)) + self.failUnless(t.storage == [{'Content-type':'text/html'}], + t.storage) + self.failUnless(t._get_saved_settings() == \ + {"Content-type":"text/html"}, + t._get_saved_settings()) + def testRequiredDefaultingProperty(self): + """Testing a required defaulting versioned property""" + class Test (TestObject): + settings_properties = [] + required_saved_properties = [] + @versioned_property( + name="Content-type", + doc="A test property", + default="text/plain", + settings_properties=settings_properties, + required_saved_properties=required_saved_properties, + require_save=True) + def content_type(): return {} + t = Test() + self.failUnless(t.settings == {}, t.settings) + self.failUnless(t.content_type == "text/plain", t.content_type) + self.failUnless(t.settings == {"Content-type":EMPTY}, + t.settings) + self.failUnless(t.load_count == 1, t.load_count) + self.failUnless(len(t.storage) == 0, len(t.storage)) + self.failUnless(t._get_saved_settings() == \ + {"Content-type":"text/plain"}, + t._get_saved_settings()) + t.content_type = "text/html" + self.failUnless(t.content_type == "text/html", + t.content_type) + self.failUnless(t.settings == {"Content-type":"text/html"}, + t.settings) + self.failUnless(t.load_count == 1, t.load_count) + self.failUnless(len(t.storage) == 1, len(t.storage)) + self.failUnless(t.storage == [{'Content-type':'text/html'}], + t.storage) + self.failUnless(t._get_saved_settings() == \ + {"Content-type":"text/html"}, + t._get_saved_settings()) + def testClassVersionedPropertyDefinition(self): + """Testing a class-specific _versioned property decorator""" + class Test (TestObject): + settings_properties = [] + required_saved_properties = [] + def _versioned_property( + settings_properties=settings_properties, + required_saved_properties=required_saved_properties, + **kwargs): + if "settings_properties" not in kwargs: + kwargs["settings_properties"] = settings_properties + if "required_saved_properties" not in kwargs: + kwargs["required_saved_properties"] = \ + required_saved_properties + return versioned_property(**kwargs) + @_versioned_property(name="Content-type", + doc="A test property", + default="text/plain", + require_save=True) + def content_type(): return {} + t = Test() + self.failUnless(t._get_saved_settings() == \ + {"Content-type":"text/plain"}, + t._get_saved_settings()) + self.failUnless(t.load_count == 1, t.load_count) + self.failUnless(len(t.storage) == 0, len(t.storage)) + t.content_type = "text/html" + self.failUnless(t._get_saved_settings() == \ + {"Content-type":"text/html"}, + t._get_saved_settings()) + self.failUnless(t.load_count == 1, t.load_count) + self.failUnless(len(t.storage) == 1, len(t.storage)) + self.failUnless(t.storage == [{'Content-type':'text/html'}], + t.storage) + def testMutableChangeHookedProperty(self): + """Testing a mutable change-hooked property""" + class Test (TestObject): + settings_properties = [] + required_saved_properties = [] + @versioned_property( + name="List-type", + doc="A test property", + mutable=True, + change_hook=prop_save_settings, + settings_properties=settings_properties, + required_saved_properties=required_saved_properties) + def list_type(): return {} + t = Test() + self.failUnless(len(t.storage) == 0, len(t.storage)) + self.failUnless(t.list_type == None, t.list_type) + self.failUnless(len(t.storage) == 0, len(t.storage)) + self.failUnless(t.settings["List-type"]==EMPTY, + t.settings["List-type"]) + t.list_type = [] + self.failUnless(t.settings["List-type"] == [], + t.settings["List-type"]) + self.failUnless(len(t.storage) == 1, len(t.storage)) + self.failUnless(t.storage == [{'List-type':[]}], + t.storage) + t.list_type.append(5) # external modification not detected yet + self.failUnless(len(t.storage) == 1, len(t.storage)) + self.failUnless(t.storage == [{'List-type':[]}], + t.storage) + self.failUnless(t.settings["List-type"] == [5], + t.settings["List-type"]) + self.failUnless(t.list_type == [5], t.list_type)# get triggers save + self.failUnless(len(t.storage) == 2, len(t.storage)) + self.failUnless(t.storage == [{'List-type':[]}, + {'List-type':[5]}], + t.storage) + + unitsuite = unittest.TestLoader().loadTestsFromTestCase( \ + SavedSettingsObjectTests) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/storage/util/upgrade.py b/libbe/storage/util/upgrade.py new file mode 100644 index 0000000..f3c4912 --- /dev/null +++ b/libbe/storage/util/upgrade.py @@ -0,0 +1,331 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +Handle conversion between the various BE storage formats. +""" + +import codecs +import os, os.path +import sys + +import libbe +import libbe.bug +import libbe.storage.util.mapfile as mapfile +from libbe.storage import STORAGE_VERSIONS, STORAGE_VERSION +#import libbe.storage.vcs # delay import to avoid cyclic dependency +import libbe.ui.util.editor +import libbe.util +import libbe.util.encoding as encoding +import libbe.util.id + + +class Upgrader (object): + "Class for converting between different on-disk BE storage formats." + initial_version = None + final_version = None + def __init__(self, repo): + import libbe.storage.vcs + + self.repo = repo + vcs_name = self._get_vcs_name() + if vcs_name == None: + vcs_name = 'None' + self.vcs = libbe.storage.vcs.vcs_by_name(vcs_name) + self.vcs.repo = self.repo + self.vcs.root() + + def get_path(self, *args): + """ + Return the absolute path using args relative to .be. + """ + dir = os.path.join(self.repo, '.be') + if len(args) == 0: + return dir + return os.path.join(dir, *args) + + def _get_vcs_name(self): + return None + + def check_initial_version(self): + path = self.get_path('version') + version = encoding.get_file_contents(path, decode=True).rstrip('\n') + assert version == self.initial_version, '%s: %s' % (path, version) + + def set_version(self): + path = self.get_path('version') + encoding.set_file_contents(path, self.final_version+'\n') + self.vcs._vcs_update(path) + + def upgrade(self): + print >> sys.stderr, 'upgrading bugdir from "%s" to "%s"' \ + % (self.initial_version, self.final_version) + self.check_initial_version() + self.set_version() + self._upgrade() + + def _upgrade(self): + raise NotImplementedError + + +class Upgrade_1_0_to_1_1 (Upgrader): + initial_version = "Bugs Everywhere Tree 1 0" + final_version = "Bugs Everywhere Directory v1.1" + def _get_vcs_name(self): + path = self.get_path('settings') + settings = encoding.get_file_contents(path) + for line in settings.splitlines(False): + fields = line.split('=') + if len(fields) == 2 and fields[0] == 'rcs_name': + return fields[1] + return None + + def _upgrade_mapfile(self, path): + contents = encoding.get_file_contents(path, decode=True) + old_format = False + for line in contents.splitlines(): + if len(line.split('=')) == 2: + old_format = True + break + if old_format == True: + # translate to YAML. + newlines = [] + for line in contents.splitlines(): + line = line.rstrip('\n') + if len(line) == 0: + continue + fields = line.split("=") + if len(fields) == 2: + key,value = fields + newlines.append('%s: "%s"' % (key, value.replace('"','\\"'))) + else: + newlines.append(line) + contents = '\n'.join(newlines) + # load the YAML and save + map = mapfile.parse(contents) + contents = mapfile.generate(map) + encoding.set_file_contents(path, contents) + self.vcs._vcs_update(path) + + def _upgrade(self): + """ + Comment value field "From" -> "Author". + Homegrown mapfile -> YAML. + """ + path = self.get_path('settings') + self._upgrade_mapfile(path) + for bug_uuid in os.listdir(self.get_path('bugs')): + path = self.get_path('bugs', bug_uuid, 'values') + self._upgrade_mapfile(path) + c_path = ['bugs', bug_uuid, 'comments'] + if not os.path.exists(self.get_path(*c_path)): + continue # no comments for this bug + for comment_uuid in os.listdir(self.get_path(*c_path)): + path_list = c_path + [comment_uuid, 'values'] + path = self.get_path(*path_list) + self._upgrade_mapfile(path) + settings = mapfile.parse( + encoding.get_file_contents(path)) + if 'From' in settings: + settings['Author'] = settings.pop('From') + encoding.set_file_contents( + path, mapfile.generate(settings)) + self.vcs._vcs_update(path) + + +class Upgrade_1_1_to_1_2 (Upgrader): + initial_version = "Bugs Everywhere Directory v1.1" + final_version = "Bugs Everywhere Directory v1.2" + def _get_vcs_name(self): + path = self.get_path('settings') + settings = mapfile.parse(encoding.get_file_contents(path)) + if 'rcs_name' in settings: + return settings['rcs_name'] + return None + + def _upgrade(self): + """ + BugDir settings field "rcs_name" -> "vcs_name". + """ + path = self.get_path('settings') + settings = mapfile.parse(encoding.get_file_contents(path)) + if 'rcs_name' in settings: + settings['vcs_name'] = settings.pop('rcs_name') + encoding.set_file_contents(path, mapfile.generate(settings)) + self.vcs._vcs_update(path) + +class Upgrade_1_2_to_1_3 (Upgrader): + initial_version = "Bugs Everywhere Directory v1.2" + final_version = "Bugs Everywhere Directory v1.3" + def __init__(self, *args, **kwargs): + Upgrader.__init__(self, *args, **kwargs) + self._targets = {} # key: target text,value: new target bug + + def _get_vcs_name(self): + path = self.get_path('settings') + settings = mapfile.parse(encoding.get_file_contents(path)) + if 'vcs_name' in settings: + return settings['vcs_name'] + return None + + def _save_bug_settings(self, bug): + # The target bugs don't have comments + path = self.get_path('bugs', bug.uuid, 'values') + if not os.path.exists(path): + self.vcs._add_path(path, directory=False) + path = self.get_path('bugs', bug.uuid, 'values') + mf = mapfile.generate(bug._get_saved_settings()) + encoding.set_file_contents(path, mf) + self.vcs._vcs_update(path) + + def _target_bug(self, target_text): + if target_text not in self._targets: + bug = libbe.bug.Bug(summary=target_text) + bug.severity = 'target' + self._targets[target_text] = bug + return self._targets[target_text] + + def _upgrade_bugdir_mapfile(self): + path = self.get_path('settings') + mf = encoding.get_file_contents(path) + if mf == libbe.util.InvalidObject: + return # settings file does not exist + settings = mapfile.parse(mf) + if 'target' in settings: + settings['target'] = self._target_bug(settings['target']).uuid + mf = mapfile.generate(settings) + encoding.set_file_contents(path, mf) + self.vcs._vcs_update(path) + + def _upgrade_bug_mapfile(self, bug_uuid): + import libbe.command.depend as dep + path = self.get_path('bugs', bug_uuid, 'values') + mf = encoding.get_file_contents(path) + if mf == libbe.util.InvalidObject: + return # settings file does not exist + settings = mapfile.parse(mf) + if 'target' in settings: + target_bug = self._target_bug(settings['target']) + + blocked_by_string = '%s%s' % (dep.BLOCKED_BY_TAG, bug_uuid) + dep._add_remove_extra_string(target_bug, blocked_by_string, add=True) + blocks_string = dep._generate_blocks_string(target_bug) + estrs = settings.get('extra_strings', []) + estrs.append(blocks_string) + settings['extra_strings'] = sorted(estrs) + + settings.pop('target') + mf = mapfile.generate(settings) + encoding.set_file_contents(path, mf) + self.vcs._vcs_update(path) + + def _upgrade(self): + """ + Bug value field "target" -> target bugs. + Bugdir value field "target" -> pointer to current target bug. + """ + for bug_uuid in os.listdir(self.get_path('bugs')): + self._upgrade_bug_mapfile(bug_uuid) + self._upgrade_bugdir_mapfile() + for bug in self._targets.values(): + self._save_bug_settings(bug) + +class Upgrade_1_3_to_1_4 (Upgrader): + initial_version = "Bugs Everywhere Directory v1.3" + final_version = "Bugs Everywhere Directory v1.4" + def _get_vcs_name(self): + path = self.get_path('settings') + settings = mapfile.parse(encoding.get_file_contents(path)) + if 'vcs_name' in settings: + return settings['vcs_name'] + return None + + def _upgrade(self): + """ + add new directory "./be/BUGDIR-UUID" + "./be/bugs" -> "./be/BUGDIR-UUID/bugs" + "./be/settings" -> "./be/BUGDIR-UUID/settings" + """ + self.repo = os.path.abspath(self.repo) + basenames = [p for p in os.listdir(self.get_path())] + if not 'bugs' in basenames and not 'settings' in basenames \ + and len([p for p in basenames if len(p)==36]) == 1: + return # the user has upgraded the directory. + basenames = [p for p in basenames if p in ['bugs','settings']] + uuid = libbe.util.id.uuid_gen() + add = [self.get_path(uuid)] + move = [(self.get_path(p), self.get_path(uuid, p)) for p in basenames] + msg = ['Upgrading BE directory version v1.3 to v1.4', + '', + "Because BE's VCS drivers don't support 'move',", + 'please make the following changes with your VCS', + 'and re-run BE. Note that you can choose a different', + 'bugdir UUID to preserve uniformity across branches', + 'of a distributed repository.' + '', + 'add', + ' ' + '\n '.join(add), + 'move', + ' ' + '\n '.join(['%s %s' % (a,b) for a,b in move]), + ] + self.vcs._cached_path_id.destroy() + raise Exception('Need user assistance\n%s' % '\n'.join(msg)) + + +upgraders = [Upgrade_1_0_to_1_1, + Upgrade_1_1_to_1_2, + Upgrade_1_2_to_1_3, + Upgrade_1_3_to_1_4] +upgrade_classes = {} +for upgrader in upgraders: + upgrade_classes[(upgrader.initial_version,upgrader.final_version)]=upgrader + +def upgrade(path, current_version, + target_version=STORAGE_VERSION): + """ + Call the appropriate upgrade function to convert current_version + to target_version. If a direct conversion function does not exist, + use consecutive conversion functions. + """ + if current_version not in STORAGE_VERSIONS: + raise NotImplementedError, \ + "Cannot handle version '%s' yet." % current_version + if target_version not in STORAGE_VERSIONS: + raise NotImplementedError, \ + "Cannot handle version '%s' yet." % current_version + + if (current_version, target_version) in upgrade_classes: + # direct conversion + upgrade_class = upgrade_classes[(current_version, target_version)] + u = upgrade_class(path) + u.upgrade() + else: + # consecutive single-step conversion + i = STORAGE_VERSIONS.index(current_version) + while True: + version_a = STORAGE_VERSIONS[i] + version_b = STORAGE_VERSIONS[i+1] + try: + upgrade_class = upgrade_classes[(version_a, version_b)] + except KeyError: + raise NotImplementedError, \ + "Cannot convert version '%s' to '%s' yet." \ + % (version_a, version_b) + u = upgrade_class(path) + u.upgrade() + if version_b == target_version: + break + i += 1 diff --git a/libbe/storage/vcs/__init__.py b/libbe/storage/vcs/__init__.py new file mode 100644 index 0000000..552d43e --- /dev/null +++ b/libbe/storage/vcs/__init__.py @@ -0,0 +1,41 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Define the Version Controlled System (VCS)-based +:class:`~libbe.storage.base.Storage` and +:class:`~libbe.storage.base.VersionedStorage` implementations. + +There is a base class (:class:`~libbe.storage.vcs.VCS`) translating +Storage language to VCS language, and a number of `VCS` implementations: + +* :class:`~libbe.storage.vcs.arch.Arch` +* :class:`~libbe.storage.vcs.bzr.Bzr` +* :class:`~libbe.storage.vcs.darcs.Darcs` +* :class:`~libbe.storage.vcs.git.Git` +* :class:`~libbe.storage.vcs.hg.Hg` + +The base `VCS` class also serves as a filesystem Storage backend (not +versioning) in the event that a user has no VCS installed. +""" + +import base + +set_preferred_vcs = base.set_preferred_vcs +vcs_by_name = base.vcs_by_name +detect_vcs = base.detect_vcs +installed_vcs = base.installed_vcs + +__all__ = [set_preferred_vcs, vcs_by_name, detect_vcs, installed_vcs] diff --git a/libbe/storage/vcs/arch.py b/libbe/storage/vcs/arch.py new file mode 100644 index 0000000..3a50414 --- /dev/null +++ b/libbe/storage/vcs/arch.py @@ -0,0 +1,441 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Ben Finney <benf@cybersource.com.au> +# Gianluca Montecchi <gian@grys.it> +# James Rowe <jnrowe@ukfsn.org> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""GNU Arch_ (tla) backend. + +.. _Arch: http://www.gnu.org/software/gnu-arch/ +""" + +import codecs +import os +import os.path +import re +import shutil +import sys +import time # work around http://mercurial.selenic.com/bts/issue618 + +import libbe +import libbe.ui.util.user +import libbe.storage.util.config +from libbe.util.id import uuid_gen +from libbe.util.subproc import CommandError +import base + +if libbe.TESTING == True: + import unittest + import doctest + + +class CantAddFile(Exception): + def __init__(self, file): + self.file = file + Exception.__init__(self, "Can't automatically add file %s" % file) + +DEFAULT_CLIENT = 'tla' + +client = libbe.storage.util.config.get_val( + 'arch_client', default=DEFAULT_CLIENT) + +def new(): + return Arch() + +class Arch(base.VCS): + """:class:`base.VCS` implementation for GNU Arch. + """ + name = 'arch' + client = client + _archive_name = None + _archive_dir = None + _tmp_archive = False + _project_name = None + _tmp_project = False + _arch_paramdir = os.path.expanduser('~/.arch-params') + + def __init__(self, *args, **kwargs): + base.VCS.__init__(self, *args, **kwargs) + self.versioned = True + self.interspersed_vcs_files = True + self.paranoid = False + self.__updated = [] # work around http://mercurial.selenic.com/bts/issue618 + + def _vcs_version(self): + status,output,error = self._u_invoke_client('--version') + version = '\n'.join(output.splitlines()[:2]) + return version + + def _vcs_detect(self, path): + """Detect whether a directory is revision-controlled using Arch""" + if self._u_search_parent_directories(path, '{arch}') != None : + libbe.storage.util.config.set_val('arch_client', client) + return True + return False + + def _vcs_init(self, path): + self._create_archive(path) + self._create_project(path) + self._add_project_code(path) + + def _create_archive(self, path): + """Create a temporary Arch archive in the directory PATH. This + archive will be removed by:: + + destroy->_vcs_destroy->_remove_archive + """ + # http://regexps.srparish.net/tutorial-tla/new-archive.html#Creating_a_New_Archive + assert self._archive_name == None + id = self.get_user_id() + name, email = libbe.ui.util.user.parse_user_id(id) + if email == None: + email = '%s@example.com' % name + trailer = '%s-%s' % ('bugs-everywhere-auto', uuid_gen()[0:8]) + self._archive_name = '%s--%s' % (email, trailer) + self._archive_dir = '/tmp/%s' % trailer + self._tmp_archive = True + self._u_invoke_client('make-archive', self._archive_name, + self._archive_dir, cwd=path) + + def _invoke_client(self, *args, **kwargs): + """Invoke the client on our archive. + """ + assert self._archive_name != None + command = args[0] + if len(args) > 1: + tailargs = args[1:] + else: + tailargs = [] + arglist = [command, '-A', self._archive_name] + arglist.extend(tailargs) + args = tuple(arglist) + return self._u_invoke_client(*args, **kwargs) + + def _remove_archive(self): + assert self._tmp_archive == True + assert self._archive_dir != None + assert self._archive_name != None + os.remove(os.path.join(self._arch_paramdir, + '=locations', self._archive_name)) + shutil.rmtree(self._archive_dir) + self._tmp_archive = False + self._archive_dir = False + self._archive_name = False + + def _create_project(self, path): + """ + Create a temporary Arch project in the directory PATH. This + project will be removed by + destroy->_vcs_destroy->_remove_project + """ + # http://mwolson.org/projects/GettingStartedWithArch.html + # http://regexps.srparish.net/tutorial-tla/new-project.html#Starting_a_New_Project + category = 'bugs-everywhere' + branch = 'mainline' + version = '0.1' + self._project_name = '%s--%s--%s' % (category, branch, version) + self._invoke_client('archive-setup', self._project_name, + cwd=path) + self._tmp_project = True + + def _remove_project(self): + assert self._tmp_project == True + assert self._project_name != None + assert self._archive_dir != None + shutil.rmtree(os.path.join(self._archive_dir, self._project_name)) + self._tmp_project = False + self._project_name = False + + def _archive_project_name(self): + assert self._archive_name != None + assert self._project_name != None + return '%s/%s' % (self._archive_name, self._project_name) + + def _adjust_naming_conventions(self, path): + """Adjust `Arch naming conventions`_ so ``.be`` is considered source + code. + + By default, Arch restricts source code filenames to:: + + ^[_=a-zA-Z0-9].*$ + + Since our bug directory ``.be`` doesn't satisfy these conventions, + we need to adjust them. The conventions are specified in:: + + project-root/{arch}/=tagging-method + + .. _Arch naming conventions: + http://regexps.srparish.net/tutorial-tla/naming-conventions.html + """ + tagpath = os.path.join(path, '{arch}', '=tagging-method') + lines_out = [] + f = codecs.open(tagpath, 'r', self.encoding) + for line in f: + if line.startswith('source '): + lines_out.append('source ^[._=a-zA-X0-9].*$\n') + else: + lines_out.append(line) + f.close() + f = codecs.open(tagpath, 'w', self.encoding) + f.write(''.join(lines_out)) + f.close() + + def _add_project_code(self, path): + # http://mwolson.org/projects/GettingStartedWithArch.html + # http://regexps.srparish.net/tutorial-tla/new-source.html + # http://regexps.srparish.net/tutorial-tla/importing-first.html + self._invoke_client('init-tree', self._project_name, + cwd=path) + self._adjust_naming_conventions(path) + self._invoke_client('import', '--summary', 'Began versioning', + cwd=path) + + def _vcs_destroy(self): + if self._tmp_project == True: + self._remove_project() + if self._tmp_archive == True: + self._remove_archive() + vcs_dir = os.path.join(self.repo, '{arch}') + if os.path.exists(vcs_dir): + shutil.rmtree(vcs_dir) + self._archive_name = None + + def _vcs_root(self, path): + if not os.path.isdir(path): + dirname = os.path.dirname(path) + else: + dirname = path + status,output,error = self._u_invoke_client('tree-root', dirname) + root = output.rstrip('\n') + + self._get_archive_project_name(root) + + return root + + def _get_archive_name(self, root): + status,output,error = self._u_invoke_client('archives') + lines = output.split('\n') + # e.g. output: + # jdoe@example.com--bugs-everywhere-auto-2008.22.24.52 + # /tmp/BEtestXXXXXX/rootdir + # (+ repeats) + for archive,location in zip(lines[::2], lines[1::2]): + if os.path.realpath(location) == os.path.realpath(root): + self._archive_name = archive + assert self._archive_name != None + + def _get_archive_project_name(self, root): + # get project names + status,output,error = self._u_invoke_client('tree-version', cwd=root) + # e.g output + # jdoe@example.com--bugs-everywhere-auto-2008.22.24.52/be--mainline--0.1 + archive_name,project_name = output.rstrip('\n').split('/') + self._archive_name = archive_name + self._project_name = project_name + + def _vcs_get_user_id(self): + try: + status,output,error = self._u_invoke_client('my-id') + return output.rstrip('\n') + except Exception, e: + if 'no arch user id set' in e.args[0]: + return None + else: + raise + + def _vcs_add(self, path): + self._u_invoke_client('add-id', path) + realpath = os.path.realpath(self._u_abspath(path)) + pathAdded = realpath in self._list_added(self.repo) + if self.paranoid and not pathAdded: + self._force_source(path) + + def _list_added(self, root): + assert os.path.exists(root) + assert os.access(root, os.X_OK) + root = os.path.realpath(root) + status,output,error = self._u_invoke_client('inventory', '--source', + '--both', '--all', root) + inv_str = output.rstrip('\n') + return [os.path.join(root, p) for p in inv_str.split('\n')] + + def _add_dir_rule(self, rule, dirname, root): + inv_path = os.path.join(dirname, '.arch-inventory') + f = codecs.open(inv_path, 'a', self.encoding) + f.write(rule) + f.close() + if os.path.realpath(inv_path) not in self._list_added(root): + paranoid = self.paranoid + self.paranoid = False + self.add(inv_path) + self.paranoid = paranoid + + def _force_source(self, path): + rule = 'source %s\n' % self._u_rel_path(path) + self._add_dir_rule(rule, os.path.dirname(path), self.repo) + if os.path.realpath(path) not in self._list_added(self.repo): + raise CantAddFile(path) + + def _vcs_remove(self, path): + if self._vcs_is_versioned(path): + self._u_invoke_client('delete-id', path) + arch_ids = os.path.join(self.repo, path, '.arch-ids') + if os.path.exists(arch_ids): + shutil.rmtree(arch_ids) + + def _vcs_update(self, path): + self.__updated.append(path) # work around http://mercurial.selenic.com/bts/issue618 + + def _vcs_is_versioned(self, path): + if '.arch-ids' in path: + return False + return True + + def _vcs_get_file_contents(self, path, revision=None): + if revision == None: + return base.VCS._vcs_get_file_contents(self, path, revision) + else: + relpath = self._file_find(path, revision, relpath=True) + return base.VCS._vcs_get_file_contents(self, relpath) + + def _file_find(self, path, revision, relpath=False): + try: + status,output,error = \ + self._invoke_client( + 'file-find', '--unescaped', path, revision) + path = output.rstrip('\n').splitlines()[-1] + except CommandError, e: + if e.status == 2 \ + and 'illegally formed changeset index' in e.stderr: + raise NotImplementedError( +"""Outstanding tla bug, see + https://bugs.launchpad.net/ubuntu/+source/tla/+bug/513472 +""") + raise + if relpath == True: + return path + return os.path.abspath(os.path.join(self.repo, path)) + + def _vcs_path(self, id, revision): + return self._u_find_id(id, revision) + + def _vcs_isdir(self, path, revision): + abspath = self._file_find(path, revision) + return os.path.isdir(abspath) + + def _vcs_listdir(self, path, revision): + abspath = self._file_find(path, revision) + return [p for p in os.listdir(abspath) if self._vcs_is_versioned(p)] + + def _vcs_commit(self, commitfile, allow_empty=False): + if allow_empty == False: + # arch applies empty commits without complaining, so check first + status,output,error = self._u_invoke_client('changes',expect=(0,1)) + if status == 0: + # work around http://mercurial.selenic.com/bts/issue618 + time.sleep(1) + for path in self.__updated: + os.utime(os.path.join(self.repo, path), None) + self.__updated = [] + status,output,error = self._u_invoke_client('changes',expect=(0,1)) + if status == 0: + # end work around + raise base.EmptyCommit() + summary,body = self._u_parse_commitfile(commitfile) + args = ['commit', '--summary', summary] + if body != None: + args.extend(['--log-message',body]) + status,output,error = self._u_invoke_client(*args) + revision = None + revline = re.compile('[*] committed (.*)') + match = revline.search(output) + assert match != None, output+error + assert len(match.groups()) == 1 + revpath = match.groups()[0] + assert not " " in revpath, revpath + assert revpath.startswith(self._archive_project_name()+'--') + revision = revpath[len(self._archive_project_name()+'--'):] + return revpath + + def _vcs_revision_id(self, index): + status,output,error = self._u_invoke_client('logs') + logs = output.splitlines() + first_log = logs.pop(0) + assert first_log == 'base-0', first_log + try: + if index > 0: + log = logs[index-1] + elif index < 0: + log = logs[index] + else: + return None + except IndexError: + return None + return '%s--%s' % (self._archive_project_name(), log) + + def _diff(self, revision): + status,output,error = self._u_invoke_client( + 'diff', '--summary', '--unescaped', revision, expect=(0,1)) + return output + + def _parse_diff(self, diff_text): + """ + Example diff text: + + * local directory is at ... + * build pristine tree for ... + * from import revision: ... + * patching for revision: ... + * comparing to ... + D .be/dir/bugs/.arch-ids/moved.id + D .be/dir/bugs/.arch-ids/removed.id + D .be/dir/bugs/moved + D .be/dir/bugs/removed + A .be/dir/bugs/.arch-ids/moved2.id + A .be/dir/bugs/.arch-ids/new.id + A .be/dir/bugs/moved2 + A .be/dir/bugs/new + A {arch}/bugs-everywhere/bugs-everywhere--mainline/... + M .be/dir/bugs/modified + """ + new = [] + modified = [] + removed = [] + lines = diff_text.splitlines() + for i,line in enumerate(lines): + if line.startswith('* ') or '/.arch-ids/' in line: + continue + change,file = line.split(' ',1) + if file.startswith('{arch}/'): + continue + if change == 'A': + new.append(file) + elif change == 'M': + modified.append(file) + elif change == 'D': + removed.append(file) + return (new,modified,removed) + + def _vcs_changed(self, revision): + return self._parse_diff(self._diff(revision)) + + +if libbe.TESTING == True: + base.make_vcs_testcase_subclasses(Arch, sys.modules[__name__]) + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py new file mode 100644 index 0000000..d85c94d --- /dev/null +++ b/libbe/storage/vcs/base.py @@ -0,0 +1,1127 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Alexander Belchenko <bialix@ukr.net> +# Ben Finney <benf@cybersource.com.au> +# Chris Ball <cjb@laptop.org> +# Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Define the base :class:`VCS` (Version Control System) class, which +should be subclassed by other Version Control System backends. The +base class implements a "do not version" VCS. +""" + +import codecs +import os +import os.path +import re +import shutil +import sys +import tempfile +import types + +import libbe +import libbe.storage +import libbe.storage.base +import libbe.util.encoding +from libbe.storage.base import EmptyCommit, InvalidRevision, InvalidID +from libbe.util.utility import Dir, search_parent_directories +from libbe.util.subproc import CommandError, invoke +from libbe.util.plugin import import_by_name +import libbe.storage.util.upgrade as upgrade + +if libbe.TESTING == True: + import unittest + import doctest + + import libbe.ui.util.user + +VCS_ORDER = ['arch', 'bzr', 'darcs', 'git', 'hg'] +"""List VCS modules in order of preference. + +Don't list this module, it is implicitly last. +""" + +def set_preferred_vcs(name): + """Manipulate :data:`VCS_ORDER` to place `name` first. + + This is primarily indended for testing purposes. + """ + global VCS_ORDER + assert name in VCS_ORDER, \ + 'unrecognized VCS %s not in\n %s' % (name, VCS_ORDER) + VCS_ORDER.remove(name) + VCS_ORDER.insert(0, name) + +def _get_matching_vcs(matchfn): + """Return the first module for which matchfn(VCS_instance) is True. + + Searches in :data:`VCS_ORDER`. + """ + for submodname in VCS_ORDER: + module = import_by_name('libbe.storage.vcs.%s' % submodname) + vcs = module.new() + if matchfn(vcs) == True: + return vcs + return VCS() + +def vcs_by_name(vcs_name): + """Return the module for the VCS with the given name. + + Searches in :data:`VCS_ORDER`. + """ + if vcs_name == VCS.name: + return new() + return _get_matching_vcs(lambda vcs: vcs.name == vcs_name) + +def detect_vcs(dir): + """Return an VCS instance for the vcs being used in this directory. + + Searches in :data:`VCS_ORDER`. + """ + return _get_matching_vcs(lambda vcs: vcs._detect(dir)) + +def installed_vcs(): + """Return an instance of an installed VCS. + + Searches in :data:`VCS_ORDER`. + """ + return _get_matching_vcs(lambda vcs: vcs.installed()) + + +class VCSNotRooted (libbe.storage.base.ConnectionError): + def __init__(self, vcs): + msg = 'VCS not rooted' + libbe.storage.base.ConnectionError.__init__(self, msg) + self.vcs = vcs + +class VCSUnableToRoot (libbe.storage.base.ConnectionError): + def __init__(self, vcs): + msg = 'VCS unable to root' + libbe.storage.base.ConnectionError.__init__(self, msg) + self.vcs = vcs + +class InvalidPath (InvalidID): + def __init__(self, path, root, msg=None, **kwargs): + if msg == None: + msg = 'Path "%s" not in root "%s"' % (path, root) + InvalidID.__init__(self, msg=msg, **kwargs) + self.path = path + self.root = root + +class SpacerCollision (InvalidPath): + def __init__(self, path, spacer): + msg = 'Path "%s" collides with spacer directory "%s"' % (path, spacer) + InvalidPath.__init__(self, path, root=None, msg=msg) + self.spacer = spacer + +class NoSuchFile (InvalidID): + def __init__(self, pathname, root='.'): + path = os.path.abspath(os.path.join(root, pathname)) + InvalidID.__init__(self, 'No such file: %s' % path) + + +class CachedPathID (object): + """Cache Storage ID <-> path policy. + + Paths generated following:: + + .../.be/BUGDIR/bugs/BUG/comments/COMMENT + ^-- root path + + See :mod:`libbe.util.id` for a discussion of ID formats. + + Examples + -------- + + >>> dir = Dir() + >>> os.mkdir(os.path.join(dir.path, '.be')) + >>> os.mkdir(os.path.join(dir.path, '.be', 'abc')) + >>> os.mkdir(os.path.join(dir.path, '.be', 'abc', 'bugs')) + >>> os.mkdir(os.path.join(dir.path, '.be', 'abc', 'bugs', '123')) + >>> os.mkdir(os.path.join(dir.path, '.be', 'abc', 'bugs', '123', 'comments')) + >>> os.mkdir(os.path.join(dir.path, '.be', 'abc', 'bugs', '123', 'comments', 'def')) + >>> os.mkdir(os.path.join(dir.path, '.be', 'abc', 'bugs', '456')) + >>> file(os.path.join(dir.path, '.be', 'abc', 'values'), + ... 'w').close() + >>> file(os.path.join(dir.path, '.be', 'abc', 'bugs', '123', 'values'), + ... 'w').close() + >>> file(os.path.join(dir.path, '.be', 'abc', 'bugs', '123', 'comments', 'def', 'values'), + ... 'w').close() + >>> c = CachedPathID() + >>> c.root(dir.path) + >>> c.id(os.path.join(dir.path, '.be', 'abc', 'bugs', '123', 'comments', 'def', 'values')) + 'def/values' + >>> c.init() + >>> sorted(os.listdir(os.path.join(c._root, '.be'))) + ['abc', 'id-cache'] + >>> c.connect() + >>> c.path('123/values') # doctest: +ELLIPSIS + u'.../.be/abc/bugs/123/values' + >>> c.disconnect() + >>> c.destroy() + >>> sorted(os.listdir(os.path.join(c._root, '.be'))) + ['abc'] + >>> c.connect() # demonstrate auto init + >>> sorted(os.listdir(os.path.join(c._root, '.be'))) + ['abc', 'id-cache'] + >>> c.add_id(u'xyz', parent=None) # doctest: +ELLIPSIS + u'.../.be/xyz' + >>> c.add_id('xyz/def', parent='xyz') # doctest: +ELLIPSIS + u'.../.be/xyz/def' + >>> c.add_id('qrs', parent='123') # doctest: +ELLIPSIS + u'.../.be/abc/bugs/123/comments/qrs' + >>> c.disconnect() + >>> c.connect() + >>> c.path('qrs') # doctest: +ELLIPSIS + u'.../.be/abc/bugs/123/comments/qrs' + >>> c.remove_id('qrs') + >>> c.path('qrs') + Traceback (most recent call last): + ... + InvalidID: qrs in revision None + >>> c.disconnect() + >>> c.destroy() + >>> dir.cleanup() + """ + def __init__(self, encoding=None): + self.encoding = libbe.util.encoding.get_filesystem_encoding() + self._spacer_dirs = ['.be', 'bugs', 'comments'] + + def root(self, path): + self._root = os.path.abspath(path).rstrip(os.path.sep) + self._cache_path = os.path.join( + self._root, self._spacer_dirs[0], 'id-cache') + + def init(self, verbose=True, cache=None): + """Create cache file for an existing .be directory. + + The file contains multiple lines of the form:: + + UUID\tPATH + """ + if cache == None: + self._cache = {} + else: + self._cache = cache + spaced_root = os.path.join(self._root, self._spacer_dirs[0]) + for dirpath, dirnames, filenames in os.walk(spaced_root): + if dirpath == spaced_root: + continue + try: + id = self.id(dirpath) + relpath = dirpath[len(self._root)+1:] + if id.count('/') == 0: + if verbose == True and id in self._cache: + print >> sys.stderr, 'Multiple paths for %s: \n %s\n %s' % (id, self._cache[id], relpath) + self._cache[id] = relpath + except InvalidPath: + pass + if self._cache != cache: + self._changed = True + if cache == None: + self.disconnect() + + def destroy(self): + if os.path.exists(self._cache_path): + os.remove(self._cache_path) + + def connect(self): + if not os.path.exists(self._cache_path): + try: + self.init() + except IOError: + raise libbe.storage.base.ConnectionError + self._cache = {} # key: uuid, value: path + self._changed = False + f = codecs.open(self._cache_path, 'r', self.encoding) + for line in f: + fields = line.rstrip('\n').split('\t') + self._cache[fields[0]] = fields[1] + f.close() + + def disconnect(self): + if self._changed == True: + f = codecs.open(self._cache_path, 'w', self.encoding) + for uuid,path in self._cache.items(): + f.write('%s\t%s\n' % (uuid, path)) + f.close() + self._cache = {} + + def path(self, id, relpath=False): + fields = id.split('/', 1) + uuid = fields[0] + if len(fields) == 1: + extra = [] + else: + extra = fields[1:] + if uuid not in self._cache: + self.init(verbose=False, cache=self._cache) + if uuid not in self._cache: + raise InvalidID(uuid) + if relpath == True: + return os.path.join(self._cache[uuid], *extra) + return os.path.join(self._root, self._cache[uuid], *extra) + + def add_id(self, id, parent=None): + if id.count('/') > 0: + # not a UUID-level path + assert id.startswith(parent), \ + 'Strange ID: "%s" should start with "%s"' % (id, parent) + path = self.path(id) + elif id in self._cache: + # already added + path = self.path(id) + else: + if parent == None: + parent_path = '' + spacer = self._spacer_dirs[0] + else: + assert parent.count('/') == 0, \ + 'Strange parent ID: "%s" should be UUID' % parent + parent_path = self.path(parent, relpath=True) + parent_spacer = parent_path.split(os.path.sep)[-2] + i = self._spacer_dirs.index(parent_spacer) + spacer = self._spacer_dirs[i+1] + path = os.path.join(parent_path, spacer, id) + self._cache[id] = path + self._changed = True + path = os.path.join(self._root, path) + return path + + def remove_id(self, id): + if id.count('/') > 0: + return # not a UUID-level path + self._cache.pop(id) + self._changed = True + + def id(self, path): + path = os.path.join(self._root, path) + if not path.startswith(self._root + os.path.sep): + raise InvalidPath(path, self._root) + path = path[len(self._root)+1:] + orig_path = path + if not path.startswith(self._spacer_dirs[0] + os.path.sep): + raise InvalidPath(path, self._spacer_dirs[0]) + for spacer in self._spacer_dirs: + if not path.startswith(spacer + os.path.sep): + break + id = path[len(spacer)+1:] + fields = path[len(spacer)+1:].split(os.path.sep,1) + if len(fields) == 1: + break + path = fields[1] + for spacer in self._spacer_dirs: + if id.endswith(os.path.sep + spacer): + raise SpacerCollision(orig_path, spacer) + if os.path.sep != '/': + id = id.replace(os.path.sep, '/') + return id + + +def new(): + return VCS() + +class VCS (libbe.storage.base.VersionedStorage): + """Implement a 'no-VCS' interface. + + Support for other VCSs can be added by subclassing this class, and + overriding methods `_vcs_*()` with code appropriate for your VCS. + + The methods `_u_*()` are utility methods available to the `_vcs_*()` + methods. + """ + name = 'None' + client = 'false' # command-line tool for _u_invoke_client + + def __init__(self, *args, **kwargs): + if 'encoding' not in kwargs: + kwargs['encoding'] = libbe.util.encoding.get_filesystem_encoding() + libbe.storage.base.VersionedStorage.__init__(self, *args, **kwargs) + self.versioned = False + self.interspersed_vcs_files = False + self.verbose_invoke = False + self._cached_path_id = CachedPathID() + self._rooted = False + + def _vcs_version(self): + """ + Return the VCS version string. + """ + return '0' + + def _vcs_get_user_id(self): + """ + Get the VCS's suggested user id (e.g. "John Doe <jdoe@example.com>"). + If the VCS has not been configured with a username, return None. + """ + return None + + def _vcs_detect(self, path=None): + """ + Detect whether a directory is revision controlled with this VCS. + """ + return True + + def _vcs_root(self, path): + """ + Get the VCS root. This is the default working directory for + future invocations. You would normally set this to the root + directory for your VCS. + """ + if os.path.isdir(path) == False: + path = os.path.dirname(path) + if path == '': + path = os.path.abspath('.') + return path + + def _vcs_init(self, path): + """ + Begin versioning the tree based at path. + """ + pass + + def _vcs_destroy(self): + """ + Remove any files used in versioning (e.g. whatever _vcs_init() + created). + """ + pass + + def _vcs_add(self, path): + """ + Add the already created file at path to version control. + """ + pass + + def _vcs_exists(self, path, revision=None): + """ + Does the path exist in a given revision? (True/False) + """ + raise NotImplementedError('Lazy BE developers') + + def _vcs_remove(self, path): + """ + Remove the file at path from version control. Optionally + remove the file from the filesystem as well. + """ + pass + + def _vcs_update(self, path): + """ + Notify the versioning system of changes to the versioned file + at path. + """ + pass + + def _vcs_is_versioned(self, path): + """ + Return true if a path is under version control, False + otherwise. You only need to set this if the VCS goes about + dumping VCS-specific files into the .be directory. + + If you do need to implement this method (e.g. Arch), set + self.interspersed_vcs_files = True + """ + assert self.interspersed_vcs_files == False + raise NotImplementedError + + def _vcs_get_file_contents(self, path, revision=None): + """ + Get the file contents as they were in a given revision. + Revision==None specifies the current revision. + """ + if revision != None: + raise libbe.storage.base.InvalidRevision( + 'The %s VCS does not support revision specifiers' % self.name) + path = os.path.join(self.repo, path) + if not os.path.exists(path): + return libbe.util.InvalidObject + if os.path.isdir(path): + return libbe.storage.base.InvalidDirectory + f = open(path, 'rb') + contents = f.read() + f.close() + return contents + + def _vcs_path(self, id, revision): + """ + Return the relative path to object id as of revision. + + Revision will not be None. + """ + raise NotImplementedError + + def _vcs_isdir(self, path, revision): + """ + Return True if path (as returned by _vcs_path) was a directory + as of revision, False otherwise. + + Revision will not be None. + """ + raise NotImplementedError + + def _vcs_listdir(self, path, revision): + """ + Return a list of the contents of the directory path (as + returned by _vcs_path) as of revision. + + Revision will not be None, and ._vcs_isdir(path, revision) + will be True. + """ + raise NotImplementedError + + def _vcs_commit(self, commitfile, allow_empty=False): + """ + Commit the current working directory, using the contents of + commitfile as the comment. Return the name of the old + revision (or None if commits are not supported). + + If allow_empty == False, raise EmptyCommit if there are no + changes to commit. + """ + return None + + def _vcs_revision_id(self, index): + """ + Return the name of the <index>th revision. Index will be an + integer (possibly <= 0). The choice of which branch to follow + when crossing branches/merges is not defined. + + Return None if revision IDs are not supported, or if the + specified revision does not exist. + """ + return None + + def _vcs_changed(self, revision): + """ + Return a tuple of lists of ids + (new, modified, removed) + from the specified revision to the current situation. + """ + return ([], [], []) + + def version(self): + # Cache version string for efficiency. + if not hasattr(self, '_version'): + self._version = self._get_version() + return self._version + + def _get_version(self): + try: + ret = self._vcs_version() + return ret + except OSError, e: + if e.errno == errno.ENOENT: + return None + else: + raise OSError, e + except CommandError: + return None + + def installed(self): + if self.version() != None: + return True + return False + + def get_user_id(self): + """ + Get the VCS's suggested user id (e.g. "John Doe <jdoe@example.com>"). + If the VCS has not been configured with a username, return None. + You can override the automatic lookup procedure by setting the + VCS.user_id attribute to a string of your choice. + """ + if not hasattr(self, 'user_id'): + self.user_id = self._vcs_get_user_id() + return self.user_id + + def _detect(self, path='.'): + """ + Detect whether a directory is revision controlled with this VCS. + """ + return self._vcs_detect(path) + + def root(self): + """Set the root directory to the path's VCS root. + + This is the default working directory for future invocations. + Consider the following usage case: + + You have a project rooted in:: + + /path/to/source/ + + by which I mean the VCS repository is in, for example:: + + /path/to/source/.bzr + + However, you're of in some subdirectory like:: + + /path/to/source/ui/testing + + and you want to comment on a bug. `root` will locate your VCS + root (``/path/to/source/``) and set the repo there. This + means that it doesn't matter where you are in your project + tree when you call "be COMMAND", it always acts as if you called + it from the VCS root. + """ + if self._detect(self.repo) == False: + raise VCSUnableToRoot(self) + root = self._vcs_root(self.repo) + self.repo = os.path.abspath(root) + if os.path.isdir(self.repo) == False: + self.repo = os.path.dirname(self.repo) + self.be_dir = os.path.join( + self.repo, self._cached_path_id._spacer_dirs[0]) + self._cached_path_id.root(self.repo) + self._rooted = True + + def _init(self): + """ + Begin versioning the tree based at self.repo. + Also roots the vcs at path. + + See Also + -------- + root : called if the VCS has already been initialized. + """ + if not os.path.exists(self.repo) or not os.path.isdir(self.repo): + raise VCSUnableToRoot(self) + if self._vcs_detect(self.repo) == False: + self._vcs_init(self.repo) + if self._rooted == False: + self.root() + os.mkdir(self.be_dir) + self._vcs_add(self._u_rel_path(self.be_dir)) + self._setup_storage_version() + self._cached_path_id.init() + + def _destroy(self): + self._vcs_destroy() + self._cached_path_id.destroy() + if os.path.exists(self.be_dir): + shutil.rmtree(self.be_dir) + + def _connect(self): + if self._rooted == False: + self.root() + if not os.path.isdir(self.be_dir): + raise libbe.storage.base.ConnectionError(self) + self._cached_path_id.connect() + self.check_storage_version() + + def _disconnect(self): + self._cached_path_id.disconnect() + + def path(self, id, revision=None, relpath=True): + if revision == None: + path = self._cached_path_id.path(id) + if relpath == True: + return self._u_rel_path(path) + return path + path = self._vcs_path(id, revision) + if relpath == True: + return path + return os.path.join(self.repo, path) + + def _add_path(self, path, directory=False): + relpath = self._u_rel_path(path) + reldirs = relpath.split(os.path.sep) + if directory == False: + reldirs = reldirs[:-1] + dir = self.repo + for reldir in reldirs: + dir = os.path.join(dir, reldir) + if not os.path.exists(dir): + os.mkdir(dir) + self._vcs_add(self._u_rel_path(dir)) + elif not os.path.isdir(dir): + raise libbe.storage.base.InvalidDirectory + if directory == False: + if not os.path.exists(path): + open(path, 'w').close() + self._vcs_add(self._u_rel_path(path)) + + def _add(self, id, parent=None, **kwargs): + path = self._cached_path_id.add_id(id, parent) + self._add_path(path, **kwargs) + + def _exists(self, id, revision=None): + if revision == None: + try: + path = self.path(id, revision, relpath=False) + except InvalidID, e: + return False + return os.path.exists(path) + path = self.path(id, revision, relpath=True) + return self._vcs_exists(relpath, revision) + + def _remove(self, id): + path = self._cached_path_id.path(id) + if os.path.exists(path): + if os.path.isdir(path) and len(self.children(id)) > 0: + raise libbe.storage.base.DirectoryNotEmpty(id) + self._vcs_remove(self._u_rel_path(path)) + if os.path.exists(path): + if os.path.isdir(path): + os.rmdir(path) + else: + os.remove(path) + self._cached_path_id.remove_id(id) + + def _recursive_remove(self, id): + path = self._cached_path_id.path(id) + for dirpath,dirnames,filenames in os.walk(path, topdown=False): + filenames.extend(dirnames) + for f in filenames: + fullpath = os.path.join(dirpath, f) + if os.path.exists(fullpath) == False: + continue + self._vcs_remove(self._u_rel_path(fullpath)) + if os.path.exists(path): + shutil.rmtree(path) + path = self._cached_path_id.path(id, relpath=True) + for id,p in self._cached_path_id._cache.items(): + if p.startswith(path): + self._cached_path_id.remove_id(id) + + def _ancestors(self, id=None, revision=None): + if id==None: + path = self.be_dir + else: + path = self.path(id, revision, relpath=False) + ancestors = [] + while True: + if not path.startswith(self.repo + os.path.sep): + break + path = os.path.dirname(path) + try: + id = self._u_path_to_id(path) + ancestors.append(id) + except (SpacerCollision, InvalidPath): + pass + return ancestors + + def _children(self, id=None, revision=None): + if revision == None: + isdir = os.path.isdir + listdir = os.listdir + else: + isdir = lambda path : self._vcs_isdir( + self._u_rel_path(path), revision) + listdir = lambda path : self._vcs_listdir( + self._u_rel_path(path), revision) + if id==None: + path = self.be_dir + else: + path = self.path(id, revision, relpath=False) + if isdir(path) == False: + return [] + children = listdir(path) + for i,c in enumerate(children): + if c in self._cached_path_id._spacer_dirs: + children[i] = None + children.extend([os.path.join(c, c2) for c2 in + listdir(os.path.join(path, c))]) + elif c in ['id-cache', 'version']: + children[i] = None + elif self.interspersed_vcs_files \ + and self._vcs_is_versioned(c) == False: + children[i] = None + for i,c in enumerate(children): + if c == None: continue + cpath = os.path.join(path, c) + if self.interspersed_vcs_files == True \ + and revision != None \ + and self._vcs_is_versioned(cpath) == False: + children[i] = None + else: + children[i] = self._u_path_to_id(cpath) + children[i] + return [c for c in children if c != None] + + def _get(self, id, default=libbe.util.InvalidObject, revision=None): + try: + relpath = self.path(id, revision, relpath=True) + contents = self._vcs_get_file_contents(relpath, revision) + except InvalidID, e: + if default == libbe.util.InvalidObject: + raise e + return default + if contents in [libbe.storage.base.InvalidDirectory, + libbe.util.InvalidObject] \ + or len(contents) == 0: + if default == libbe.util.InvalidObject: + raise InvalidID(id, revision) + return default + return contents + + def _set(self, id, value): + try: + path = self._cached_path_id.path(id) + except InvalidID, e: + raise + if not os.path.exists(path): + raise InvalidID(id) + if os.path.isdir(path): + raise libbe.storage.base.InvalidDirectory(id) + f = open(path, "wb") + f.write(value) + f.close() + self._vcs_update(self._u_rel_path(path)) + + def _commit(self, summary, body=None, allow_empty=False): + summary = summary.strip()+'\n' + if body is not None: + summary += '\n' + body.strip() + '\n' + descriptor, filename = tempfile.mkstemp() + revision = None + try: + temp_file = os.fdopen(descriptor, 'wb') + temp_file.write(summary) + temp_file.flush() + revision = self._vcs_commit(filename, allow_empty=allow_empty) + temp_file.close() + finally: + os.remove(filename) + return revision + + def revision_id(self, index=None): + if index == None: + return None + try: + if int(index) != index: + raise InvalidRevision(index) + except ValueError: + raise InvalidRevision(index) + revid = self._vcs_revision_id(index) + if revid == None: + raise libbe.storage.base.InvalidRevision(index) + return revid + + def changed(self, revision): + new,mod,rem = self._vcs_changed(revision) + def paths_to_ids(paths): + for p in paths: + try: + id = self._u_path_to_id(p) + yield id + except (SpacerCollision, InvalidPath): + pass + new_id = list(paths_to_ids(new)) + mod_id = list(paths_to_ids(mod)) + rem_id = list(paths_to_ids(rem)) + return (new_id, mod_id, rem_id) + + def _u_any_in_string(self, list, string): + """Return True if any of the strings in list are in string. + Otherwise return False. + """ + for list_string in list: + if list_string in string: + return True + return False + + def _u_invoke(self, *args, **kwargs): + if 'cwd' not in kwargs: + kwargs['cwd'] = self.repo + if 'verbose' not in kwargs: + kwargs['verbose'] = self.verbose_invoke + if 'encoding' not in kwargs: + kwargs['encoding'] = self.encoding + return invoke(*args, **kwargs) + + def _u_invoke_client(self, *args, **kwargs): + cl_args = [self.client] + cl_args.extend(args) + return self._u_invoke(cl_args, **kwargs) + + def _u_search_parent_directories(self, path, filename): + """Find the file (or directory) named filename in path or in any of + path's parents. + + e.g. + search_parent_directories("/a/b/c", ".be") + will return the path to the first existing file from + /a/b/c/.be + /a/b/.be + /a/.be + /.be + or None if none of those files exist. + """ + try: + ret = search_parent_directories(path, filename) + except AssertionError, e: + return None + return ret + + def _u_find_id_from_manifest(self, id, manifest, revision=None): + """Search for the relative path to id using manifest, a list of all + files. + + Returns None if the id is not found. + """ + be_dir = self._cached_path_id._spacer_dirs[0] + be_dir_sep = self._cached_path_id._spacer_dirs[0] + os.path.sep + files = [f for f in manifest if f.startswith(be_dir_sep)] + for file in files: + if not file.startswith(be_dir+os.path.sep): + continue + parts = file.split(os.path.sep) + dir = parts.pop(0) # don't add the first spacer dir + for part in parts[:-1]: + dir = os.path.join(dir, part) + if not dir in files: + files.append(dir) + for file in files: + try: + p_id = self._u_path_to_id(file) + if p_id == id: + return file + except (SpacerCollision, InvalidPath): + pass + raise InvalidID(id, revision=revision) + + def _u_find_id(self, id, revision): + """Search for the relative path to id as of revision. + + Returns None if the id is not found. + """ + assert self._rooted == True + be_dir = self._cached_path_id._spacer_dirs[0] + stack = [(be_dir, be_dir)] + while len(stack) > 0: + path,long_id = stack.pop() + if long_id.endswith('/'+id): + return path + if self._vcs_isdir(path, revision) == False: + continue + for child in self._vcs_listdir(path, revision): + stack.append((os.path.join(path, child), + '/'.join([long_id, child]))) + raise InvalidID(id, revision=revision) + + def _u_path_to_id(self, path): + return self._cached_path_id.id(path) + + def _u_rel_path(self, path, root=None): + """Return the relative path to path from root. + + Examples: + + >>> vcs = new() + >>> vcs._u_rel_path("/a.b/c/.be", "/a.b/c") + '.be' + >>> vcs._u_rel_path("/a.b/c/", "/a.b/c") + '.' + >>> vcs._u_rel_path("/a.b/c/", "/a.b/c/") + '.' + >>> vcs._u_rel_path("./a", ".") + 'a' + """ + if root == None: + if self.repo == None: + raise VCSNotRooted(self) + root = self.repo + path = os.path.abspath(path) + absRoot = os.path.abspath(root) + absRootSlashedDir = os.path.join(absRoot,"") + if path in [absRoot, absRootSlashedDir]: + return '.' + if not path.startswith(absRootSlashedDir): + raise InvalidPath(path, absRootSlashedDir) + relpath = path[len(absRootSlashedDir):] + return relpath + + def _u_abspath(self, path, root=None): + """Return the absolute path from a path realtive to root. + + Examples + -------- + + >>> vcs = new() + >>> vcs._u_abspath(".be", "/a.b/c") + '/a.b/c/.be' + """ + if root == None: + assert self.repo != None, "VCS not rooted" + root = self.repo + return os.path.abspath(os.path.join(root, path)) + + def _u_parse_commitfile(self, commitfile): + """Split the commitfile created in self.commit() back into summary and + header lines. + """ + f = codecs.open(commitfile, 'r', self.encoding) + summary = f.readline() + body = f.read() + body.lstrip('\n') + if len(body) == 0: + body = None + f.close() + return (summary, body) + + def check_storage_version(self): + version = self.storage_version() + if version != libbe.storage.STORAGE_VERSION: + upgrade.upgrade(self.repo, version) + + def storage_version(self, revision=None, path=None): + """Return the storage version of the on-disk files. + + See Also + -------- + :mod:`libbe.storage.util.upgrade` + """ + if path == None: + path = os.path.join(self.repo, '.be', 'version') + if not os.path.exists(path): + raise libbe.storage.InvalidStorageVersion(None) + if revision == None: # don't require connection + return libbe.util.encoding.get_file_contents( + path, decode=True).rstrip('\n') + relpath = self._u_rel_path(path) + contents = self._vcs_get_file_contents(relpath, revision=revision) + if type(contents) != types.UnicodeType: + contents = unicode(contents, self.encoding) + return contents.strip() + + def _setup_storage_version(self): + """ + Requires disk access. + """ + assert self._rooted == True + path = os.path.join(self.be_dir, 'version') + if not os.path.exists(path): + libbe.util.encoding.set_file_contents(path, + libbe.storage.STORAGE_VERSION+'\n') + self._vcs_add(self._u_rel_path(path)) + + +if libbe.TESTING == True: + class VCSTestCase (unittest.TestCase): + """ + Test cases for base VCS class (in addition to the Storage test + cases). + """ + + Class = VCS + + def __init__(self, *args, **kwargs): + super(VCSTestCase, self).__init__(*args, **kwargs) + self.dirname = None + + def setUp(self): + """Set up test fixtures for Storage test case.""" + super(VCSTestCase, self).setUp() + self.dir = Dir() + self.dirname = self.dir.path + self.s = self.Class(repo=self.dirname) + if self.s.installed() == True: + self.s.init() + self.s.connect() + + def tearDown(self): + super(VCSTestCase, self).tearDown() + if self.s.installed() == True: + self.s.disconnect() + self.s.destroy() + self.dir.cleanup() + + class VCS_installed_TestCase (VCSTestCase): + def test_installed(self): + """See if the VCS is installed. + """ + self.failUnless(self.s.installed() == True, + '%(name)s VCS not found' % vars(self.Class)) + + + class VCS_detection_TestCase (VCSTestCase): + def test_detection(self): + """See if the VCS detects its installed repository + """ + if self.s.installed(): + self.s.disconnect() + self.failUnless(self.s._detect(self.dirname) == True, + 'Did not detected %(name)s VCS after initialising' + % vars(self.Class)) + self.s.connect() + + def test_no_detection(self): + """See if the VCS detects its installed repository + """ + if self.s.installed() and self.Class.name != 'None': + self.s.disconnect() + self.s.destroy() + self.failUnless(self.s._detect(self.dirname) == False, + 'Detected %(name)s VCS before initialising' + % vars(self.Class)) + self.s.init() + self.s.connect() + + def test_vcs_repo_in_specified_root_path(self): + """VCS root directory should be in specified root path.""" + rp = os.path.realpath(self.s.repo) + dp = os.path.realpath(self.dirname) + vcs_name = self.Class.name + self.failUnless( + dp == rp or rp == None, + "%(vcs_name)s VCS root in wrong dir (%(dp)s %(rp)s)" % vars()) + + class VCS_get_user_id_TestCase(VCSTestCase): + """Test cases for VCS.get_user_id method.""" + + def test_gets_existing_user_id(self): + """Should get the existing user ID.""" + if self.s.installed(): + user_id = self.s.get_user_id() + if user_id == None: + return + name,email = libbe.ui.util.user.parse_user_id(user_id) + if email != None: + self.failUnless('@' in email, email) + + def make_vcs_testcase_subclasses(vcs_class, namespace): + c = vcs_class() + if c.installed(): + if c.versioned == True: + libbe.storage.base.make_versioned_storage_testcase_subclasses( + vcs_class, namespace) + else: + libbe.storage.base.make_storage_testcase_subclasses( + vcs_class, namespace) + + if namespace != sys.modules[__name__]: + # Make VCSTestCase subclasses for vcs_class in the namespace. + vcs_testcase_classes = [ + c for c in ( + ob for ob in globals().values() if isinstance(ob, type)) + if issubclass(c, VCSTestCase) \ + and c.Class == VCS] + + for base_class in vcs_testcase_classes: + testcase_class_name = vcs_class.__name__ + base_class.__name__ + testcase_class_bases = (base_class,) + testcase_class_dict = dict(base_class.__dict__) + testcase_class_dict['Class'] = vcs_class + testcase_class = type( + testcase_class_name, testcase_class_bases, testcase_class_dict) + setattr(namespace, testcase_class_name, testcase_class) + + make_vcs_testcase_subclasses(VCS, sys.modules[__name__]) + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py new file mode 100644 index 0000000..5a62968 --- /dev/null +++ b/libbe/storage/vcs/bzr.py @@ -0,0 +1,361 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Ben Finney <benf@cybersource.com.au> +# Gianluca Montecchi <gian@grys.it> +# Marien Zwart <marienz@gentoo.org> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Bazaar_ (bzr) backend. + +.. _Bazaar: http://bazaar.canonical.com/ +""" + +try: + import bzrlib + import bzrlib.branch + import bzrlib.builtins + import bzrlib.config + import bzrlib.errors + import bzrlib.option +except ImportError: + bzrlib = None +import os +import os.path +import re +import shutil +import StringIO +import sys +import types + +import libbe +import base + +if libbe.TESTING == True: + import doctest + import unittest + + +def new(): + return Bzr() + +class Bzr(base.VCS): + """:class:`base.VCS` implementation for Bazaar. + """ + name = 'bzr' + client = None # bzrlib module + + def __init__(self, *args, **kwargs): + base.VCS.__init__(self, *args, **kwargs) + self.versioned = True + + def _vcs_version(self): + if bzrlib == None: + return None + return bzrlib.__version__ + + def version_cmp(self, *args): + """Compare the installed Bazaar version `V_i` with another version + `V_o` (given in `*args`). Returns + + === =============== + 1 if `V_i > V_o` + 0 if `V_i == V_o` + -1 if `V_i < V_o` + === =============== + + Examples + -------- + + >>> b = Bzr(repo='.') + >>> b._vcs_version = lambda : "2.3.1 (release)" + >>> b.version_cmp(2,3,1) + 0 + >>> b.version_cmp(2,3,2) + -1 + >>> b.version_cmp(2,3,0) + 1 + >>> b.version_cmp(3) + -1 + >>> b._vcs_version = lambda : "2.0.0pre2" + >>> b._parsed_version = None + >>> b.version_cmp(3) + -1 + >>> b.version_cmp(2,0,1) + Traceback (most recent call last): + ... + NotImplementedError: Cannot parse non-integer portion "0pre2" of Bzr version "2.0.0pre2" + """ + if not hasattr(self, '_parsed_version') \ + or self._parsed_version == None: + num_part = self._vcs_version().split(' ')[0] + self._parsed_version = [] + for num in num_part.split('.'): + try: + self._parsed_version.append(int(num)) + except ValueError, e: + self._parsed_version.append(num) + for current,other in zip(self._parsed_version, args): + if type(current) != types.IntType: + raise NotImplementedError( + 'Cannot parse non-integer portion "%s" of Bzr version "%s"' + % (current, self._vcs_version())) + c = cmp(current,other) + if c != 0: + return c + return 0 + + def _vcs_get_user_id(self): + # excerpted from bzrlib.builtins.cmd_whoami.run() + try: + c = bzrlib.branch.Branch.open_containing(self.repo)[0].get_config() + except errors.NotBranchError: + c = bzrlib.config.GlobalConfig() + return c.username() + + def _vcs_detect(self, path): + if self._u_search_parent_directories(path, '.bzr') != None : + return True + return False + + def _vcs_root(self, path): + """Find the root of the deepest repository containing path.""" + cmd = bzrlib.builtins.cmd_root() + cmd.outf = StringIO.StringIO() + cmd.run(filename=path) + return cmd.outf.getvalue().rstrip('\n') + + def _vcs_init(self, path): + cmd = bzrlib.builtins.cmd_init() + cmd.outf = StringIO.StringIO() + cmd.run(location=path) + + def _vcs_destroy(self): + vcs_dir = os.path.join(self.repo, '.bzr') + if os.path.exists(vcs_dir): + shutil.rmtree(vcs_dir) + + def _vcs_add(self, path): + path = os.path.join(self.repo, path) + cmd = bzrlib.builtins.cmd_add() + cmd.outf = StringIO.StringIO() + cmd.run(file_list=[path], file_ids_from=self.repo) + + def _vcs_exists(self, path, revision=None): + manifest = self._vcs_listdir( + self.repo, revision=revision, recursive=True) + if path in manifest: + return True + return False + + def _vcs_remove(self, path): + # --force to also remove unversioned files. + path = os.path.join(self.repo, path) + cmd = bzrlib.builtins.cmd_remove() + cmd.outf = StringIO.StringIO() + cmd.run(file_list=[path], file_deletion_strategy='force') + + def _vcs_update(self, path): + pass + + def _parse_revision_string(self, revision=None): + if revision == None: + return revision + rev_opt = bzrlib.option.Option.OPTIONS['revision'] + try: + rev_spec = rev_opt.type(revision) + except bzrlib.errors.NoSuchRevisionSpec: + raise base.InvalidRevision(revision) + return rev_spec + + def _vcs_get_file_contents(self, path, revision=None): + if revision == None: + return base.VCS._vcs_get_file_contents(self, path, revision) + path = os.path.join(self.repo, path) + revision = self._parse_revision_string(revision) + cmd = bzrlib.builtins.cmd_cat() + cmd.outf = StringIO.StringIO() + if self.version_cmp(1,6,0) < 0: + # old bzrlib cmd_cat uses sys.stdout not self.outf for output. + stdout = sys.stdout + sys.stdout = cmd.outf + try: + cmd.run(filename=path, revision=revision) + except bzrlib.errors.BzrCommandError, e: + if 'not present in revision' in str(e): + raise base.InvalidPath(path, root=self.repo, revision=revision) + raise + finally: + if self.version_cmp(2,0,0) < 0: + cmd.outf = sys.stdout + sys.stdout = stdout + return cmd.outf.getvalue() + + def _vcs_path(self, id, revision): + manifest = self._vcs_listdir( + self.repo, revision=revision, recursive=True) + return self._u_find_id_from_manifest(id, manifest, revision=revision) + + def _vcs_isdir(self, path, revision): + try: + self._vcs_listdir(path, revision) + except AttributeError, e: + if 'children' in str(e): + return False + raise + return True + + def _vcs_listdir(self, path, revision, recursive=False): + path = os.path.join(self.repo, path) + revision = self._parse_revision_string(revision) + cmd = bzrlib.builtins.cmd_ls() + cmd.outf = StringIO.StringIO() + try: + if self.version_cmp(2,0,0) >= 0: + cmd.run(revision=revision, path=path, recursive=recursive) + else: + # Pre-2.0 Bazaar (non_recursive) + # + working around broken non_recursive+path implementation + # (https://bugs.launchpad.net/bzr/+bug/158690) + cmd.run(revision=revision, path=path, + non_recursive=False) + except bzrlib.errors.BzrCommandError, e: + if 'not present in revision' in str(e): + raise base.InvalidPath(path, root=self.repo, revision=revision) + raise + children = cmd.outf.getvalue().rstrip('\n').splitlines() + children = [self._u_rel_path(c, path) for c in children] + if self.version_cmp(2,0,0) < 0 and recursive == False: + children = [c for c in children if os.path.sep not in c] + return children + + def _vcs_commit(self, commitfile, allow_empty=False): + cmd = bzrlib.builtins.cmd_commit() + cmd.outf = StringIO.StringIO() + cwd = os.getcwd() + os.chdir(self.repo) + try: + cmd.run(file=commitfile, unchanged=allow_empty) + except bzrlib.errors.BzrCommandError, e: + strings = ['no changes to commit.', # bzr 1.3.1 + 'No changes to commit.'] # bzr 1.15.1 + if self._u_any_in_string(strings, str(e)) == True: + raise base.EmptyCommit() + raise + finally: + os.chdir(cwd) + return self._vcs_revision_id(-1) + + def _vcs_revision_id(self, index): + cmd = bzrlib.builtins.cmd_revno() + cmd.outf = StringIO.StringIO() + cmd.run(location=self.repo) + current_revision = int(cmd.outf.getvalue()) + if index > current_revision or index < -current_revision: + return None + if index >= 0: + return str(index) # bzr commit 0 is the empty tree. + return str(current_revision+index+1) + + def _diff(self, revision): + revision = self._parse_revision_string(revision) + cmd = bzrlib.builtins.cmd_diff() + cmd.outf = StringIO.StringIO() + # for some reason, cmd_diff uses sys.stdout not self.outf for output. + stdout = sys.stdout + sys.stdout = cmd.outf + try: + status = cmd.run(revision=revision, file_list=[self.repo]) + finally: + sys.stdout = stdout + assert status in [0,1], "Invalid status %d" % status + return cmd.outf.getvalue() + + def _parse_diff(self, diff_text): + """_parse_diff(diff_text) -> (new,modified,removed) + + `new`, `modified`, and `removed` are lists of files. + + Example diff text:: + + === modified file 'dir/changed' + --- dir/changed 2010-01-16 01:54:53 +0000 + +++ dir/changed 2010-01-16 01:54:54 +0000 + @@ -1,3 +1,3 @@ + hi + -there + +everyone and + joe + + === removed file 'dir/deleted' + --- dir/deleted 2010-01-16 01:54:53 +0000 + +++ dir/deleted 1970-01-01 00:00:00 +0000 + @@ -1,3 +0,0 @@ + -in + -the + -beginning + + === removed file 'dir/moved' + --- dir/moved 2010-01-16 01:54:53 +0000 + +++ dir/moved 1970-01-01 00:00:00 +0000 + @@ -1,4 +0,0 @@ + -the + -ants + -go + -marching + + === added file 'dir/moved2' + --- dir/moved2 1970-01-01 00:00:00 +0000 + +++ dir/moved2 2010-01-16 01:54:34 +0000 + @@ -0,0 +1,4 @@ + +the + +ants + +go + +marching + + === added file 'dir/new' + --- dir/new 1970-01-01 00:00:00 +0000 + +++ dir/new 2010-01-16 01:54:54 +0000 + @@ -0,0 +1,2 @@ + +hello + +world + + """ + new = [] + modified = [] + removed = [] + for line in diff_text.splitlines(): + if not line.startswith('=== '): + continue + fields = line.split() + action = fields[1] + file = fields[-1].strip("'") + if action == 'added': + new.append(file) + elif action == 'modified': + modified.append(file) + elif action == 'removed': + removed.append(file) + return (new,modified,removed) + + def _vcs_changed(self, revision): + return self._parse_diff(self._diff(revision)) + + +if libbe.TESTING == True: + base.make_vcs_testcase_subclasses(Bzr, sys.modules[__name__]) + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/storage/vcs/darcs.py b/libbe/storage/vcs/darcs.py new file mode 100644 index 0000000..0f23278 --- /dev/null +++ b/libbe/storage/vcs/darcs.py @@ -0,0 +1,399 @@ +# Copyright (C) 2009-2010 Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Darcs_ backend. + +.. _Darcs: http://darcs.net/ +""" + +import codecs +import os +import re +import shutil +import sys +import time # work around http://mercurial.selenic.com/bts/issue618 +import types +try: # import core module, Python >= 2.5 + from xml.etree import ElementTree +except ImportError: # look for non-core module + from elementtree import ElementTree +from xml.sax.saxutils import unescape + +import libbe +import base + +if libbe.TESTING == True: + import doctest + import unittest + + +def new(): + return Darcs() + +class Darcs(base.VCS): + """:class:`base.VCS` implementation for Darcs. + """ + name='darcs' + client='darcs' + + def __init__(self, *args, **kwargs): + base.VCS.__init__(self, *args, **kwargs) + self.versioned = True + self.__updated = [] # work around http://mercurial.selenic.com/bts/issue618 + + def _vcs_version(self): + status,output,error = self._u_invoke_client('--version') + return output.strip() + + def version_cmp(self, *args): + """Compare the installed Darcs version `V_i` with another version + `V_o` (given in `*args`). Returns + + === =============== + 1 if `V_i > V_o` + 0 if `V_i == V_o` + -1 if `V_i < V_o` + === =============== + + Examples + -------- + + >>> d = Darcs(repo='.') + >>> d._vcs_version = lambda : "2.3.1 (release)" + >>> d.version_cmp(2,3,1) + 0 + >>> d.version_cmp(2,3,2) + -1 + >>> d.version_cmp(2,3,0) + 1 + >>> d.version_cmp(3) + -1 + >>> d._vcs_version = lambda : "2.0.0pre2" + >>> d._parsed_version = None + >>> d.version_cmp(3) + -1 + >>> d.version_cmp(2,0,1) + Traceback (most recent call last): + ... + NotImplementedError: Cannot parse non-integer portion "0pre2" of Darcs version "2.0.0pre2" + """ + if not hasattr(self, '_parsed_version') \ + or self._parsed_version == None: + num_part = self._vcs_version().split(' ')[0] + self._parsed_version = [] + for num in num_part.split('.'): + try: + self._parsed_version.append(int(num)) + except ValueError, e: + self._parsed_version.append(num) + for current,other in zip(self._parsed_version, args): + if type(current) != types.IntType: + raise NotImplementedError( + 'Cannot parse non-integer portion "%s" of Darcs version "%s"' + % (current, self._vcs_version())) + c = cmp(current,other) + if c != 0: + return c + return 0 + + def _vcs_get_user_id(self): + # following http://darcs.net/manual/node4.html#SECTION00410030000000000000 + # as of June 29th, 2009 + if self.repo == None: + return None + darcs_dir = os.path.join(self.repo, '_darcs') + if darcs_dir != None: + for pref_file in ['author', 'email']: + pref_path = os.path.join(darcs_dir, 'prefs', pref_file) + if os.path.exists(pref_path): + return self._vcs_get_file_contents(pref_path).strip() + for env_variable in ['DARCS_EMAIL', 'EMAIL']: + if env_variable in os.environ: + return os.environ[env_variable] + return None + + def _vcs_detect(self, path): + if self._u_search_parent_directories(path, "_darcs") != None : + return True + return False + + def _vcs_root(self, path): + """Find the root of the deepest repository containing path.""" + # Assume that nothing funny is going on; in particular, that we aren't + # dealing with a bare repo. + if os.path.isdir(path) != True: + path = os.path.dirname(path) + darcs_dir = self._u_search_parent_directories(path, '_darcs') + if darcs_dir == None: + return None + return os.path.dirname(darcs_dir) + + def _vcs_init(self, path): + self._u_invoke_client('init', cwd=path) + + def _vcs_destroy(self): + vcs_dir = os.path.join(self.repo, '_darcs') + if os.path.exists(vcs_dir): + shutil.rmtree(vcs_dir) + + def _vcs_add(self, path): + if os.path.isdir(path): + return + self._u_invoke_client('add', path) + + def _vcs_remove(self, path): + if not os.path.isdir(self._u_abspath(path)): + os.remove(os.path.join(self.repo, path)) # darcs notices removal + + def _vcs_update(self, path): + self.__updated.append(path) # work around http://mercurial.selenic.com/bts/issue618 + pass # darcs notices changes + + def _vcs_get_file_contents(self, path, revision=None): + if revision == None: + return base.VCS._vcs_get_file_contents(self, path, revision) + if self.version_cmp(2, 0, 0) == 1: + status,output,error = self._u_invoke_client( \ + 'show', 'contents', '--patch', revision, path) + return output + # Darcs versions < 2.0.0pre2 lack the 'show contents' command + + patch = self._diff(revision, path=path, unicode_output=False) + + # '--output -' to be supported in GNU patch > 2.5.9 + # but that hasn't been released as of June 30th, 2009. + + # Rewrite path to status before the patch we want + args=['patch', '--reverse', path] + status,output,error = self._u_invoke(args, stdin=patch) + + if os.path.exists(os.path.join(self.repo, path)) == True: + contents = base.VCS._vcs_get_file_contents(self, path) + else: + contents = '' + + # Now restore path to it's current incarnation + args=['patch', path] + status,output,error = self._u_invoke(args, stdin=patch) + return contents + + def _vcs_path(self, id, revision): + return self._u_find_id(id, revision) + + def _vcs_isdir(self, path, revision): + if self.version_cmp(2, 3, 1) == 1: + # Sun Nov 15 20:32:06 EST 2009 thomashartman1@gmail.com + # * add versioned show files functionality (darcs show files -p 'some patch') + status,output,error = self._u_invoke_client( \ + 'show', 'files', '--no-files', '--patch', revision) + children = output.rstrip('\n').splitlines() + rpath = '.' + children = [self._u_rel_path(c, rpath) for c in children] + if path in children: + return True + return False + raise NotImplementedError( + 'Darcs versions <= 2.3.1 lack the --patch option for "show files"') + + def _vcs_listdir(self, path, revision): + if self.version_cmp(2, 3, 1) == 1: + # Sun Nov 15 20:32:06 EST 2009 thomashartman1@gmail.com + # * add versioned show files functionality (darcs show files -p 'some patch') + # Wed Dec 9 05:42:21 EST 2009 Luca Molteni <volothamp@gmail.com> + # * resolve issue835 show file with file directory arguments + path = path.rstrip(os.path.sep) + status,output,error = self._u_invoke_client( \ + 'show', 'files', '--patch', revision, path) + files = output.rstrip('\n').splitlines() + if path == '.': + descendents = [self._u_rel_path(f, path) for f in files + if f != '.'] + else: + descendents = [self._u_rel_path(f, path) for f in files + if f.startswith(path)] + return [f for f in descendents if f.count(os.path.sep) == 0] + # Darcs versions <= 2.3.1 lack the --patch option for 'show files' + raise NotImplementedError + + def _vcs_commit(self, commitfile, allow_empty=False): + id = self.get_user_id() + if id == None or '@' not in id: + id = '%s <%s@invalid.com>' % (id, id) + args = ['record', '--all', '--author', id, '--logfile', commitfile] + status,output,error = self._u_invoke_client(*args) + empty_strings = ['No changes!'] + # work around http://mercurial.selenic.com/bts/issue618 + if self._u_any_in_string(empty_strings, output) == True \ + and len(self.__updated) > 0: + time.sleep(1) + for path in self.__updated: + os.utime(os.path.join(self.repo, path), None) + status,output,error = self._u_invoke_client(*args) + self.__updated = [] + # end work around + if self._u_any_in_string(empty_strings, output) == True: + if allow_empty == False: + raise base.EmptyCommit() + # note that darcs does _not_ make an empty revision. + # this returns the last non-empty revision id... + revision = self._vcs_revision_id(-1) + else: + revline = re.compile("Finished recording patch '(.*)'") + match = revline.search(output) + assert match != None, output+error + assert len(match.groups()) == 1 + revision = match.groups()[0] + return revision + + def _revisions(self): + """ + Return a list of revisions in the repository. + """ + status,output,error = self._u_invoke_client('changes', '--xml') + revisions = [] + xml_str = output.encode('unicode_escape').replace(r'\n', '\n') + element = ElementTree.XML(xml_str) + assert element.tag == 'changelog', element.tag + for patch in element.getchildren(): + assert patch.tag == 'patch', patch.tag + for child in patch.getchildren(): + if child.tag == 'name': + text = unescape(unicode(child.text).decode('unicode_escape').strip()) + revisions.append(text) + revisions.reverse() + return revisions + + def _vcs_revision_id(self, index): + revisions = self._revisions() + try: + if index > 0: + return revisions[index-1] + elif index < 0: + return revisions[index] + else: + return None + except IndexError: + return None + + def _diff(self, revision, path=None, unicode_output=True): + revisions = self._revisions() + i = revisions.index(revision) + args = ['diff', '--unified'] + if i+1 < len(revisions): + next_rev = revisions[i+1] + args.extend(['--from-patch', next_rev]) + if path != None: + args.append(path) + kwargs = {'unicode_output':unicode_output} + status,output,error = self._u_invoke_client( + *args, **kwargs) + return output + + def _parse_diff(self, diff_text): + """_parse_diff(diff_text) -> (new,modified,removed) + + `new`, `modified`, and `removed` are lists of files. + + Example diff text:: + + Mon Jan 18 15:19:30 EST 2010 None <None@invalid.com> + * Final state + diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/modified new-BEtestgQtDuD/.be/dir/bugs/modified + --- old-BEtestgQtDuD/.be/dir/bugs/modified 2010-01-18 15:19:30.000000000 -0500 + +++ new-BEtestgQtDuD/.be/dir/bugs/modified 2010-01-18 15:19:30.000000000 -0500 + @@ -1 +1 @@ + -some value to be modified + \ No newline at end of file + +a new value + \ No newline at end of file + diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/moved new-BEtestgQtDuD/.be/dir/bugs/moved + --- old-BEtestgQtDuD/.be/dir/bugs/moved 2010-01-18 15:19:30.000000000 -0500 + +++ new-BEtestgQtDuD/.be/dir/bugs/moved 1969-12-31 19:00:00.000000000 -0500 + @@ -1 +0,0 @@ + -this entry will be moved + \ No newline at end of file + diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/moved2 new-BEtestgQtDuD/.be/dir/bugs/moved2 + --- old-BEtestgQtDuD/.be/dir/bugs/moved2 1969-12-31 19:00:00.000000000 -0500 + +++ new-BEtestgQtDuD/.be/dir/bugs/moved2 2010-01-18 15:19:30.000000000 -0500 + @@ -0,0 +1 @@ + +this entry will be moved + \ No newline at end of file + diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/new new-BEtestgQtDuD/.be/dir/bugs/new + --- old-BEtestgQtDuD/.be/dir/bugs/new 1969-12-31 19:00:00.000000000 -0500 + +++ new-BEtestgQtDuD/.be/dir/bugs/new 2010-01-18 15:19:30.000000000 -0500 + @@ -0,0 +1 @@ + +this entry is new + \ No newline at end of file + diff -rN --unified old-BEtestgQtDuD/.be/dir/bugs/removed new-BEtestgQtDuD/.be/dir/bugs/removed + --- old-BEtestgQtDuD/.be/dir/bugs/removed 2010-01-18 15:19:30.000000000 -0500 + +++ new-BEtestgQtDuD/.be/dir/bugs/removed 1969-12-31 19:00:00.000000000 -0500 + @@ -1 +0,0 @@ + -this entry will be deleted + \ No newline at end of file + + """ + new = [] + modified = [] + removed = [] + lines = diff_text.splitlines() + repodir = os.path.basename(self.repo) + os.path.sep + i = 0 + while i < len(lines): + line = lines[i]; i += 1 + if not line.startswith('diff '): + continue + file_a,file_b = line.split()[-2:] + assert file_a.startswith('old-'), \ + 'missformed file_a %s' % file_a + assert file_b.startswith('new-'), \ + 'missformed file_a %s' % file_b + file = file_a[4:] + assert file_b[4:] == file, \ + 'diff file missmatch %s != %s' % (file_a, file_b) + assert file.startswith(repodir), \ + 'missformed file_a %s' % file_a + file = file[len(repodir):] + lines_added = 0 + lines_removed = 0 + line = lines[i]; i += 1 + assert line.startswith('--- old-'), \ + 'missformed "---" line %s' % line + time_a = line.split('\t')[1] + line = lines[i]; i += 1 + assert line.startswith('+++ new-'), \ + 'missformed "+++" line %s' % line + time_b = line.split('\t')[1] + zero_time = time.strftime('%Y-%m-%d %H:%M:%S.000000000 ', + time.localtime(0)) + # note that zero_time is missing the trailing timezone offset + if time_a.startswith(zero_time): + new.append(file) + elif time_b.startswith(zero_time): + removed.append(file) + else: + modified.append(file) + return (new,modified,removed) + + def _vcs_changed(self, revision): + return self._parse_diff(self._diff(revision)) + + +if libbe.TESTING == True: + base.make_vcs_testcase_subclasses(Darcs, sys.modules[__name__]) + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py new file mode 100644 index 0000000..4df9bc8 --- /dev/null +++ b/libbe/storage/vcs/git.py @@ -0,0 +1,269 @@ +# Copyright (C) 2008-2010 Ben Finney <benf@cybersource.com.au> +# Chris Ball <cjb@laptop.org> +# Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Git_ backend. + +.. _Git: http://git-scm.com/ +""" + +import os +import os.path +import re +import shutil +import unittest + +import libbe +import libbe.ui.util.user +import base + +if libbe.TESTING == True: + import doctest + import sys + + +def new(): + return Git() + +class Git(base.VCS): + """:class:`base.VCS` implementation for Git. + """ + name='git' + client='git' + + def __init__(self, *args, **kwargs): + base.VCS.__init__(self, *args, **kwargs) + self.versioned = True + + def _vcs_version(self): + status,output,error = self._u_invoke_client('--version') + return output.strip() + + def _vcs_get_user_id(self): + status,output,error = \ + self._u_invoke_client('config', 'user.name', expect=(0,1)) + if status == 0: + name = output.rstrip('\n') + else: + name = '' + status,output,error = \ + self._u_invoke_client('config', 'user.email', expect=(0,1)) + if status == 0: + email = output.rstrip('\n') + else: + email = '' + if name != '' or email != '': # got something! + # guess missing info, if necessary + if name == '': + name = libbe.ui.util.user.get_fallback_username() + if email == '': + email = libe.ui.util.user.get_fallback_email() + return libbe.ui.util.user.create_user_id(name, email) + return None # Git has no infomation + + def _vcs_detect(self, path): + if self._u_search_parent_directories(path, '.git') != None : + return True + return False + + def _vcs_root(self, path): + """Find the root of the deepest repository containing path.""" + # Assume that nothing funny is going on; in particular, that we aren't + # dealing with a bare repo. + if os.path.isdir(path) != True: + path = os.path.dirname(path) + status,output,error = self._u_invoke_client('rev-parse', '--git-dir', + cwd=path) + gitdir = os.path.join(path, output.rstrip('\n')) + dirname = os.path.abspath(os.path.dirname(gitdir)) + return dirname + + def _vcs_init(self, path): + self._u_invoke_client('init', cwd=path) + + def _vcs_destroy(self): + vcs_dir = os.path.join(self.repo, '.git') + if os.path.exists(vcs_dir): + shutil.rmtree(vcs_dir) + + def _vcs_add(self, path): + if os.path.isdir(path): + return + self._u_invoke_client('add', path) + + def _vcs_remove(self, path): + if not os.path.isdir(self._u_abspath(path)): + self._u_invoke_client('rm', '-f', path) + + def _vcs_update(self, path): + self._vcs_add(path) + + def _vcs_get_file_contents(self, path, revision=None): + if revision == None: + return base.VCS._vcs_get_file_contents(self, path, revision) + else: + arg = '%s:%s' % (revision,path) + status,output,error = self._u_invoke_client('show', arg) + return output + + def _vcs_path(self, id, revision): + return self._u_find_id(id, revision) + + def _vcs_isdir(self, path, revision): + arg = '%s:%s' % (revision,path) + args = ['ls-tree', arg] + kwargs = {'expect':(0,128)} + status,output,error = self._u_invoke_client(*args, **kwargs) + if status != 0: + if 'not a tree object' in error: + return False + raise base.CommandError(args, status, stderr=error) + return True + + def _vcs_listdir(self, path, revision): + arg = '%s:%s' % (revision,path) + status,output,error = self._u_invoke_client( + 'ls-tree', '--name-only', arg) + return output.rstrip('\n').splitlines() + + def _vcs_commit(self, commitfile, allow_empty=False): + args = ['commit', '--all', '--file', commitfile] + if allow_empty == True: + args.append('--allow-empty') + status,output,error = self._u_invoke_client(*args) + else: + kwargs = {'expect':(0,1)} + status,output,error = self._u_invoke_client(*args, **kwargs) + strings = ['nothing to commit', + 'nothing added to commit'] + if self._u_any_in_string(strings, output) == True: + raise base.EmptyCommit() + full_revision = self._vcs_revision_id(-1) + assert full_revision[:7] in output, \ + 'Mismatched revisions:\n%s\n%s' % (full_revision, output) + return full_revision + + def _vcs_revision_id(self, index): + args = ['rev-list', '--first-parent', '--reverse', 'HEAD'] + kwargs = {'expect':(0,128)} + status,output,error = self._u_invoke_client(*args, **kwargs) + if status == 128: + if error.startswith("fatal: ambiguous argument 'HEAD': unknown "): + return None + raise base.CommandError(args, status, stderr=error) + revisions = output.splitlines() + try: + if index > 0: + return revisions[index-1] + elif index < 0: + return revisions[index] + else: + return None + except IndexError: + return None + + def _diff(self, revision): + status,output,error = self._u_invoke_client('diff', revision) + return output + + def _parse_diff(self, diff_text): + """_parse_diff(diff_text) -> (new,modified,removed) + + `new`, `modified`, and `removed` are lists of files. + + Example diff text:: + + diff --git a/dir/changed b/dir/changed + index 6c3ea8c..2f2f7c7 100644 + --- a/dir/changed + +++ b/dir/changed + @@ -1,3 +1,3 @@ + hi + -there + +everyone and + joe + diff --git a/dir/deleted b/dir/deleted + deleted file mode 100644 + index 225ec04..0000000 + --- a/dir/deleted + +++ /dev/null + @@ -1,3 +0,0 @@ + -in + -the + -beginning + diff --git a/dir/moved b/dir/moved + deleted file mode 100644 + index 5ef102f..0000000 + --- a/dir/moved + +++ /dev/null + @@ -1,4 +0,0 @@ + -the + -ants + -go + -marching + diff --git a/dir/moved2 b/dir/moved2 + new file mode 100644 + index 0000000..5ef102f + --- /dev/null + +++ b/dir/moved2 + @@ -0,0 +1,4 @@ + +the + +ants + +go + +marching + diff --git a/dir/new b/dir/new + new file mode 100644 + index 0000000..94954ab + --- /dev/null + +++ b/dir/new + @@ -0,0 +1,2 @@ + +hello + +world + """ + new = [] + modified = [] + removed = [] + lines = diff_text.splitlines() + for i,line in enumerate(lines): + if not line.startswith('diff '): + continue + file_a,file_b = line.split()[-2:] + assert file_a.startswith('a/'), \ + 'missformed file_a %s' % file_a + assert file_b.startswith('b/'), \ + 'missformed file_a %s' % file_b + file = file_a[2:] + assert file_b[2:] == file, \ + 'diff file missmatch %s != %s' % (file_a, file_b) + if lines[i+1].startswith('new '): + new.append(file) + elif lines[i+1].startswith('index '): + modified.append(file) + elif lines[i+1].startswith('deleted '): + removed.append(file) + return (new,modified,removed) + + def _vcs_changed(self, revision): + return self._parse_diff(self._diff(revision)) + + +if libbe.TESTING == True: + base.make_vcs_testcase_subclasses(Git, sys.modules[__name__]) + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py new file mode 100644 index 0000000..9378336 --- /dev/null +++ b/libbe/storage/vcs/hg.py @@ -0,0 +1,257 @@ +# Copyright (C) 2007-2010 Aaron Bentley and Panometrics, Inc. +# Ben Finney <benf@cybersource.com.au> +# Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Mercurial_ (hg) backend. + +.. _Mercurial: http://mercurial.selenic.com/ +""" + +try: + import mercurial + import mercurial.dispatch + import mercurial.ui +except ImportError: + mercurial = None + +try: + # mercurial >= 1.2 + from mercurial.util import version +except ImportError: + try: + # mercurial <= 1.1.2 + from mercurial.version import get_version as version + except ImportError: + version = None + +import os +import os.path +import re +import shutil +import StringIO +import sys +import time # work around http://mercurial.selenic.com/bts/issue618 + +import libbe +import base + +if libbe.TESTING == True: + import doctest + import unittest + + +def new(): + return Hg() + +class Hg(base.VCS): + """:class:`base.VCS` implementation for Mercurial. + """ + name='hg' + client=None # mercurial module + + def __init__(self, *args, **kwargs): + base.VCS.__init__(self, *args, **kwargs) + self.versioned = True + self.__updated = [] # work around http://mercurial.selenic.com/bts/issue618 + + def _vcs_version(self): + if version == None: + return None + return version() + + def _u_invoke_client(self, *args, **kwargs): + if 'cwd' not in kwargs: + kwargs['cwd'] = self.repo + assert len(kwargs) == 1, kwargs + fullargs = ['--cwd', kwargs['cwd']] + fullargs.extend(args) + stdout = sys.stdout + tmp_stdout = StringIO.StringIO() + sys.stdout = tmp_stdout + cwd = os.getcwd() + mercurial.dispatch.dispatch(fullargs) + os.chdir(cwd) + sys.stdout = stdout + return tmp_stdout.getvalue().rstrip('\n') + + def _vcs_get_user_id(self): + output = self._u_invoke_client( + 'showconfig', 'ui.username').rstrip('\n') + if output != '': + return output + return None + + def _vcs_detect(self, path): + """Detect whether a directory is revision-controlled using Mercurial""" + if self._u_search_parent_directories(path, '.hg') != None: + return True + return False + + def _vcs_root(self, path): + return self._u_invoke_client('root', cwd=path) + + def _vcs_init(self, path): + self._u_invoke_client('init', cwd=path) + + def _vcs_destroy(self): + vcs_dir = os.path.join(self.repo, '.hg') + if os.path.exists(vcs_dir): + shutil.rmtree(vcs_dir) + + def _vcs_add(self, path): + self._u_invoke_client('add', path) + + def _vcs_remove(self, path): + self._u_invoke_client('rm', '--force', path) + + def _vcs_update(self, path): + self.__updated.append(path) # work around http://mercurial.selenic.com/bts/issue618 + + def _vcs_get_file_contents(self, path, revision=None): + if revision == None: + return base.VCS._vcs_get_file_contents(self, path, revision) + else: + return self._u_invoke_client('cat', '-r', revision, path) + + def _vcs_path(self, id, revision): + manifest = self._u_invoke_client( + 'manifest', '--rev', revision).splitlines() + return self._u_find_id_from_manifest(id, manifest, revision=revision) + + def _vcs_isdir(self, path, revision): + output = self._u_invoke_client('manifest', '--rev', revision) + files = output.splitlines() + if path in files: + return False + return True + + def _vcs_listdir(self, path, revision): + output = self._u_invoke_client('manifest', '--rev', revision) + files = output.splitlines() + path = path.rstrip(os.path.sep) + os.path.sep + return [self._u_rel_path(f, path) for f in files if f.startswith(path)] + + def _vcs_commit(self, commitfile, allow_empty=False): + args = ['commit', '--logfile', commitfile] + output = self._u_invoke_client(*args) + # work around http://mercurial.selenic.com/bts/issue618 + strings = ['nothing changed'] + if self._u_any_in_string(strings, output) == True \ + and len(self.__updated) > 0: + time.sleep(1) + for path in self.__updated: + os.utime(os.path.join(self.repo, path), None) + output = self._u_invoke_client(*args) + self.__updated = [] + # end work around + if allow_empty == False: + strings = ['nothing changed'] + if self._u_any_in_string(strings, output) == True: + raise base.EmptyCommit() + return self._vcs_revision_id(-1) + + def _vcs_revision_id(self, index, style='id'): + if index > 0: + index -= 1 + args = ['identify', '--rev', str(int(index)), '--%s' % style] + output = self._u_invoke_client(*args) + id = output.strip() + if id == '000000000000': + return None # before initial commit. + return id + + def _diff(self, revision): + return self._u_invoke_client( + 'diff', '-r', revision, '--git') + + def _parse_diff(self, diff_text): + """_parse_diff(diff_text) -> (new,modified,removed) + + `new`, `modified`, and `removed` are lists of files. + + Example diff text:: + + diff --git a/.be/dir/bugs/modified b/.be/dir/bugs/modified + --- a/.be/dir/bugs/modified + +++ b/.be/dir/bugs/modified + @@ -1,1 +1,1 @@ some value to be modified + -some value to be modified + \ No newline at end of file + +a new value + \ No newline at end of file + diff --git a/.be/dir/bugs/moved b/.be/dir/bugs/moved + deleted file mode 100644 + --- a/.be/dir/bugs/moved + +++ /dev/null + @@ -1,1 +0,0 @@ + -this entry will be moved + \ No newline at end of file + diff --git a/.be/dir/bugs/moved2 b/.be/dir/bugs/moved2 + new file mode 100644 + --- /dev/null + +++ b/.be/dir/bugs/moved2 + @@ -0,0 +1,1 @@ + +this entry will be moved + \ No newline at end of file + diff --git a/.be/dir/bugs/new b/.be/dir/bugs/new + new file mode 100644 + --- /dev/null + +++ b/.be/dir/bugs/new + @@ -0,0 +1,1 @@ + +this entry is new + \ No newline at end of file + diff --git a/.be/dir/bugs/removed b/.be/dir/bugs/removed + deleted file mode 100644 + --- a/.be/dir/bugs/removed + +++ /dev/null + @@ -1,1 +0,0 @@ + -this entry will be deleted + \ No newline at end of file + """ + new = [] + modified = [] + removed = [] + lines = diff_text.splitlines() + for i,line in enumerate(lines): + if not line.startswith('diff '): + continue + file_a,file_b = line.split()[-2:] + assert file_a.startswith('a/'), \ + 'missformed file_a %s' % file_a + assert file_b.startswith('b/'), \ + 'missformed file_a %s' % file_b + file = file_a[2:] + assert file_b[2:] == file, \ + 'diff file missmatch %s != %s' % (file_a, file_b) + if lines[i+1].startswith('new '): + new.append(file) + elif lines[i+1].startswith('deleted '): + removed.append(file) + else: + modified.append(file) + return (new,modified,removed) + + def _vcs_changed(self, revision): + return self._parse_diff(self._diff(revision)) + + +if libbe.TESTING == True: + base.make_vcs_testcase_subclasses(Hg, sys.modules[__name__]) + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/ui/__init__.py b/libbe/ui/__init__.py new file mode 100644 index 0000000..3b461a5 --- /dev/null +++ b/libbe/ui/__init__.py @@ -0,0 +1,15 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py new file mode 100644 index 0000000..dd10954 --- /dev/null +++ b/libbe/ui/command_line.py @@ -0,0 +1,340 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Chris Ball <cjb@laptop.org> +# Gianluca Montecchi <gian@grys.it> +# Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +A command line interface to Bugs Everywhere. +""" + +import optparse +import os +import sys + +import libbe +import libbe.bugdir +import libbe.command +import libbe.command.util +import libbe.version +import libbe.ui.util.pager + +if libbe.TESTING == True: + import doctest + +class CallbackExit (Exception): + pass + +class CmdOptionParser(optparse.OptionParser): + def __init__(self, command): + self.command = command + optparse.OptionParser.__init__(self) + self.remove_option('-h') + self.disable_interspersed_args() + self._option_by_name = {} + for option in self.command.options: + self._add_option(option) + self.set_usage(command.usage()) + + + def _add_option(self, option): + option.validate() + self._option_by_name[option.name] = option + long_opt = '--%s' % option.name + if option.short_name != None: + short_opt = '-%s' % option.short_name + assert '_' not in option.name, \ + 'Non-reconstructable option name %s' % option.name + kwargs = {'dest':option.name.replace('-', '_'), + 'help':option.help} + if option.arg == None: # a callback option + kwargs['action'] = 'callback' + kwargs['callback'] = self.callback + elif option.arg.type == 'bool': + kwargs['action'] = 'store_true' + kwargs['metavar'] = None + kwargs['default'] = False + else: + kwargs['type'] = option.arg.type + kwargs['action'] = 'store' + kwargs['metavar'] = option.arg.metavar + kwargs['default'] = option.arg.default + if option.short_name != None: + opt = optparse.Option(short_opt, long_opt, **kwargs) + else: + opt = optparse.Option(long_opt, **kwargs) + opt._option = option + self.add_option(opt) + + def parse_args(self, args=None, values=None): + args = self._get_args(args) + options,parsed_args = optparse.OptionParser.parse_args( + self, args=args, values=values) + options = options.__dict__ + for name,value in options.items(): + if '_' in name: # reconstruct original option name + options[name.replace('_', '-')] = options.pop(name) + for name,value in options.items(): + if value == '--complete': + argument = None + option = self._option_by_name[name] + if option.arg != None: + argument = option.arg + fragment = None + indices = [i for i,arg in enumerate(args) + if arg == '--complete'] + for i in indices: + assert i > 0 # this --complete is an option value + if args[i-1] in ['--%s' % o.name + for o in self.command.options]: + name = args[i-1][2:] + if name == option.name: + break + elif option.short_name != None \ + and args[i-1].startswith('-') \ + and args[i-1].endswith(option.short_name): + break + if i+1 < len(args): + fragment = args[i+1] + self.complete(argument, fragment) + for i,arg in enumerate(parsed_args): + if arg == '--complete': + if i > 0 and self.command.name == 'be': + break # let this pass through for the command parser to handle + elif i < len(self.command.args): + argument = self.command.args[i] + elif len(self.command.args) == 0: + break # command doesn't take arguments + else: + argument = self.command.args[-1] + if argument.repeatable == False: + raise libbe.command.UserError('Too many arguments') + fragment = None + if i < len(parsed_args) - 1: + fragment = parsed_args[i+1] + self.complete(argument, fragment) + if len(parsed_args) > len(self.command.args) \ + and self.command.args[-1].repeatable == False: + raise libbe.command.UserError('Too many arguments') + for arg in self.command.args[len(parsed_args):]: + if arg.optional == False: + raise libbe.command.UserError( + 'Missing required argument %s' % arg.metavar) + return (options, parsed_args) + + def callback(self, option, opt, value, parser): + command_option = option._option + if command_option.name == 'complete': + argument = None + fragment = None + if len(parser.rargs) > 0: + fragment = parser.rargs[0] + self.complete(argument, fragment) + else: + print >> self.command.stdout, command_option.callback( + self.command, command_option, value) + raise CallbackExit + + def complete(self, argument=None, fragment=None): + comps = self.command.complete(argument, fragment) + if fragment != None: + comps = [c for c in comps if c.startswith(fragment)] + if len(comps) > 0: + print >> self.command.stdout, '\n'.join(comps) + raise CallbackExit + +class BE (libbe.command.Command): + """Class for parsing the command line arguments for `be`. + This class does not contain a useful _run() method. Call this + module's main() function instead. + + >>> ui = libbe.command.UserInterface() + >>> ui.io.stdout = sys.stdout + >>> be = BE(ui=ui) + >>> ui.io.setup_command(be) + >>> p = CmdOptionParser(be) + >>> p.exit_after_callback = False + >>> try: + ... options,args = p.parse_args(['--help']) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + ... except CallbackExit: + ... pass + usage: be [options] [COMMAND [command-options] [COMMAND-ARGS ...]] + <BLANKLINE> + Options: + -h, --help Print a help message. + <BLANKLINE> + --complete Print a list of possible completions. + <BLANKLINE> + --version Print version string. + ... + >>> try: + ... options,args = p.parse_args(['--complete']) # doctest: +ELLIPSIS + ... except CallbackExit: + ... print ' got callback' + --help + --complete + --version + ... + subscribe + tag + target + got callback + """ + name = 'be' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='version', + help='Print version string.', + callback=self.version), + libbe.command.Option(name='full-version', + help='Print full version information.', + callback=self.full_version), + libbe.command.Option(name='repo', short_name='r', + help='Select BE repository (see `be help repo`) rather ' + 'than the current directory.', + arg=libbe.command.Argument( + name='repo', metavar='REPO', default='.', + completion_callback=libbe.command.util.complete_path)), + libbe.command.Option(name='paginate', + help='Pipe all output into less (or if set, $PAGER).'), + libbe.command.Option(name='no-pager', + help='Do not pipe git output into a pager.'), + ]) + self.args.extend([ + libbe.command.Argument( + name='command', optional=False, + completion_callback=libbe.command.util.complete_command), + libbe.command.Argument( + name='args', optional=True, repeatable=True) + ]) + + def usage(self): + return 'usage: be [options] [COMMAND [command-options] [COMMAND-ARGS ...]]' + + def _long_help(self): + cmdlist = [] + for name in libbe.command.commands(): + Class = libbe.command.get_command_class(command_name=name) + assert hasattr(Class, '__doc__') and Class.__doc__ != None, \ + 'Command class %s missing docstring' % Class + cmdlist.append((name, Class.__doc__.splitlines()[0])) + cmdlist.sort() + longest_cmd_len = max([len(name) for name,desc in cmdlist]) + ret = ['Bugs Everywhere - Distributed bug tracking', + '', 'Supported commands'] + for name, desc in cmdlist: + numExtraSpaces = longest_cmd_len-len(name) + ret.append('be %s%*s %s' % (name, numExtraSpaces, '', desc)) + ret.extend(['', 'Run', ' be help [command]', 'for more information.']) + return '\n'.join(ret) + + def version(self, *args): + return libbe.version.version(verbose=False) + + def full_version(self, *args): + return libbe.version.version(verbose=True) + +class CommandLine (libbe.command.UserInterface): + def __init__(self, *args, **kwargs): + libbe.command.UserInterface.__init__(self, *args, **kwargs) + self.restrict_file_access = False + self.storage_callbacks = None + def help(self): + be = BE(ui=self) + self.setup_command(be) + return be.help() + +def dispatch(ui, command, args): + parser = CmdOptionParser(command) + try: + options,args = parser.parse_args(args) + ret = ui.run(command, options, args) + except CallbackExit: + return 0 + except UnicodeDecodeError, e: + print >> ui.io.stdout, '\n'.join([ + 'ERROR:', str(e), + 'You should set a locale that supports unicode, e.g.', + ' export LANG=en_US.utf8', + 'See http://docs.python.org/library/locale.html for details', + ]) + return 1 + except libbe.command.UserError, e: + print >> ui.io.stdout, 'ERROR:\n', e + return 1 + except libbe.storage.ConnectionError, e: + print >> ui.io.stdout, 'Connection Error:\n', e + return 1 + except (libbe.util.id.MultipleIDMatches, libbe.util.id.NoIDMatches, + libbe.util.id.InvalidIDStructure), e: + print >> ui.io.stdout, 'Invalid id:\n', e + return 1 + finally: + command.cleanup() + return ret + +def main(): + io = libbe.command.StdInputOutput() + ui = CommandLine(io) + be = BE(ui=ui) + ui.setup_command(be) + + parser = CmdOptionParser(be) + try: + options,args = parser.parse_args() + except CallbackExit: + return 0 + except libbe.command.UserError, e: + if str(e).endswith('COMMAND'): + # no command given, print usage string + print >> ui.io.stdout, 'ERROR:' + print >> ui.io.stdout, be.usage(), '\n', e + print >> ui.io.stdout, 'For example, try' + print >> ui.io.stdout, ' be help' + else: + print >> ui.io.stdout, 'ERROR:\n', e + return 1 + + command_name = args.pop(0) + try: + Class = libbe.command.get_command_class(command_name=command_name) + except libbe.command.UnknownCommand, e: + print >> ui.io.stdout, e + return 1 + + ui.storage_callbacks = libbe.command.StorageCallbacks(options['repo']) + command = Class(ui=ui) + ui.setup_command(command) + + if command.name in ['comment', 'commit', 'import-xml', 'serve']: + paginate = 'never' + else: + paginate = 'auto' + if options['paginate'] == True: + paginate = 'always' + if options['no-pager'] == True: + paginate = 'never' + libbe.ui.util.pager.run_pager(paginate) + + ret = dispatch(ui, command, args) + ui.cleanup() + return ret + +if __name__ == '__main__': + sys.exit(main()) diff --git a/libbe/ui/util/__init__.py b/libbe/ui/util/__init__.py new file mode 100644 index 0000000..3b461a5 --- /dev/null +++ b/libbe/ui/util/__init__.py @@ -0,0 +1,15 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. diff --git a/libbe/ui/util/editor.py b/libbe/ui/util/editor.py new file mode 100644 index 0000000..1a430c7 --- /dev/null +++ b/libbe/ui/util/editor.py @@ -0,0 +1,115 @@ +# Bugs Everywhere, a distributed bugtracker +# Copyright (C) 2008-2010 Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +Define editor_string(), a function that invokes an editor to accept +user-produced text as a string. +""" + +import codecs +import locale +import os +import sys +import tempfile + +import libbe +import libbe.util.encoding + +if libbe.TESTING == True: + import doctest + + +comment_marker = u"== Anything below this line will be ignored\n" + +class CantFindEditor(Exception): + def __init__(self): + Exception.__init__(self, "Can't find editor to get string from") + +def editor_string(comment=None, encoding=None): + """Invokes the editor, and returns the user-produced text as a string + + >>> if "EDITOR" in os.environ: + ... del os.environ["EDITOR"] + >>> if "VISUAL" in os.environ: + ... del os.environ["VISUAL"] + >>> editor_string() + Traceback (most recent call last): + CantFindEditor: Can't find editor to get string from + >>> os.environ["EDITOR"] = "echo bar > " + >>> editor_string() + u'bar\\n' + >>> os.environ["VISUAL"] = "echo baz > " + >>> editor_string() + u'baz\\n' + >>> os.environ["VISUAL"] = "echo 'baz\\n== Anything below this line will be ignored\\nHi' > " + >>> editor_string() + u'baz\\n' + >>> del os.environ["EDITOR"] + >>> del os.environ["VISUAL"] + """ + if encoding == None: + encoding = libbe.util.encoding.get_filesystem_encoding() + editor = None + for name in ('VISUAL', 'EDITOR'): + if name in os.environ and os.environ[name] != '': + editor = os.environ[name] + break + if editor == None: + raise CantFindEditor() + fhandle, fname = tempfile.mkstemp() + try: + if comment is not None: + cstring = u'\n'+comment_string(comment) + os.write(fhandle, cstring.encode(encoding)) + os.close(fhandle) + oldmtime = os.path.getmtime(fname) + os.system("%s %s" % (editor, fname)) + output = libbe.util.encoding.get_file_contents( + fname, encoding=encoding, decode=True) + output = trimmed_string(output) + if output.rstrip('\n') == "": + output = None + finally: + os.unlink(fname) + return output + + +def comment_string(comment): + """ + >>> comment_string('hello') == comment_marker+"hello" + True + """ + return comment_marker + comment + + +def trimmed_string(instring): + """ + >>> trimmed_string("hello\\n"+comment_marker) + u'hello\\n' + >>> trimmed_string("hi!\\n" + comment_string('Booga')) + u'hi!\\n' + """ + out = [] + for line in instring.splitlines(True): + if line.startswith(comment_marker): + break + out.append(line) + return ''.join(out) + +if libbe.TESTING == True: + suite = doctest.DocTestSuite() diff --git a/libbe/ui/util/pager.py b/libbe/ui/util/pager.py new file mode 100644 index 0000000..88b58af --- /dev/null +++ b/libbe/ui/util/pager.py @@ -0,0 +1,65 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +Automatic pager for terminal output (a la Git). +""" + +import sys, os, select + +# see http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby +def run_pager(paginate='auto'): + """ + paginate should be one of 'never', 'auto', or 'always'. + + usage: just call this function and continue using sys.stdout like + you normally would. + """ + if paginate == 'never' \ + or sys.platform == 'win32' \ + or not hasattr(sys.stdout, 'isatty') \ + or sys.stdout.isatty() == False: + return + + if paginate == 'auto': + if 'LESS' not in os.environ: + os.environ['LESS'] = '' # += doesn't work on undefined var + # don't page if the input is short enough + os.environ['LESS'] += ' -FRX' + if 'PAGER' in os.environ: + pager = os.environ['PAGER'] + else: + pager = 'less' + + read_fd, write_fd = os.pipe() + if os.fork() == 0: + # child process + os.close(read_fd) + os.close(0) + os.dup2(write_fd, 1) + os.close(write_fd) + if hasattr(sys.stderr, 'isatty') and sys.stderr.isatty() == True: + os.dup2(1, 2) + return + + # parent process, become pager + os.close(write_fd) + os.dup2(read_fd, 0) + os.close(read_fd) + + # Wait until we have input before we start the pager + select.select([0], [], []) + os.execlp(pager, pager) diff --git a/libbe/ui/util/user.py b/libbe/ui/util/user.py new file mode 100644 index 0000000..460a1dd --- /dev/null +++ b/libbe/ui/util/user.py @@ -0,0 +1,134 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Tools for getting, setting, creating, and parsing the user's ID. + +IDs will look like 'John Doe <jdoe@example.com>'. Note that the +:mod:`libbe.storage.vcs.arch <Arch VCS backend>` *enforces* IDs with +this format. + +Do not confuse the user IDs discussed in this module, which refer to +humans, with the "user IDs" discussed in :mod:`libbe.util.id`, which +are human-readable tags refering to objects. +""" + +try: + from email.utils import formataddr, parseaddr +except ImportErrror: # adjust to old python < 2.5 + from email.Utils import formataddr, parseaddr +import os +import re +from socket import gethostname + +import libbe +import libbe.storage.util.config + +def get_fallback_username(): + """Return a username extracted from environmental variables. + """ + name = None + for env in ["LOGNAME", "USERNAME"]: + if os.environ.has_key(env): + name = os.environ[env] + break + assert name != None + return name + +def get_fallback_email(): + """Return an email address extracted from environmental variables. + """ + hostname = gethostname() + name = get_fallback_username() + return "%s@%s" % (name, hostname) + +def create_user_id(name, email=None): + """Create a user ID string from given `name` and `email` strings. + + Examples + -------- + + >>> create_user_id("John Doe", "jdoe@example.com") + 'John Doe <jdoe@example.com>' + >>> create_user_id("John Doe") + 'John Doe' + + See Also + -------- + parse_user_id : inverse + """ + assert len(name) > 0 + if email == None or len(email) == 0: + return name + else: + return formataddr((name, email)) + +def parse_user_id(value): + """Parse a user ID string into `name` and `email` strings. + + Examples + -------- + + >>> parse_user_id("John Doe <jdoe@example.com>") + ('John Doe', 'jdoe@example.com') + >>> parse_user_id("John Doe") + ('John Doe', None) + >>> parse_user_id("John Doe <jdoe@example.com><what?>") + ('John Doe', 'jdoe@example.com') + + See Also + -------- + create_user_id : inverse + """ + if '<' not in value: + return (value, None) + return parseaddr(value) + +def get_user_id(storage=None): + """Return a user ID, checking a list of possible sources. + + The source order is: + + 1. Global BE configuration. + 2. `storage.get_user_id`, if that function is defined. + 3. :func:`get_fallback_username` and :func:`get_fallback_email`. + + Notes + ----- + Sometimes the storage will keep track of the user ID (e.g. most + VCSs, see :meth:`libbe.storage.vcs.base.VCS.get_user_id`). If so, + we prefer that ID to the fallback, since the user has likely + configured it directly. + """ + user = libbe.storage.util.config.get_val('user') + if user != None: + return user + if storage != None and hasattr(storage, 'get_user_id'): + user = storage.get_user_id() + if user != None: + return user + name = get_fallback_username() + email = get_fallback_email() + user = create_user_id(name, email) + return user + +def set_user_id(user_id): + """Set the user ID in a user's BE configuration. + + See Also + -------- + libbe.storage.util.config.set_val + """ + user = libbe.storage.util.config.set_val('user', user_id) diff --git a/libbe/util/__init__.py b/libbe/util/__init__.py new file mode 100644 index 0000000..0f4850f --- /dev/null +++ b/libbe/util/__init__.py @@ -0,0 +1,24 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +Miscellaneous utilities. +""" + +class InvalidObject (object): + """An object that won't come up by accident.""" + pass + diff --git a/libbe/util/encoding.py b/libbe/util/encoding.py new file mode 100644 index 0000000..8eea438 --- /dev/null +++ b/libbe/util/encoding.py @@ -0,0 +1,91 @@ +# Copyright (C) 2008-2010 Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +Support input/output/filesystem encodings (e.g. UTF-8). +""" + +import codecs +import locale +import sys +import types + +import libbe +if libbe.TESTING == True: + import doctest + + +ENCODING = None # override get_encoding() output by setting this + +def get_encoding(): + """ + Guess a useful input/output/filesystem encoding... Maybe we need + seperate encodings for input/output and filesystem? Hmm... + """ + if ENCODING != None: + return ENCODING + encoding = locale.getpreferredencoding() or sys.getdefaultencoding() + if sys.platform != 'win32' or sys.version_info[:2] > (2, 3): + encoding = locale.getlocale(locale.LC_TIME)[1] or encoding + # Python 2.3 on windows doesn't know about 'XYZ' alias for 'cpXYZ' + return encoding + +def get_input_encoding(): + return get_encoding() + +def get_output_encoding(): + return get_encoding() + +def get_filesystem_encoding(): + return get_encoding() + +def known_encoding(encoding): + """ + >>> known_encoding("highly-unlikely-encoding") + False + >>> known_encoding(get_encoding()) + True + """ + try: + codecs.lookup(encoding) + return True + except LookupError: + return False + +def get_file_contents(path, mode='r', encoding=None, decode=False): + if decode == True: + if encoding == None: + encoding = get_filesystem_encoding() + f = codecs.open(path, mode, encoding) + else: + f = open(path, mode) + contents = f.read() + f.close() + return contents + +def set_file_contents(path, contents, mode='w', encoding=None): + if type(contents) == types.UnicodeType: + if encoding == None: + encoding = get_filesystem_encoding() + f = codecs.open(path, mode, encoding) + else: + f = open(path, mode) + f.write(contents) + f.close() + +if libbe.TESTING == True: + suite = doctest.DocTestSuite() diff --git a/libbe/util/id.py b/libbe/util/id.py new file mode 100644 index 0000000..9192ac8 --- /dev/null +++ b/libbe/util/id.py @@ -0,0 +1,713 @@ +# Copyright (C) 2008-2010 Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Handle ID creation and parsing. + +Format +====== + +BE IDs are formatted:: + + <bug-directory>[/<bug>[/<comment>]] + +where each ``<..>`` is a UUID. For example:: + + bea86499-824e-4e77-b085-2d581fa9ccab/3438b72c-6244-4f1d-8722-8c8d41484e35 + +refers to bug ``3438b72c-6244-4f1d-8722-8c8d41484e35`` which is +located in bug directory ``bea86499-824e-4e77-b085-2d581fa9ccab``. +This is a bit of a mouthful, so you can truncate each UUID so long as +it remains unique. For example:: + + bea/343 + +If there were two bugs ``3438...`` and ``343a...`` in ``bea``, you'd +have to use:: + + bea/3438 + +BE will only truncate each UUID down to three characters to slightly +future-proof the short user ids. However, if you want to save keystrokes +and you *know* there is only one bug directory, feel free to truncate +all the way to zero characters:: + + /3438 + +Cross references +================ + +To refer to other bug-directories/bugs/comments from bug comments, simply +enclose the ID in pound signs (``#``). BE will automatically expand the +truncations to the full UUIDs before storing the comment, and the reference +will be appropriately truncated (and hyperlinked, if possible) when the +comment is displayed. + +Scope +===== + +Although bug and comment IDs always appear in compound references, +UUIDs at each level are globally unique. For example, comment +``bea/343/ba96f1c0-ba48-4df8-aaf0-4e3a3144fc46`` will *only* appear +under ``bea/343``. The prefix (``bea/343``) allows BE to reduce +caching global comment-lookup tables and enables easy error messages +("I couldn't find ``bea/343/ba9`` because I don't know where the +``bea`` bug directory is located"). +""" + +import os.path +import re + +import libbe + +if libbe.TESTING == True: + import doctest + import sys + import unittest + +try: + from uuid import uuid4 # Python >= 2.5 + def uuid_gen(): + id = uuid4() + idstr = id.urn + start = "urn:uuid:" + assert idstr.startswith(start) + return idstr[len(start):] +except ImportError: + import os + import sys + from subprocess import Popen, PIPE + + def uuid_gen(): + # Shell-out to system uuidgen + args = ['uuidgen', 'r'] + try: + if sys.platform != "win32": + q = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) + else: + # win32 don't have os.execvp() so have to run command in a shell + q = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE, + shell=True, cwd=cwd) + except OSError, e : + strerror = "%s\nwhile executing %s" % (e.args[1], args) + raise OSError, strerror + output, error = q.communicate() + status = q.wait() + if status != 0: + strerror = "%s\nwhile executing %s" % (status, args) + raise Exception, strerror + return output.rstrip('\n') + + +HIERARCHY = ['bugdir', 'bug', 'comment'] +"""Keep track of the object type hierarchy. +""" + +class MultipleIDMatches (ValueError): + """Multiple IDs match the given user ID. + + Parameters + ---------- + id : str + The not-specific-enough truncated UUID. + common : str + The initial characters common to all matching UUIDs. + matches : list of str + The list of possibly matching UUIDs. + """ + def __init__(self, id, common, matches): + msg = ('More than one id matches %s. ' + 'Please be more specific (%s*).\n%s' % (id, common, matches)) + ValueError.__init__(self, msg) + self.id = id + self.common = common + self.matches = matches + +class NoIDMatches (KeyError): + """No IDs match the given user ID. + + Parameters + ---------- + id : str + The not-matching, possibly truncated UUID. + possible_ids : list of str + The list of potential UUIDs at that level. + msg : str, optional + A helpful message explaining what went wrong. + """ + def __init__(self, id, possible_ids, msg=None): + KeyError.__init__(self, id) + self.id = id + self.possible_ids = possible_ids + self.msg = msg + def __str__(self): + if self.msg == None: + return 'No id matches %s.\n%s' % (self.id, self.possible_ids) + return self.msg + +class InvalidIDStructure (KeyError): + """A purported ID does not have the appropriate syntax. + + Parameters + ---------- + id : str + The purported ID. + msg : str, optional + A helpful message explaining what went wrong. + """ + def __init__(self, id, msg=None): + KeyError.__init__(self, id) + self.id = id + self.msg = msg + def __str__(self): + if self.msg == None: + return 'Invalid id structure "%s"' % self.id + return self.msg + +def _assemble(args, check_length=False): + """Join a bunch of level UUIDs into a single ID. + + See Also + -------- + _split : inverse + """ + args = list(args) + for i,arg in enumerate(args): + if arg == None: + args[i] = '' + id = '/'.join(args) + if check_length == True: + assert len(args) > 0, args + if len(args) > len(HIERARCHY): + raise InvalidIDStructure( + id, '%d > %d levels in "%s"' % (len(args), len(HIERARCHY), id)) + return id + +def _split(id, check_length=False): + """Split an ID into a list of level UUIDs. + + See Also + -------- + _assemble : inverse + """ + args = id.split('/') + for i,arg in enumerate(args): + if arg == '': + args[i] = None + if check_length == True: + assert len(args) > 0, args + if len(args) > len(HIERARCHY): + raise InvalidIDStructure( + id, '%d > %d levels in "%s"' % (len(args), len(HIERARCHY), id)) + return args + +def _truncate(uuid, other_uuids, min_length=3): + """Truncate a UUID to the shortest length >= `min_length` such that it + is *not* a truncated form of a UUID in `other_uuids`. + + Parameters + ---------- + uuid : str + The UUID to truncate. + other_uuids : list of str + The other UUIDs which the truncation *might* (but doesn't) refer + to. + min_length : int + Avoid rapidly outdated truncations, even if they are unique now. + + See Also + -------- + _expand : inverse + """ + if min_length == -1: + return uuid + chars = min_length + for id in other_uuids: + if id == uuid: + continue + while (id[:chars] == uuid[:chars]): + chars+=1 + return uuid[:chars] + +def _expand(truncated_id, common, other_ids): + """Expand a truncated UUID. + + Parameters + ---------- + truncated_id : str + The ID to expand. + common : str + The common portion `truncated_id` shares with the UUIDs in + `other_ids`. Not used by ``_expand``, but passed on to the + matching exceptions if they occur. + other_uuids : list of str + The other UUIDs which the truncation *might* (but doesn't) refer + to. + + Raises + ------ + NoIDMatches + MultipleIDMatches + + See Also + -------- + _expand : inverse + """ + other_ids = list(other_ids) + if len(other_ids) == 0: + raise NoIDMatches(truncated_id, other_ids) + if truncated_id == None: + if len(other_ids) == 1: + return other_ids[0] + raise MultipleIDMatches(truncated_id, common, other_ids) + matches = [] + other_ids = list(other_ids) + for id in other_ids: + if id.startswith(truncated_id): + if id == truncated_id: + return id + matches.append(id) + if len(matches) > 1: + raise MultipleIDMatches(truncated_id, common, matches) + if len(matches) == 0: + raise NoIDMatches(truncated_id, other_ids) + return matches[0] + + +class ID (object): + """Store an object ID and produce various representations. + + Parameters + ---------- + object : :class:`~libbe.bugdir.BugDir` or :class:`~libbe.bug.Bug` or :class:`~libbe.comment.Comment` + The object that the ID applies to. + type : 'bugdir' or 'bug' or 'comment' + The type of the object. + + Notes + ----- + + IDs have several formats specialized for different uses. + + In storage, all objects are represented by their uuid alone, + because that is the simplest globally unique identifier. You can + generate ids of this sort with the .storage() method. Because an + object's storage may be distributed across several chunks, and the + chunks may not have their own uuid, we generate chunk ids by + prepending the objects uuid to the chunk name. The user id types + do not support this chunk extension feature. + + For users, the full uuids are a bit overwhelming, so we truncate + them while retaining local uniqueness (with regards to the other + objects currently in storage). We also prepend truncated parent + ids for two reasons: + + 1. So that a user can locate the repository containing the + referenced object. It would be hard to find bug ``XYZ`` if + that's all you knew. Much easier with ``ABC/XYZ``, where + ``ABC`` is the bugdir. Each project can publish a list of + bugdir-id-to-location mappings, e.g.:: + + ABC...(full uuid)...DEF https://server.com/projectX/be/ + + which is easier than publishing all-object-ids-to-location + mappings. + + 2. Because it's easier to generate and parse truncated ids if you + don't have to fetch all the ids in the storage repository but + can restrict yourself to a specific branch. + + You can generate ids of this sort with the :meth:`user` method, + although in order to preform the truncation, your object (and its + parents must define a `sibling_uuids` method. + + While users can use the convenient short user ids in the short + term, the truncation will inevitably lead to name collision. To + avoid that, we provide a non-truncated form of the short user ids + via the :meth:`long_user` method. These long user ids should be + converted to short user ids by intelligent user interfaces. + + See Also + -------- + parse_user : get uuids back out of the user ids. + short_to_long_user : convert a single short user id to a long user id. + long_to_short_user : convert a single long user id to a short user id. + short_to_long_text : scan text for user ids & convert to long user ids. + long_to_short_text : scan text for long user ids & convert to short user ids. + """ + def __init__(self, object, type): + self._object = object + self._type = type + assert self._type in HIERARCHY, self._type + + def storage(self, *args): + return _assemble([self._object.uuid]+list(args)) + + def _ancestors(self): + ret = [self._object] + index = HIERARCHY.index(self._type) + if index == 0: + return ret + o = self._object + for i in range(index, 0, -1): + parent_name = HIERARCHY[i-1] + o = getattr(o, parent_name, None) + ret.insert(0, o) + return ret + + def long_user(self): + return _assemble([o.uuid for o in self._ancestors()], + check_length=True) + + def user(self): + ids = [] + for o in self._ancestors(): + if o == None: + ids.append(None) + else: + ids.append(_truncate(o.uuid, o.sibling_uuids())) + return _assemble(ids, check_length=True) + +def child_uuids(child_storage_ids): + """Extract uuid children from other children generated by + :meth:`ID.storage`. + + This is useful for separating data belonging to a particular + object directly from entries for its child objects. Since the + :class:`~libbe.storage.base.Storage` backend doesn't distinguish + between the two. + + Examples + -------- + + >>> list(child_uuids(['abc123/values', '123abc', '123def'])) + ['123abc', '123def'] + """ + for id in child_storage_ids: + fields = _split(id) + if len(fields) == 1: + yield fields[0] + +def long_to_short_user(bugdirs, id): + """Convert a long user ID to a short user ID (see :class:`ID`). + The list of bugdirs allows uniqueness-maintaining truncation of + the bugdir portion of the ID. + + See Also + -------- + short_to_long_user : inverse + long_to_short_text : conversion on a block of text + """ + ids = _split(id, check_length=True) + matching_bugdirs = [bd for bd in bugdirs if bd.uuid == ids[0]] + if len(matching_bugdirs) == 0: + raise NoIDMatches(id, [bd.uuid for bd in bugdirs]) + elif len(matching_bugdirs) > 1: + raise MultipleIDMatches(id, '', [bd.uuid for bd in bugdirs]) + bugdir = matching_bugdirs[0] + objects = [bugdir] + if len(ids) >= 2: + bug = bugdir.bug_from_uuid(ids[1]) + objects.append(bug) + if len(ids) >= 3: + comment = bug.comment_from_uuid(ids[2]) + objects.append(comment) + for i,obj in enumerate(objects): + ids[i] = _truncate(ids[i], obj.sibling_uuids()) + return _assemble(ids) + +def short_to_long_user(bugdirs, id): + """Convert a short user ID to a long user ID (see :class:`ID`). The + list of bugdirs allows uniqueness-checking during expansion of the + bugdir portion of the ID. + + See Also + -------- + long_to_short_user : inverse + short_to_long_text : conversion on a block of text + """ + ids = _split(id, check_length=True) + ids[0] = _expand(ids[0], common=None, + other_ids=[bd.uuid for bd in bugdirs]) + if len(ids) == 1: + return _assemble(ids) + bugdir = [bd for bd in bugdirs if bd.uuid == ids[0]][0] + ids[1] = _expand(ids[1], common=bugdir.id.user(), + other_ids=bugdir.uuids()) + if len(ids) == 2: + return _assemble(ids) + bug = bugdir.bug_from_uuid(ids[1]) + ids[2] = _expand(ids[2], common=bug.id.user(), + other_ids=bug.uuids()) + return _assemble(ids) + + +REGEXP = '#([-a-f0-9]*)(/[-a-g0-9]*)?(/[-a-g0-9]*)?#' +"""Regular expression for matching IDs (both short and long) in text. +""" + +class IDreplacer (object): + """Helper class for ID replacement in text. + + Reassembles the match elements from :data:`REGEXP` matching + into the original ID, for easier replacement. + + See Also + -------- + short_to_long_text, long_to_short_text + """ + def __init__(self, bugdirs, replace_fn, wrap=True): + self.bugdirs = bugdirs + self.replace_fn = replace_fn + self.wrap = wrap + def __call__(self, match): + ids = [] + for m in match.groups(): + if m == None: + m = '' + ids.append(m) + replacement = self.replace_fn(self.bugdirs, ''.join(ids)) + if self.wrap == True: + return '#%s#' % replacement + return replacement + +def short_to_long_text(bugdirs, text): + """Convert short user IDs to long user IDs in text (see :class:`ID`). + The list of bugdirs allows uniqueness-checking during expansion of + the bugdir portion of the ID. + + See Also + -------- + short_to_long_user : conversion on a single ID + long_to_short_text : inverse + """ + return re.sub(REGEXP, IDreplacer(bugdirs, short_to_long_user), text) + +def long_to_short_text(bugdirs, text): + """Convert long user IDs to short user IDs in text (see :class:`ID`). + The list of bugdirs allows uniqueness-maintaining truncation of + the bugdir portion of the ID. + + See Also + -------- + long_to_short_user : conversion on a single ID + short_to_long_text : inverse + """ + return re.sub(REGEXP, IDreplacer(bugdirs, long_to_short_user), text) + +def residual(base, fragment): + """Split the short ID `fragment` into a portion corresponding + to `base`, and a portion inside `base`. + + Examples + -------- + + >>> residual('ABC/DEF/', '//GHI') + ('//', 'GHI') + >>> residual('ABC/DEF/', '/D/GHI') + ('/D/', 'GHI') + >>> residual('ABC/DEF', 'A/D/GHI') + ('A/D/', 'GHI') + >>> residual('ABC/DEF', 'A/D/GHI/JKL') + ('A/D/', 'GHI/JKL') + """ + base = base.rstrip('/') + '/' + ids = fragment.split('/') + base_count = base.count('/') + root_ids = ids[:base_count] + [''] + residual_ids = ids[base_count:] + return ('/'.join(root_ids), '/'.join(residual_ids)) + +def _parse_user(id): + """Parse a user ID (see :class:`ID`), returning a dict of parsed + information. + + The returned dict will contain a value for "type" (from + :data:`HIERARCHY`) and values for the levels that are defined. + + Examples + -------- + + >>> _parse_user('ABC/DEF/GHI') == \\ + ... {'bugdir':'ABC', 'bug':'DEF', 'comment':'GHI', 'type':'comment'} + True + >>> _parse_user('ABC/DEF') == \\ + ... {'bugdir':'ABC', 'bug':'DEF', 'type':'bug'} + True + >>> _parse_user('ABC') == \\ + ... {'bugdir':'ABC', 'type':'bugdir'} + True + >>> _parse_user('') == \\ + ... {'bugdir':None, 'type':'bugdir'} + True + >>> _parse_user('/') == \\ + ... {'bugdir':None, 'bug':None, 'type':'bug'} + True + >>> _parse_user('/DEF/') == \\ + ... {'bugdir':None, 'bug':'DEF', 'comment':None, 'type':'comment'} + True + >>> _parse_user('a/b/c/d') + Traceback (most recent call last): + ... + InvalidIDStructure: 4 > 3 levels in "a/b/c/d" + """ + ret = {} + args = _split(id, check_length=True) + for i,(type,arg) in enumerate(zip(HIERARCHY, args)): + if arg != None and len(arg) == 0: + raise InvalidIDStructure( + id, 'Invalid %s part %d "%s" of id "%s"' % (type, i, arg, id)) + ret['type'] = type + ret[type] = arg + return ret + +def parse_user(bugdir, id): + """Parse a user ID (see :class:`ID`), returning a dict of parsed + information. + + The returned dict will contain a value for "type" (from + :data:`HIERARCHY`) and values for the levels that are defined. + + Notes + ----- + This function tries to expand IDs before parsing, so it can handle + both short and long IDs successfully. + """ + long_id = short_to_long_user([bugdir], id) + return _parse_user(long_id) + +if libbe.TESTING == True: + class UUIDtestCase(unittest.TestCase): + def testUUID_gen(self): + id = uuid_gen() + self.failUnless(len(id) == 36, 'invalid UUID "%s"' % id) + + class DummyObject (object): + def __init__(self, uuid, parent=None, siblings=[]): + self.uuid = uuid + self._siblings = siblings + if parent == None: + type_i = 0 + else: + assert parent.type in HIERARCHY, parent + setattr(self, parent.type, parent) + type_i = HIERARCHY.index(parent.type) + 1 + self.type = HIERARCHY[type_i] + self.id = ID(self, self.type) + def sibling_uuids(self): + return self._siblings + + class IDtestCase(unittest.TestCase): + def setUp(self): + self.bugdir = DummyObject('1234abcd') + self.bug = DummyObject('abcdef', self.bugdir, ['a1234', 'ab9876']) + self.comment = DummyObject('12345678', self.bug, ['1234abcd', '1234cdef']) + self.bd_id = self.bugdir.id + self.b_id = self.bug.id + self.c_id = self.comment.id + def test_storage(self): + self.failUnless(self.bd_id.storage() == self.bugdir.uuid, + self.bd_id.storage()) + self.failUnless(self.b_id.storage() == self.bug.uuid, + self.b_id.storage()) + self.failUnless(self.c_id.storage() == self.comment.uuid, + self.c_id.storage()) + self.failUnless(self.bd_id.storage('x', 'y', 'z') == \ + '1234abcd/x/y/z', + self.bd_id.storage('x', 'y', 'z')) + def test_long_user(self): + self.failUnless(self.bd_id.long_user() == self.bugdir.uuid, + self.bd_id.long_user()) + self.failUnless(self.b_id.long_user() == \ + '/'.join([self.bugdir.uuid, self.bug.uuid]), + self.b_id.long_user()) + self.failUnless(self.c_id.long_user() == + '/'.join([self.bugdir.uuid, self.bug.uuid, + self.comment.uuid]), + self.c_id.long_user) + def test_user(self): + self.failUnless(self.bd_id.user() == '123', + self.bd_id.user()) + self.failUnless(self.b_id.user() == '123/abc', + self.b_id.user()) + self.failUnless(self.c_id.user() == '123/abc/12345', + self.c_id.user()) + + class ShortLongParseTestCase(unittest.TestCase): + def setUp(self): + self.bugdir = DummyObject('1234abcd') + self.bug = DummyObject('abcdef', self.bugdir, ['a1234', 'ab9876']) + self.comment = DummyObject('12345678', self.bug, ['1234abcd', '1234cdef']) + self.bd_id = self.bugdir.id + self.b_id = self.bug.id + self.c_id = self.comment.id + self.bugdir.bug_from_uuid = lambda uuid: self.bug + self.bugdir.uuids = lambda : self.bug.sibling_uuids() + [self.bug.uuid] + self.bug.comment_from_uuid = lambda uuid: self.comment + self.bug.uuids = lambda : self.comment.sibling_uuids() + [self.comment.uuid] + self.short = 'bla bla #123/abc# bla bla #123/abc/12345# bla bla' + self.long = 'bla bla #1234abcd/abcdef# bla bla #1234abcd/abcdef/12345678# bla bla' + self.short_id_parse_pairs = [ + ('', {'bugdir':'1234abcd', 'type':'bugdir'}), + ('123/abc', {'bugdir':'1234abcd', 'bug':'abcdef', + 'type':'bug'}), + ('123/abc/12345', {'bugdir':'1234abcd', 'bug':'abcdef', + 'comment':'12345678', 'type':'comment'}), + ] + self.short_id_exception_pairs = [ + ('z', NoIDMatches('z', ['1234abcd'])), + ('///', InvalidIDStructure( + '///', msg='4 > 3 levels in "///"')), + ('/', MultipleIDMatches( + None, '123', ['a1234', 'ab9876', 'abcdef'])), + ('123/', MultipleIDMatches( + None, '123', ['a1234', 'ab9876', 'abcdef'])), + ('123/abc/', MultipleIDMatches( + None, '123/abc', ['1234abcd','1234cdef','12345678'])), + ] + def test_short_to_long_text(self): + self.failUnless(short_to_long_text([self.bugdir], self.short) == self.long, + '\n' + self.short + '\n' + short_to_long_text([self.bugdir], self.short) + '\n' + self.long) + def test_long_to_short_text(self): + self.failUnless(long_to_short_text([self.bugdir], self.long) == self.short, + '\n' + long_to_short_text([self.bugdir], self.long) + '\n' + self.short) + def test_parse_user(self): + for short_id,parsed in self.short_id_parse_pairs: + ret = parse_user(self.bugdir, short_id) + self.failUnless(ret == parsed, + 'got %s\nexpected %s' % (ret, parsed)) + def test_parse_user_exceptions(self): + for short_id,exception in self.short_id_exception_pairs: + try: + ret = parse_user(self.bugdir, short_id) + self.fail('Expected parse_user(bugdir, "%s") to raise %s,' + '\n but it returned %s' + % (short_id, exception.__class__.__name__, ret)) + except exception.__class__, e: + for attr in dir(e): + if attr.startswith('_') or attr == 'args': + continue + value = getattr(e, attr) + expected = getattr(exception, attr) + self.failUnless( + value == expected, + 'Expected parse_user(bugdir, "%s") %s.%s' + '\n to be %s, but it is %s\n\n%s' + % (short_id, exception.__class__.__name__, + attr, expected, value, e)) + + unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) diff --git a/libbe/util/plugin.py b/libbe/util/plugin.py new file mode 100644 index 0000000..e598c34 --- /dev/null +++ b/libbe/util/plugin.py @@ -0,0 +1,67 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# Marien Zwart <marienz@gentoo.org> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +Allow simple listing and loading of the various becommands and libbe +submodules (i.e. "plugins"). +""" + +import os +import os.path +import sys + + +_PLUGIN_PATH = os.path.realpath( + os.path.dirname( + os.path.dirname( + os.path.dirname(__file__)))) +if _PLUGIN_PATH not in sys.path: + sys.path.append(_PLUGIN_PATH) + +def import_by_name(modname): + """ + >>> mod = import_by_name('libbe.bugdir') + >>> 'BugDir' in dir(mod) + True + >>> import_by_name('libbe.highly_unlikely') + Traceback (most recent call last): + ... + ImportError: No module named highly_unlikely + """ + module = __import__(modname) + components = modname.split('.') + for comp in components[1:]: + module = getattr(module, comp) + return module + +def modnames(prefix): + """ + >>> 'list' in [n for n in modnames('libbe.command')] + True + >>> 'plugin' in [n for n in modnames('libbe.util')] + True + """ + components = prefix.split('.') + modfiles = os.listdir(os.path.join(_PLUGIN_PATH, *components)) + 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] diff --git a/libbe/util/subproc.py b/libbe/util/subproc.py new file mode 100644 index 0000000..b02b8e8 --- /dev/null +++ b/libbe/util/subproc.py @@ -0,0 +1,223 @@ +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +Functions for running external commands in subprocesses. +""" + +from subprocess import Popen, PIPE +import sys + +import libbe +from encoding import get_encoding +if libbe.TESTING == True: + import doctest + +_MSWINDOWS = sys.platform == 'win32' +_POSIX = not _MSWINDOWS + +if _POSIX == True: + import os + import select + +class CommandError(Exception): + def __init__(self, command, status, stdout=None, stderr=None): + strerror = ['Command failed (%d):\n %s\n' % (status, stderr), + 'while executing\n %s' % str(command)] + Exception.__init__(self, '\n'.join(strerror)) + self.command = command + self.status = status + self.stdout = stdout + self.stderr = stderr + +def invoke(args, stdin=None, stdout=PIPE, stderr=PIPE, expect=(0,), + cwd=None, unicode_output=True, verbose=False, encoding=None): + """ + expect should be a tuple of allowed exit codes. cwd should be + the directory from which the command will be executed. When + unicode_output == True, convert stdout and stdin strings to + unicode before returing them. + """ + if cwd == None: + cwd = '.' + if verbose == True: + print >> sys.stderr, '%s$ %s' % (cwd, ' '.join(args)) + try : + if _POSIX: + q = Popen(args, stdin=PIPE, stdout=stdout, stderr=stderr, cwd=cwd) + else: + assert _MSWINDOWS==True, 'invalid platform' + # win32 don't have os.execvp() so have to run command in a shell + q = Popen(args, stdin=PIPE, stdout=stdout, stderr=stderr, + shell=True, cwd=cwd) + except OSError, e: + raise CommandError(args, status=e.args[0], stderr=e) + stdout,stderr = q.communicate(input=stdin) + status = q.wait() + if unicode_output == True: + if encoding == None: + encoding = get_encoding() + if stdout != None: + stdout = unicode(stdout, encoding) + if stderr != None: + stderr = unicode(stderr, encoding) + if verbose == True: + print >> sys.stderr, '%d\n%s%s' % (status, stdout, stderr) + if status not in expect: + raise CommandError(args, status, stdout, stderr) + return status, stdout, stderr + +class Pipe (object): + """ + Simple interface for executing POSIX-style pipes based on the + subprocess module. The only complication is the adaptation of + subprocess.Popen._comminucate to listen to the stderrs of all + processes involved in the pipe, as well as the terminal process' + stdout. There are two implementations of Pipe._communicate, one + for MS Windows, and one for POSIX systems. The MS Windows + implementation is currently untested. + + >>> p = Pipe([['find', '/etc/'], ['grep', '^/etc/ssh$']]) + >>> p.stdout + '/etc/ssh\\n' + >>> p.status + 1 + >>> p.statuses + [1, 0] + >>> p.stderrs # doctest: +ELLIPSIS + [...find: ...: Permission denied..., ''] + """ + def __init__(self, cmds, stdin=None): + # spawn processes + self._procs = [] + for cmd in cmds: + if len(self._procs) != 0: + stdin = self._procs[-1].stdout + self._procs.append(Popen(cmd, stdin=stdin, stdout=PIPE, stderr=PIPE)) + + self.stdout,self.stderrs = self._communicate(input=None) + + # collect process statuses + self.statuses = [] + self.status = 0 + for proc in self._procs: + self.statuses.append(proc.wait()) + if self.statuses[-1] != 0: + self.status = self.statuses[-1] + + # Code excerpted from subprocess.Popen._communicate() + if _MSWINDOWS == True: + def _communicate(self, input=None): + assert input == None, 'stdin != None not yet supported' + # listen to each process' stderr + threads = [] + std_X_arrays = [] + for proc in self._procs: + stderr_array = [] + thread = Thread(target=proc._readerthread, + args=(proc.stderr, stderr_array)) + thread.setDaemon(True) + thread.start() + threads.append(thread) + std_X_arrays.append(stderr_array) + + # also listen to the last processes stdout + stdout_array = [] + thread = Thread(target=proc._readerthread, + args=(proc.stdout, stdout_array)) + thread.setDaemon(True) + thread.start() + threads.append(thread) + std_X_arrays.append(stdout_array) + + # join threads as they die + for thread in threads: + thread.join() + + # read output from reader threads + std_X_strings = [] + for std_X_array in std_X_arrays: + std_X_strings.append(std_X_array[0]) + + stdout = std_X_strings.pop(-1) + stderrs = std_X_strings + return (stdout, stderrs) + else: + assert _POSIX==True, 'invalid platform' + def _communicate(self, input=None): + read_set = [] + write_set = [] + read_arrays = [] + stdout = None # Return + stderr = None # Return + + if self._procs[0].stdin: + # Flush stdio buffer. This might block, if the user has + # been writing to .stdin in an uncontrolled fashion. + self._procs[0].stdin.flush() + if input: + write_set.append(self._procs[0].stdin) + else: + self._procs[0].stdin.close() + for proc in self._procs: + read_set.append(proc.stderr) + read_arrays.append([]) + read_set.append(self._procs[-1].stdout) + read_arrays.append([]) + + input_offset = 0 + while read_set or write_set: + try: + rlist, wlist, xlist = select.select(read_set, write_set, []) + except select.error, e: + if e.args[0] == errno.EINTR: + continue + raise + if self._procs[0].stdin in wlist: + # When select has indicated that the file is writable, + # we can write up to PIPE_BUF bytes without risk + # blocking. POSIX defines PIPE_BUF >= 512 + chunk = input[input_offset : input_offset + 512] + bytes_written = os.write(self.stdin.fileno(), chunk) + input_offset += bytes_written + if input_offset >= len(input): + self._procs[0].stdin.close() + write_set.remove(self._procs[0].stdin) + if self._procs[-1].stdout in rlist: + data = os.read(self._procs[-1].stdout.fileno(), 1024) + if data == '': + self._procs[-1].stdout.close() + read_set.remove(self._procs[-1].stdout) + read_arrays[-1].append(data) + for i,proc in enumerate(self._procs): + if proc.stderr in rlist: + data = os.read(proc.stderr.fileno(), 1024) + if data == '': + proc.stderr.close() + read_set.remove(proc.stderr) + read_arrays[i].append(data) + + # All data exchanged. Translate lists into strings. + read_strings = [] + for read_array in read_arrays: + read_strings.append(''.join(read_array)) + + stdout = read_strings.pop(-1) + stderrs = read_strings + return (stdout, stderrs) + +if libbe.TESTING == True: + suite = doctest.DocTestSuite() diff --git a/libbe/util/tree.py b/libbe/util/tree.py new file mode 100644 index 0000000..812b0bd --- /dev/null +++ b/libbe/util/tree.py @@ -0,0 +1,258 @@ +# Bugs Everywhere, a distributed bugtracker +# Copyright (C) 2008-2010 Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Define :class:`Tree`, a traversable tree structure. +""" + +import libbe +if libbe.TESTING == True: + import doctest + +class Tree(list): + """A traversable tree structure. + + Examples + -------- + + Construct:: + + +-b---d-g + a-+ +-e + +-c-+-f-h-i + + with + + >>> i = Tree(); i.n = "i" + >>> h = Tree([i]); h.n = "h" + >>> f = Tree([h]); f.n = "f" + >>> e = Tree(); e.n = "e" + >>> c = Tree([f,e]); c.n = "c" + >>> g = Tree(); g.n = "g" + >>> d = Tree([g]); d.n = "d" + >>> b = Tree([d]); b.n = "b" + >>> a = Tree(); a.n = "a" + >>> a.append(c) + >>> a.append(b) + + Get the longest branch length with + + >>> a.branch_len() + 5 + + Sort the tree recursively. Here we sort longest branch length + first. + + >>> a.sort(key=lambda node : -node.branch_len()) + >>> "".join([node.n for node in a.traverse()]) + 'acfhiebdg' + + And here we sort shortest branch length first. + + >>> a.sort(key=lambda node : node.branch_len()) + >>> "".join([node.n for node in a.traverse()]) + 'abdgcefhi' + + We can also do breadth-first traverses. + + >>> "".join([node.n for node in a.traverse(depth_first=False)]) + 'abcdefghi' + + Serialize the tree with depth marking branches. + + >>> for depth,node in a.thread(): + ... print "%*s" % (2*depth+1, node.n) + a + b + d + g + c + e + f + h + i + + Flattening the thread disables depth increases except at + branch splits. + + >>> for depth,node in a.thread(flatten=True): + ... print "%*s" % (2*depth+1, node.n) + a + b + d + g + c + e + f + h + i + + We can also check if a node is contained in a tree. + + >>> a.has_descendant(g) + True + >>> c.has_descendant(g) + False + >>> a.has_descendant(a) + False + >>> a.has_descendant(a, match_self=True) + True + """ + def __cmp__(self, other): + return cmp(id(self), id(other)) + + def __eq__(self, other): + return self.__cmp__(other) == 0 + + def __ne__(self, other): + return self.__cmp__(other) != 0 + + def branch_len(self): + """Return the largest number of nodes from root to leaf (inclusive). + + For the tree:: + + +-b---d-g + a-+ +-e + +-c-+-f-h-i + + this method returns 5. + + Notes + ----- + Exhaustive search every time == *slow*. + + Use only on small trees, or reimplement by overriding + child-addition methods to allow accurate caching. + """ + if len(self) == 0: + return 1 + else: + return 1 + max([child.branch_len() for child in self]) + + def sort(self, *args, **kwargs): + """Sort the tree recursively. + + This method extends :meth:`list.sort` to Trees. + + Notes + ----- + This method can be slow, e.g. on a :meth:`branch_len` sort, + since a node at depth `N` from the root has it's + :meth:`branch_len` method called `N` times. + """ + list.sort(self, *args, **kwargs) + for child in self: + child.sort(*args, **kwargs) + + def traverse(self, depth_first=True): + """Generate all the nodes in a tree, starting with the root node. + + Parameters + ---------- + depth_first : bool + Depth first by default, but you can set `depth_first` to + `False` for breadth first ordering. Siblings are returned + in the order they are stored, so you might want to + :meth:`sort` your tree first. + """ + if depth_first == True: + yield self + for child in self: + for descendant in child.traverse(): + yield descendant + else: # breadth first, Wikipedia algorithm + # http://en.wikipedia.org/wiki/Breadth-first_search + queue = [self] + while len(queue) > 0: + node = queue.pop(0) + yield node + queue.extend(node) + + def thread(self, flatten=False): + """Generate a (depth, node) tuple for every node in the tree. + + When `flatten` is `False`, the depth of any node is one + greater than the depth of its parent. That way the + inheritance is explicit, but you can end up with highly + indented threads. + + When `flatten` is `True`, the depth of any node is only + greater than the depth of its parent when there is a branch, + and the node is not the last child. This can lead to ancestry + ambiguity, but keeps the total indentation down. For example:: + + +-b +-b-c + a-+-c and a-+ + +-d-e-f +-d-e-f + + would both produce (after sorting by :meth:`branch_len`):: + + (0, a) + (1, b) + (1, c) + (0, d) + (0, e) + (0, f) + + """ + stack = [] # ancestry of the current node + if flatten == True: + depthDict = {} + + for node in self.traverse(depth_first=True): + while len(stack) > 0 \ + and id(node) not in [id(c) for c in stack[-1]]: + stack.pop(-1) + if flatten == False: + depth = len(stack) + else: + if len(stack) == 0: + depth = 0 + else: + parent = stack[-1] + depth = depthDict[id(parent)] + if len(parent) > 1 and node != parent[-1]: + depth += 1 + depthDict[id(node)] = depth + yield (depth,node) + stack.append(node) + + def has_descendant(self, descendant, depth_first=True, match_self=False): + """Check if a node is contained in a tree. + + Parameters + ---------- + descendant : Tree + The potential descendant. + depth_first : bool + The search order. Set this if you feel depth/breadth would + be a faster search. + match_self : bool + Set to `True` for:: + + x.has_descendant(x, match_self=True) -> True + """ + if descendant == self: + return match_self + for d in self.traverse(depth_first): + if descendant == d: + return True + return False + +if libbe.TESTING == True: + suite = doctest.DocTestSuite() diff --git a/libbe/util/utility.py b/libbe/util/utility.py new file mode 100644 index 0000000..c12e9a2 --- /dev/null +++ b/libbe/util/utility.py @@ -0,0 +1,248 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +Assorted utility functions that don't fit in anywhere else. +""" + +import calendar +import codecs +import os +import shutil +import tempfile +import time +import types + +import libbe +if libbe.TESTING == True: + import doctest + +class InvalidXML(ValueError): + """Invalid XML while parsing for a `*.from_xml()` method. + + Parameters + ---------- + type : str + String identifying `*`, e.g. "bug", "comment", ... + element : :class:`ElementTree.Element` + ElementTree.Element instance which caused the error. + error : str + Error description. + """ + def __init__(self, type, element, error): + msg = 'Invalid %s xml: %s\n %s\n' \ + % (type, error, ElementTree.tostring(element)) + ValueError.__init__(self, msg) + self.type = type + self.element = element + self.error = error + +def search_parent_directories(path, filename): + """ + Find the file (or directory) named filename in path or in any + of path's parents. For example:: + + 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 (object): + """A temporary directory for testing use. + + Make sure you run :meth:`cleanup` after you're done using the + directory. + """ + def __init__(self): + self.path = tempfile.mkdtemp(prefix="BEtest") + self.removed = False + def cleanup(self): + if self.removed == False: + shutil.rmtree(self.path) + self.removed = True + def __call__(self): + return self.path + +RFC_2822_TIME_FMT = "%a, %d %b %Y %H:%M:%S +0000" +"""RFC 2822 [#]_ format string for :func:`time.strftime` and +:func:`time.strptime`. + +.. [#] See `RFC 2822`_, sections 3.3 and A.1.1. +.. _RFC 2822: http://www.faqs.org/rfcs/rfc2822.html +""" + +def time_to_str(time_val): + """Convert a time number into an RFC 2822-formatted string. + + Parameters + ---------- + time_val : float + Float seconds since the Epoc, see :func:`time.time`. + Note that while `time_val` may contain sub-second data, + the output string will not. + + Examples + -------- + + >>> time_to_str(0) + 'Thu, 01 Jan 1970 00:00:00 +0000' + + See Also + -------- + str_to_time : inverse + handy_time : localtime string + """ + return time.strftime(RFC_2822_TIME_FMT, time.gmtime(time_val)) + +def str_to_time(str_time): + """Convert an RFC 2822-fomatted string into a time value. + + Parameters + ---------- + str_time : str + An RFC 2822-formatted string. + + Examples + -------- + + >>> str_to_time("Thu, 01 Jan 1970 00:00:00 +0000") + 0 + >>> q = time.time() + >>> str_to_time(time_to_str(q)) == int(q) + True + >>> str_to_time("Thu, 01 Jan 1970 00:00:00 -1000") + 36000 + + See Also + -------- + time_to_str : inverse + """ + timezone_str = str_time[-5:] + if timezone_str != "+0000": + str_time = str_time.replace(timezone_str, "+0000") + time_val = calendar.timegm(time.strptime(str_time, RFC_2822_TIME_FMT)) + timesign = -int(timezone_str[0]+"1") # "+" -> time_val ahead of GMT + timezone_tuple = time.strptime(timezone_str[1:], "%H%M") + timezone = timezone_tuple.tm_hour*3600 + timezone_tuple.tm_min*60 + return time_val + timesign*timezone + +def handy_time(time_val): + """Convert a time number into a useful localtime. + + Where :func:`time_to_str` returns GMT +0000, `handy_time` returns + a string in local time. This may be more accessible for the user. + + Parameters + ---------- + time_val : float + Float seconds since the Epoc, see :func:`time.time`. + """ + return time.strftime("%a, %d %b %Y %H:%M", time.localtime(time_val)) + +def time_to_gmtime(str_time): + """Convert an RFC 2822-fomatted string to a GMT string. + + Parameters + ---------- + str_time : str + An RFC 2822-formatted string. + + Examples + -------- + + >>> time_to_gmtime("Thu, 01 Jan 1970 00:00:00 -1000") + 'Thu, 01 Jan 1970 10:00:00 +0000' + """ + time_val = str_to_time(str_time) + return time_to_str(time_val) + +def iterable_full_of_strings(value, alternative=None): + """Require an iterable full of strings. + + This is useful, for example, in validating `*.extra_strings`. + See :attr:`libbe.bugdir.BugDir.extra_strings` + + Parameters + ---------- + value : list or None + The potential list of strings. + alternative + Allow a default (e.g. `None`), such that:: + + iterable_full_of_strings(value=x, alternative=x) -> True + + Examples + -------- + + >>> iterable_full_of_strings([]) + True + >>> iterable_full_of_strings(["abc", "def", u"hij"]) + True + >>> iterable_full_of_strings(["abc", None, u"hij"]) + False + >>> iterable_full_of_strings(None, alternative=None) + True + """ + if value == alternative: + return True + elif not hasattr(value, '__iter__'): + return False + for x in value: + if type(x) not in types.StringTypes: + return False + return True + +def underlined(string, char='='): + """Produces a version of a string that is underlined. + + Parameters + ---------- + string : str + The string to underline + char : str + The character to use for the underlining. + + Examples + -------- + + >>> underlined("Underlined String") + 'Underlined String\\n=================' + """ + assert len(char) == 1, char + return '%s\n%s' % (string, char*len(string)) + +if libbe.TESTING == True: + suite = doctest.DocTestSuite() diff --git a/libbe/version.py b/libbe/version.py new file mode 100644 index 0000000..2792de4 --- /dev/null +++ b/libbe/version.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +Store version info for this BE installation. By default, use the +bzr-generated information in _version.py, but allow manual overriding +by setting _VERSION. This allows support of both the "I don't want to +be bothered setting version strings" and the "I want complete control +over the version strings" workflows. +""" + +import copy + +import libbe._version as _version +import libbe.storage + +# Manually set a version string (optional, defaults to bzr revision id) +#_VERSION = "1.2.3" + +def version(verbose=False): + """ + Returns the version string for this BE installation. If + verbose==True, the string will include extra lines with more + detail (e.g. bzr branch nickname, etc.). + """ + if "_VERSION" in globals(): + string = _VERSION + else: + string = _version.version_info["revision_id"] + if verbose == True: + info = copy.copy(_version.version_info) + info['storage'] = libbe.storage.STORAGE_VERSION + string += ("\n" + "revision: %(revno)d\n" + "nick: %(branch_nick)s\n" + "revision id: %(revision_id)s\n" + "storage version: %(storage)s" + % info) + return string + +if __name__ == "__main__": + print version(verbose=True) diff --git a/misc/completion/be.bash b/misc/completion/be.bash new file mode 100644 index 0000000..834bf25 --- /dev/null +++ b/misc/completion/be.bash @@ -0,0 +1,39 @@ +#!/bin/bash +# Bash completion script for be (Bugs Everywhere) +# +# System wide installation: +# Copy this file to /etc/bash_completion/be +# Per-user installation: +# Copy this file to ~/.be-completion.sh and source it in your .bashrc: +# source ~/.be-completion.sh +# +# For a good intro to Bash completion, see Steve Kemp's article +# "An introduction to bash completion: part 2" +# http://www.debian-administration.org/articles/317 + +# Requires: +# be [X Y Z] --complete +# to print a list of available completions at that point +_be() +{ + local cur prev opts + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [ $COMP_CWORD -eq 1 ]; then + # no command yet, show all commands + COMPREPLY=( $( compgen -W "$(be --complete)" -- $cur ) ) + else + # remove the first word (should be "be") for security reasons + unset COMP_WORDS[0] + # remove the current word and all later words, because they + # are not needed for completion. + for i in `seq $COMP_CWORD ${#COMP_WORDS[@]}`; do + unset COMP_WORDS[$i]; + done + COMPREPLY=( $( compgen -W "$(be "${COMP_WORDS[@]}" --complete $cur)" -- $cur ) ) + fi +} + +complete -F _be be diff --git a/misc/logo.svg b/misc/logo.svg new file mode 100644 index 0000000..43964b1 --- /dev/null +++ b/misc/logo.svg @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:xml="http://www.w3.org/XML/1998/namespace" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + sodipodi:docname="logo.svg" + sodipodi:docbase="/home/abentley/arch/be" + inkscape:version="0.41" + sodipodi:version="0.32" + id="svg2" + height="297mm" + width="210mm"> + <defs + id="defs3" /> + <sodipodi:namedview + inkscape:window-y="63" + inkscape:window-x="22" + inkscape:window-height="759" + inkscape:window-width="910" + inkscape:current-layer="layer1" + inkscape:document-units="px" + inkscape:cy="836.68762" + inkscape:cx="408.57141" + inkscape:zoom="0.67765957" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" /> + <metadata + id="metadata4"> + <rdf:RDF + id="RDF5"> + <cc:Work + id="Work6" + rdf:about=""> + <dc:format + id="format7">image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" + id="type9" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:groupmode="layer" + inkscape:label="Layer 1"> + <g + id="g2065"> + <text + sodipodi:linespacing="100%" + id="text1291" + y="143.78516" + x="36.981991" + style="font-size:78.161568;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#000000;fill-opacity:1.0000000;stroke:none;stroke-width:1.0000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000;font-family:Attic;text-anchor:start;writing-mode:lr;line-height:100%" + xml:space="preserve"><tspan + y="143.78516" + x="36.981991" + id="tspan1293" + sodipodi:role="line">Bugs Everwhere</tspan></text> + <path + id="path1299" + d="M 435.39129,169.91317 C 435.39129,178.31858 440.70441,153.65014 445.24931,146.30250 C 450.43734,137.91513 453.18058,163.90397 453.46434,166.96184 C 453.55532,167.94222 453.46434,168.92939 453.46434,169.91317" + style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#000000;stroke-width:4.2500000;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1.0000000;stroke-dasharray:none;stroke-miterlimit:4.0000000" /> + <path + id="path1303" + d="M 440.06423,89.055564 C 440.06423,95.774126 450.18051,79.127074 454.66879,73.752225 C 458.94498,68.631300 461.04359,61.697359 464.40532,56.262692 C 469.05687,48.742525 470.05816,49.640806 479.00985,46.424830" + style="stroke-dasharray:none;stroke-opacity:1.0000000;stroke-miterlimit:4.0000000;stroke-linejoin:round;stroke-linecap:round;stroke-width:4.2500000;stroke:#000000;fill-rule:evenodd;fill-opacity:0.75000000;fill:none" /> + <path + id="path1305" + d="M 457.10286,68.286745 C 463.68121,65.923450 451.21494,56.705449 448.58360,50.797213" + style="stroke-dasharray:none;stroke-opacity:1.0000000;stroke-miterlimit:4.0000000;stroke-linejoin:round;stroke-linecap:round;stroke-width:4.2500000;stroke:#000000;fill-rule:evenodd;fill-opacity:0.75000000;fill:none" /> + </g> + </g> +</svg> diff --git a/misc/xml/be-mail-to-xml b/misc/xml/be-mail-to-xml new file mode 100755 index 0000000..5a1a88f --- /dev/null +++ b/misc/xml/be-mail-to-xml @@ -0,0 +1,175 @@ +#!/usr/bin/env python +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" +Convert an mbox into xml suitable for input into be. + $ be-mbox-to-xml file.mbox | be import-xml -c <ID> - +mbox is a flat-file format, consisting of a series of messages. +Messages begin with a a From_ line, followed by RFC 822 email, +followed by a blank line. +""" + +import base64 +import email.utils +from libbe.util.encoding import get_output_encoding +from libbe.util.utility import time_to_str +import mailbox # the mailbox people really want an on-disk copy +import optparse +from time import asctime, gmtime, mktime +import types +from xml.sax.saxutils import escape + +BREAK = u'--' # signature separator +DEFAULT_ENCODING = get_output_encoding() + +KNOWN_IDS = [] + +def normalize_email_address(address): + """ + Standardize whitespace, etc. + """ + addr = email.utils.formataddr(email.utils.parseaddr(address)) + if len(addr) == 0: + return None + return addr + +def normalize_RFC_2822_date(date): + """ + Some email clients write non-RFC 2822-compliant date tags like: + Fri, 18 Sep 2009 08:49:02 -0400 (EDT) + with the non-standard (EDT) timezone name. This funtion attempts + to deal with such inconsistencies. + """ + time_tuple = email.utils.parsedate(date) + assert time_tuple != None, \ + 'unparsable date: "%s"' % date + return time_to_str(mktime(time_tuple)) + +def strip_footer(body): + body_lines = body.splitlines() + for i,line in enumerate(body_lines): + if line.startswith(BREAK): + break + i += 1 # increment past the current valid line. + return u'\n'.join(body_lines[:i]).strip() + +def comment_message_to_xml(message, fields=None): + if fields == None: + fields = {} + new_fields = {} + new_fields[u'alt-id'] = message[u'message-id'] + new_fields[u'in-reply-to'] = message[u'in-reply-to'] + new_fields[u'author'] = normalize_email_address(message[u'from']) + new_fields[u'date'] = message[u'date'] + if new_fields[u'date'] != None: + new_fields[u'date'] = normalize_RFC_2822_date(new_fields[u'date']) + new_fields[u'content-type'] = message.get_content_type() + for k,v in new_fields.items(): + if v != None and type(v) != types.UnicodeType: + fields[k] = unicode(v, encoding=DEFAULT_ENCODING) + elif v == None and k in fields: + new_fields[k] = fields[k] + for k,v in fields.items(): + if k not in new_fields: + new_fields.k = fields[k] + fields = new_fields + + if fields[u'in-reply-to'] == None: + if message[u'references'] != None: + refs = message[u'references'].split() + for ref in refs: # search for a known reference id. + if ref in KNOWN_IDS: + fields[u'in-reply-to'] = ref + break + if fields[u'in-reply-to'] == None and len(refs) > 0: + fields[u'in-reply-to'] = refs[0] # default to the first + else: # check for mutliple in-reply-to references. + refs = fields[u'in-reply-to'].split() + found_ref = False + for ref in refs: # search for a known reference id. + if ref in KNOWN_IDS: + fields[u'in-reply-to'] = ref + found_ref = True + break + if found_ref == False and len(refs) > 0: + fields[u'in-reply-to'] = refs[0] # default to the first + + if fields[u'alt-id'] != None: + KNOWN_IDS.append(fields[u'alt-id']) + + if message.is_multipart(): + ret = [] + alt_id = fields[u'alt-id'] + from_str = fields[u'author'] + date = fields[u'date'] + for m in message.walk(): + if m == message: + continue + fields[u'author'] = from_str + fields[u'date'] = date + if len(ret) > 0: # we've added one part already + fields.pop(u'alt-id') # don't pass alt-id to other parts + fields[u'in-reply-to'] = alt_id # others respond to first + ret.append(comment_message_to_xml(m, fields)) + return u'\n'.join(ret) + + charset = message.get_content_charset(DEFAULT_ENCODING).lower() + #assert charset == DEFAULT_ENCODING.lower(), \ + # u"Unknown charset: %s" % charset + + if message[u'content-transfer-encoding'] == None: + encoding = DEFAULT_ENCODING + else: + encoding = message[u'content-transfer-encoding'].lower() + body = message.get_payload(decode=True) # attempt to decode + assert body != None, "Unable to decode?" + if fields[u'content-type'].startswith(u"text/"): + body = strip_footer(unicode(body, encoding=charset)) + else: + body = base64.encode(body) + fields[u'body'] = body + lines = [u"<comment>"] + for tag,body in fields.items(): + if body != None: + ebody = escape(body) + lines.append(u" <%s>%s</%s>" % (tag, ebody, tag)) + lines.append(u"</comment>") + return u'\n'.join(lines) + +def main(argv): + parser = optparse.OptionParser(usage='%prog [options] mailbox') + formats = ['mbox', 'Maildir', 'MH', 'Babyl', 'MMDF'] + parser.add_option('-f', '--format', type='choice', dest='format', + help="Select the mailbox format from %s. See the mailbox module's documention for descriptions of these formats." \ + % ', '.join(formats), + default='mbox', choices=formats) + options,args = parser.parse_args(argv) + mailbox_file = args[1] + reader = getattr(mailbox, options.format) + mb = reader(mailbox_file, factory=None) + print u'<?xml version="1.0" encoding="%s" ?>' % DEFAULT_ENCODING + print u"<be-xml>" + for message in mb: + print comment_message_to_xml(message) + print u"</be-xml>" + + +if __name__ == "__main__": + import sys + import codecs + + sys.stdout = codecs.getwriter(DEFAULT_ENCODING)(sys.stdout) + main(sys.argv) diff --git a/misc/xml/be-xml-to-mbox b/misc/xml/be-xml-to-mbox new file mode 100755 index 0000000..c8b7479 --- /dev/null +++ b/misc/xml/be-xml-to-mbox @@ -0,0 +1,220 @@ +#!/usr/bin/env python +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" +Convert xml output of `be list --xml` into mbox format for browsing +with a mail reader. For example + $ be list --xml --status=all | be-xml-to-mbox | catmutt + +mbox is a flat-file format, consisting of a series of messages. +Messages begin with a a From_ line, followed by RFC 822 email, +followed by a blank line. +""" + +#from mailbox import mbox, Message # the mailbox people really want an on-disk copy +import email.utils +from libbe.util.encoding import get_output_encoding +from libbe.util.utility import str_to_time as rfc2822_to_gmtime_integer +from time import asctime, gmtime +import types +try: # import core module, Python >= 2.5 + from xml.etree import ElementTree +except ImportError: # look for non-core module + from elementtree import ElementTree +from xml.sax.saxutils import unescape + + +DEFAULT_DOMAIN = "invalid.com" +DEFAULT_EMAIL = "dummy@" + DEFAULT_DOMAIN +DEFAULT_ENCODING = get_output_encoding() + +def rfc2822_to_asctime(rfc2822_string): + """Convert an RFC 2822-fomatted string into a asctime string. + >>> rfc2822_to_asctime("Thu, 01 Jan 1970 00:00:00 +0000") + "Thu Jan 01 00:00:00 1970" + """ + if rfc2822_string == "": + return asctime(gmtime(0)) + return asctime(gmtime(rfc2822_to_gmtime_integer(rfc2822_string))) + +class LimitedAttrDict (dict): + """ + Dict with error checking, to avoid invalid bug/comment fields. + """ + _attrs = [] # override with list of valid attribute names + def __init__(self, **kwargs): + dict.__init__(self) + for key,value in kwargs.items(): + self[key] = value + def __setitem__(self, key, item): + self._validate_key(key) + dict.__setitem__(self, key, item) + def _validate_key(self, key): + if key in self._attrs: + return + elif type(key) not in types.StringTypes: + raise TypeError, "Invalid attribute type %s for '%s'" % (type(key), key) + else: + raise ValueError, "Invalid attribute name '%s'" % key + +class Bug (LimitedAttrDict): + _attrs = [u"uuid", + u"short-name", + u"severity", + u"status", + u"assigned", + u"reporter", + u"creator", + u"created", + u"summary", + u"comments", + u"extra-strings"] + def print_to_mbox(self): + if "creator" in self: + # otherwise, probably a `be show` uuid-only bug to avoid + # root comments. + name,addr = email.utils.parseaddr(self["creator"]) + print "From %s %s" % (addr, rfc2822_to_asctime(self["created"])) + print "Message-id: <%s@%s>" % (self["uuid"], DEFAULT_DOMAIN) + print "Date: %s" % self["created"] + print "From: %s" % self["creator"] + print "Content-Type: %s; charset=%s" \ + % ("text/plain", DEFAULT_ENCODING) + print "Content-Transfer-Encoding: 8bit" + print "Subject: %s: %s" % (self["short-name"], self["summary"]) + if "extra-strings" in self: + for estr in self["extra-strings"]: + print "X-Extra-String: %s" % estr + print "" + print self["summary"] + print "" + if "comments" in self: + for comment in self["comments"]: + comment.print_to_mbox(self) + def init_from_etree(self, element): + assert element.tag == "bug", element.tag + for field in element.getchildren(): + text = unescape(unicode(field.text).decode("unicode_escape").strip()) + if field.tag == "comment": + comm = Comment() + comm.init_from_etree(field) + if "comments" in self: + self["comments"].append(comm) + else: + self["comments"] = [comm] + elif field.tag == "extra-string": + if "extra-strings" in self: + self["extra-strings"].append(text) + else: + self["extra-strings"] = [text] + else: + self[field.tag] = text + +def wrap_id(id): + if "@" not in id: + return "<%s@%s>" % (id, DEFAULT_DOMAIN) + return id + +class Comment (LimitedAttrDict): + _attrs = [u"uuid", + u"alt-id", + u"short-name", + u"in-reply-to", + u"author", + u"date", + u"content-type", + u"body", + u"extra-strings"] + def print_to_mbox(self, bug=None): + if bug == None: + bug = Bug() + bug[u"uuid"] = u"no-uuid" + name,addr = email.utils.parseaddr(self["author"]) + print "From %s %s" % (addr, rfc2822_to_asctime(self["date"])) + if "uuid" in self: id = self["uuid"] + elif "alt-id" in self: id = self["alt-id"] + else: id = None + if id != None: + print "Message-id: %s" % wrap_id(id) + if "alt-id" in self: + print "Alt-id: %s" % wrap_id(self["alt-id"]) + print "Date: %s" % self["date"] + print "From: %s" % self["author"] + subject = "" + if "short-name" in self: + subject += self["short-name"]+u": " + if "summary" in bug: + subject += bug["summary"] + else: + subject += u"no-subject" + print "Subject: %s" % subject + if "in-reply-to" not in self.keys(): + self["in-reply-to"] = bug["uuid"] + print "In-Reply-To: %s" % wrap_id(self["in-reply-to"]) + if "extra-strings" in self: + for estr in self["extra-strings"]: + print "X-Extra-String: %s" % estr + if self["content-type"].startswith("text/"): + print "Content-Transfer-Encoding: 8bit" + print "Content-Type: %s; charset=%s" \ + % (self["content-type"], DEFAULT_ENCODING) + else: + print "Content-Transfer-Encoding: base64" + print "Content-Type: %s;" % (self["content-type"]) + print "" + print self["body"] + print "" + def init_from_etree(self, element): + assert element.tag == "comment", element.tag + for field in element.getchildren(): + text = unescape(unicode(field.text).decode("unicode_escape").strip()) + if field.tag == "extra-string": + if "extra-strings" in self: + self["extra-strings"].append(text) + else: + self["extra-strings"] = [text] + else: + if field.tag == "body": + text+="\n" + self[field.tag] = text + +def print_to_mbox(element): + if element.tag == "bug": + b = Bug() + b.init_from_etree(element) + b.print_to_mbox() + elif element.tag == "comment": + c = Comment() + c.init_from_etree(element) + c.print_to_mbox() + elif element.tag in ["be-xml"]: + for elt in element.getchildren(): + print_to_mbox(elt) + +if __name__ == "__main__": + import codecs + import sys + + sys.stdin = codecs.getreader(DEFAULT_ENCODING)(sys.stdin) + sys.stdout = codecs.getwriter(DEFAULT_ENCODING)(sys.stdout) + + if len(sys.argv) == 1: # no filename given, use stdin + xml_unicode = sys.stdin.read() + else: + xml_unicode = codecs.open(sys.argv[1], "r", DEFAULT_ENCODING).read() + xml_str = xml_unicode.encode("unicode_escape").replace(r"\n", "\n") + elist = ElementTree.XML(xml_str) + print_to_mbox(elist) diff --git a/misc/xml/catmutt b/misc/xml/catmutt new file mode 100755 index 0000000..601f14f --- /dev/null +++ b/misc/xml/catmutt @@ -0,0 +1,59 @@ +#!/bin/sh + +# catmutt - wrap mutt allowing mboxes read from stdin. +# +# Copyright (C) 1998-1999 Moritz Barsnick <barsnick (at) gmx (dot) net>, +# 2009 William Trevor King <wking (at) drexel (dot) edu> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# 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 +# +# developed from grepm-0.6 +# http://www.barsnick.net/sw/grepm.html + +PROGNAME=`basename "$0"` +export TMPDIR="${TMPDIR-/tmp}" # used by mktemp +umask 077 + +if [ $# -gt 0 ] && [ "$1" = "--help" ]; then + echo 1>&2 "Usage: ${PROGNAME} [--help] mutt-arguments" + echo 1>&2 "" + echo 1>&2 "Read a mailbox file from stdin and opens it with mutt." + echo 1>&2 "For example: cat somefile.mbox | ${PROGNAME}" + exit 0 +fi + +# Note: the -t/-p options to mktemp are deprecated for mktemp (GNU +# coreutils) 7.1 in favor of --tmpdir but the --tmpdir option does not +# exist yet for my 6.10-3ubuntu2 coreutils +TMPFILE=`mktemp -t catmutt.XXXXXX` || exit 1 + +trap "rm -f ${TMPFILE}; exit 1" 1 2 3 13 15 + +cat > "${TMPFILE}" || exit 1 + +# Now that we've read in the mailbox file, reopen stdin for mutt/user +# interaction. When in a pipe we're not technically in a tty, so use +# a little hack from "greno" at +# http://www.linuxforums.org/forum/linux-programming-scripting/98607-bash-stdin-problem.html +tty="/dev/`ps -p$$ --no-heading | awk '{print $2}'`" +exec < ${tty} + +if [ `wc -c "${TMPFILE}" | awk '{print $1}'` -gt 0 ]; then + echo 1>&2 "Calling mutt on temporary mailbox file (${TMPFILE})." + mutt -R -f "${TMPFILE}" "$@" +else + echo 1>&2 "Empty mailbox input." +fi + +rm -f "${TMPFILE}" && echo 1>&2 "Deleted temporary mailbox file (${TMPFILE})." diff --git a/release.py b/release.py new file mode 100755 index 0000000..d038cce --- /dev/null +++ b/release.py @@ -0,0 +1,161 @@ +#!/usr/bin/python +# +# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os +import os.path +import shutil +import string +import sys + +from libbe.subproc import Pipe, invoke +from update_copyright import update_authors, update_files + +def validate_tag(tag): + """ + >>> validate_tag('1.0.0') + >>> validate_tag('A.B.C-r7') + >>> validate_tag('A.B.C r7') + Traceback (most recent call last): + ... + Exception: Invalid character ' ' in tag 'A.B.C r7' + >>> validate_tag('"') + Traceback (most recent call last): + ... + Exception: Invalid character '"' in tag '"' + >>> validate_tag("'") + Traceback (most recent call last): + ... + Exception: Invalid character ''' in tag ''' + """ + for char in tag: + if char in string.digits: + continue + elif char in string.letters: + continue + elif char in ['.','-']: + continue + raise Exception("Invalid character '%s' in tag '%s'" % (char, tag)) + +def bzr_pending_changes(): + """Use `bzr diff`s exit status to detect change: + 1 - changed + 2 - unrepresentable changes + 3 - error + 0 - no change + """ + p = Pipe([['bzr', 'diff']]) + if p.status == 0: + return False + elif p.status in [1,2]: + return True + raise Exception("Error in bzr diff %d\n%s" % (p.status, p.stderrs[-1])) + +def set_release_version(tag): + print "set libbe.version._VERSION = '%s'" % tag + p = Pipe([['sed', '-i', "s/^# *_VERSION *=.*/_VERSION = '%s'/" % tag, + os.path.join('libbe', 'version.py')]]) + assert p.status == 0, p.statuses + +def bzr_commit(commit_message): + print 'commit current status:', commit_message + p = Pipe([['bzr', 'commit', '-m', commit_message]]) + assert p.status == 0, p.statuses + +def bzr_tag(tag): + print 'tag current revision', tag + p = Pipe([['bzr', 'tag', tag]]) + assert p.status == 0, p.statuses + +def bzr_export(target_dir): + print 'export current revision to', target_dir + p = Pipe([['bzr', 'export', target_dir]]) + assert p.status == 0, p.statuses + +def make_version(): + print 'generate libbe/_version.py' + p = Pipe([['make', os.path.join('libbe', '_version.py')]]) + assert p.status == 0, p.statuses + +def make_changelog(filename, tag): + print 'generate ChangeLog file', filename, 'up to tag', tag + p = invoke(['bzr', 'log', '--gnu-changelog', '-n1', '-r', + '..tag:%s' % tag], stdout=file(filename, 'w')) + status = p.wait() + assert status == 0, status + +def set_vcs_name(filename, vcs_name='None'): + """Exported directory is not a bzr repository, so set vcs_name to + something that will work. + vcs_name: new_vcs_name + """ + print 'set vcs_name in', filename, 'to', vcs_name + p = Pipe([['sed', '-i', "s/^vcs_name:.*/vcs_name: %s/" % vcs_name, + filename]]) + assert p.status == 0, p.statuses + +def create_tarball(tag): + release_name='be-%s' % tag + export_dir = release_name + bzr_export(export_dir) + make_version() + print 'copy libbe/_version.py to %s/libbe/_version.py' % export_dir + shutil.copy(os.path.join('libbe', '_version.py'), + os.path.join(export_dir, 'libbe', '_version.py')) + make_changelog(os.path.join(export_dir, 'ChangeLog'), tag) + set_vcs_name(os.path.join(export_dir, '.be', 'settings')) + tarball_file = '%s.tar.gz' % release_name + print 'create tarball', tarball_file + p = Pipe([['tar', '-czf', tarball_file, export_dir]]) + assert p.status == 0, p.statuses + print 'remove', export_dir + shutil.rmtree(export_dir) + +def test(): + import doctest + doctest.testmod() + +if __name__ == '__main__': + import optparse + usage = """%prog [options] TAG + +Create a bzr tag and a release tarball from the current revision. +For example + %prog 1.0.0 +""" + p = optparse.OptionParser(usage) + p.add_option('--test', dest='test', default=False, + action='store_true', help='Run internal tests and exit') + options,args = p.parse_args() + + if options.test == True: + test() + sys.exit(0) + + assert len(args) == 1, '%d (!= 1) arguments: %s' % (len(args), args) + tag = args[0] + validate_tag(tag) + + if bzr_pending_changes() == True: + print "Handle pending changes before releasing." + sys.exit(1) + set_release_version(tag) + update_authors() + update_files() + bzr_commit("Bumped to version %s" % tag) + bzr_tag(tag) + create_tarball(tag) diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..ab0a608 --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +from distutils.core import setup +from libbe import _version + +rev_id = _version.version_info["revision_id"] +rev_date = rev_id.split("-")[1] + +setup( + name='Bugs Everywhere', + version=rev_date, + description='Bugtracker supporting distributed revision control', + url='http://bugseverywhere.org/', + packages=['libbe', + 'libbe.command', + 'libbe.storage', + 'libbe.storage.util', + 'libbe.storage.vcs', + 'libbe.ui', + 'libbe.ui.util', + 'libbe.util'], + scripts=['be'], + data_files=[ + ('share/man/man1', ['doc/man/be.1']), + ] + ) @@ -0,0 +1,122 @@ +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. +# Marien Zwart <marienz@gentoo.org> +# W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import doctest +import os +import os.path +import sys +import unittest + +import libbe +libbe.TESTING = True +from libbe.util.tree import Tree +from libbe.util.plugin import import_by_name +from libbe.version import version + +def python_tree(root_path='libbe', root_modname='libbe'): + tree = Tree() + tree.path = root_path + tree.parent = None + stack = [tree] + while len(stack) > 0: + f = stack.pop(0) + if f.path.endswith('.py'): + f.name = os.path.basename(f.path)[:-len('.py')] + elif os.path.isdir(f.path) \ + and os.path.exists(os.path.join(f.path, '__init__.py')): + f.name = os.path.basename(f.path) + f.is_module = True + for child in os.listdir(f.path): + if child == '__init__.py': + continue + c = Tree() + c.path = os.path.join(f.path, child) + c.parent = f + stack.append(c) + else: + continue + if f.parent == None: + f.modname = root_modname + else: + f.modname = f.parent.modname + '.' + f.name + f.parent.append(f) + return tree + +def add_module_tests(suite, modname): + try: + mod = import_by_name(modname) + except ValueError, e: + print >> sys.stderr, 'Failed to import "%s"' % (modname) + raise e + if hasattr(mod, 'suite'): + s = mod.suite + else: + s = unittest.TestLoader().loadTestsFromModule(mod) + try: + sdoc = doctest.DocTestSuite(mod) + suite.addTest(sdoc) + except ValueError: + pass + suite.addTest(s) + +if __name__ == '__main__': + import optparse + parser = optparse.OptionParser(usage='%prog [options] [modules ...]', + description= +"""When called without optional module names, run the test suites for +*all* modules. This may raise lots of errors if you haven't installed +one of the versioning control systems. + +When called with module name arguments, only run the test suites from +those modules and their submodules. For example:: + + $ python test.py libbe.bugdir libbe.storage +""") + parser.add_option('-q', '--quiet', action='store_true', default=False, + help='Run unittests in quiet mode (verbosity 1).') + options,args = parser.parse_args() + print >> sys.stderr, 'Testing BE\n%s' % version(verbose=True) + + verbosity = 2 + if options.quiet == True: + verbosity = 1 + + suite = unittest.TestSuite() + tree = python_tree() + if len(args) == 0: + for node in tree.traverse(): + add_module_tests(suite, node.modname) + else: + added = [] + for modname in args: + for node in tree.traverse(): + if node.modname == modname: + for n in node.traverse(): + if n.modname not in added: + add_module_tests(suite, n.modname) + added.append(n.modname) + break + + result = unittest.TextTestRunner(verbosity=verbosity).run(suite) + + numErrors = len(result.errors) + numFailures = len(result.failures) + numBad = numErrors + numFailures + if numBad > 126: + numBad = 1 + sys.exit(numBad) diff --git a/test_upgrade.py b/test_upgrade.py new file mode 100755 index 0000000..40db42a --- /dev/null +++ b/test_upgrade.py @@ -0,0 +1,33 @@ +#!/bin/bash +# +# Test upgrade functionality by checking out revisions with the +# various initial on-disk versions and running `be list` on them to +# force an auto-upgrade. +# +# usage: test_upgrade.sh + +REVS='revid:wking@drexel.edu-20090831063121-85p59rpwoi1mzk3i +revid:wking@drexel.edu-20090831171945-73z3wwt4lrm7zbmu +revid:wking@drexel.edu-20091205224008-z4fed13sd80bj4fe +revid:wking@drexel.edu-20091207123614-okq7i0ahciaupuy9' + +ROOT=$(bzr root) +BE="$ROOT/be" +cd "$ROOT" + +echo "$REVS" | while read REV; do + TMPDIR=$(mktemp --directory --tmpdir "BE-upgrade.XXXXXXXXXX") + REPO="$TMPDIR/repo" + echo "Testing revision: $REV" + echo " Test directory: $REPO" + bzr checkout --lightweight --revision="$REV" "$ROOT" "$TMPDIR/repo" + VERSION=$(cat "$REPO/.be/version") + echo " Version: $VERSION" + $BE --repo "$REPO" list > /dev/null + RET="$?" + rm -rf "$TMPDIR" + if [ $RET -ne 0 ]; then + echo "Error! ($RET)" + exit $RET + fi +done diff --git a/test_usage.sh b/test_usage.sh new file mode 100755 index 0000000..9b7dafe --- /dev/null +++ b/test_usage.sh @@ -0,0 +1,154 @@ +#!/bin/bash +# +# Run through some simple usage cases. This both tests that important +# features work, and gives an example of suggested usage to get people +# started. +# +# usage: test_usage.sh VCS +# where VCS is one of: +# bzr, git, hg, arch, none +# +# Note that this script uses the *installed* version of be, not the +# one in your working tree. + +set -e # exit immediately on failed command +set -o pipefail # pipes fail if any stage fails +set -v # verbose, echo commands to stdout + +exec 6>&2 # save stderr to file descriptor 6 +exec 2>&1 # fd 2 now writes to stdout + +if [ $# -gt 1 ] +then + echo "usage: test_usage.sh [VCS]" + echo "" + echo "where VCS is one of" + for VCS in arch bzr darcs git hg none + do + echo " $VCS" + done + exit 1 +elif [ $# -eq 0 ] +then + for VCS in arch bzr darcs git hg none + do + echo -e "\n\nTesting $VCS\n\n" + $0 "$VCS" || exit 1 + done + exit 0 +fi + +VCS="$1" + +TESTDIR=`mktemp -d /tmp/BEtest.XXXXXXXXXX` +cd $TESTDIR + +# Initialize the VCS repository +if [ "$VCS" == "arch" ] +then + ID=`tla my-id` + ARCH_PARAM_DIR="$HOME/.arch-params" + ARCH_ARCHIVE_ROOT=`mktemp -d /tmp/BEtest.XXXXXXXXXX` + UNIQUE=`echo "$ARCH_ARCHIVE_ROOT" | sed 's/\/tmp\/BEtest.//;s/[0-9]//g'` + ARCH_ARCHIVE="j@x.com--BE-test-usage-$UNIQUE" + ARCH_PROJECT="BE-test-usage--twig--99.5" + ARCH_ARCHIVE_DIR="$ARCH_ARCHIVE_ROOT/$ARCH_PROJECT" + echo "tla make-archive $ARCH_ARCHIVE $ARCH_ARCHIVE_DIR" + tla make-archive $ARCH_ARCHIVE $ARCH_ARCHIVE_DIR + echo "tla archive-setup -A $ARCH_ARCHIVE $ARCH_PROJECT" + tla archive-setup -A $ARCH_ARCHIVE $ARCH_PROJECT + echo "tla init-tree -A $ARCH_ARCHIVE $ARCH_PROJECT" + tla init-tree -A $ARCH_ARCHIVE $ARCH_PROJECT + echo "Adjusing the naming conventions to allow .files" + sed -i 's/^source .*/source ^[._=a-zA-X0-9].*$/' '{arch}/=tagging-method' + echo "tla import -A $ARCH_ARCHIVE --summary 'Began versioning'" + tla import -A $ARCH_ARCHIVE --summary 'Began versioning' +elif [ "$VCS" == "bzr" ] +then + ID=`bzr whoami` + bzr init +elif [ "$VCS" == "darcs" ] +then + if [ -z "$DARCS_EMAIL" ]; then + export DARCS_EMAIL="J. Doe <jdoe@example.com>" + fi + ID="$DARCS_EMAIL" + darcs init +elif [ "$VCS" == "git" ] +then + NAME=`git config user.name` + EMAIL=`git config user.email` + ID="$NAME <$EMAIL>" + git init +elif [ "$VCS" == "hg" ] +then + ID=`hg showconfig ui.username` + hg init +elif [ "$VCS" == "none" ] +then + ID=`id -nu` +else + echo "Unrecognized VCS '$VCS'" + exit 1 +fi + +if [ -z "$ID" ] +then # set a default ID for VCSs that aren't tracking one yet. + ID="John Doe <jdoe@example.com>" +fi +echo "I am '$ID'" + +be init # initialize the Bugs Everywhere repository +OUT=`be new 'having too much fun'` # create a new bug +echo "$OUT" +BUG=`echo "$OUT" | sed -n 's/Created bug with ID //p'` +echo "Working with bug: $BUG" +be comment $BUG "This is an argument" +#be set user_id "$ID" # get tired of guessing user id for none VCS +be set # show settings +be comment $BUG/ "No it isn't" # comment on the first comment +be show $BUG # show details on a given bug +be status closed $BUG # set bug status to 'closed' +be comment $BUG "It's closed, but I can still comment." +if [ "$VCS" != 'none' ]; then + be commit 'Initial commit' +fi +be status open $BUG # set bug status to 'open' +be comment $BUG "Reopend, comment again" +be status fixed $BUG # set bug status to 'fixed' +be list # list all open bugs +be list --status fixed # list all fixed bugs +be assign - $BUG # assign the bug to yourself +be list -m --status fixed # see fixed bugs assigned to you +be assign 'Joe' $BUG # assign the bug to Joe +be list -a Joe --status fixed # list the fixed bugs assigned to Joe +be assign none $BUG # un-assign the bug +if [ "$VCS" != 'none' ]; then + be diff # see what has changed +fi +OUT=`be new 'also having too much fun'` +BUGB=`echo "$OUT" | sed -n 's/Created bug with ID //p'` +be comment $BUGB "Blissfully unaware of a similar bug" +be merge $BUG $BUGB # join BUGB to BUG +be --no-pager show $BUG # show bug details & comments +# you can also export/import XML bugs/comments +OUT=`be new 'yet more fun'` +BUGC=`echo "$OUT" | sed -n 's/Created bug with ID //p'` +be comment $BUGC "The ants go marching..." +be show --xml $BUGC/ | be import-xml --add-only --comment-root $BUG - +be remove $BUG # decide that you don't like that bug after all +be commit "You can even commit using BE" +be commit --allow-empty "And you can add empty commits if you like" +be commit "But this will fail" || echo "Failed" + +cd / +rm -rf $TESTDIR + +if [ "$VCS" == "arch" ] +then + # Cleanup everything outside of TESTDIR + rm -rf "$ARCH_ARCHIVE_ROOT" + rm -rf "$ARCH_PARAM_DIR/=locations/$ARCH_ARCHIVE" +fi + +exec 2>&6 6>&- # restore stderr and close fd 6 diff --git a/update_copyright.py b/update_copyright.py new file mode 100755 index 0000000..2490ba9 --- /dev/null +++ b/update_copyright.py @@ -0,0 +1,318 @@ +#!/usr/bin/python +# +# Copyright (C) 2009 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os.path +import re +import sys +import time + +import os +import sys +import select +from threading import Thread + +from libbe.util.subproc import Pipe + +COPYRIGHT_TEXT="""# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" + +COPYRIGHT_TAG='-xyz-COPYRIGHT-zyx-' # unlikely to occur in the wild :p + +ALIASES = [ + ['Ben Finney <benf@cybersource.com.au>', + 'Ben Finney <ben+python@benfinney.id.au>', + 'John Doe <jdoe@example.com>'], + ['Chris Ball <cjb@laptop.org>', + 'Chris Ball <cjb@thunk.printf.net>'], + ['Gianluca Montecchi <gian@grys.it>', + 'gian <gian@li82-39>', + 'gianluca <gian@galactica>'], + ['W. Trevor King <wking@drexel.edu>', + 'wking <wking@mjolnir>'], + [None, + 'j^ <j@oil21.org>'], + ] +COPYRIGHT_ALIASES = [ + ['Aaron Bentley and Panometrics, Inc.', + 'Aaron Bentley <abentley@panoramicfeedback.com>'], + ] +EXCLUDES = [ + ['Aaron Bentley and Panometrics, Inc.', + 'Aaron Bentley <aaron.bentley@utoronto.ca>',] + ] + + +IGNORED_PATHS = ['./.be/', './.bzr/', './build/'] +IGNORED_FILES = ['COPYING', 'update_copyright.py', 'catmutt'] + +def _strip_email(*args): + """ + >>> _strip_email('J Doe <jdoe@a.com>') + ['J Doe'] + >>> _strip_email('J Doe <jdoe@a.com>', 'JJJ Smith <jjjs@a.com>') + ['J Doe', 'JJJ Smith'] + """ + args = list(args) + for i,arg in enumerate(args): + if arg == None: + continue + index = arg.find('<') + if index > 0: + args[i] = arg[:index].rstrip() + return args + +def _replace_aliases(authors, with_email=True, aliases=None, + excludes=None): + """ + >>> aliases = [['J Doe and C, Inc.', 'J Doe <jdoe@c.com>'], + ... ['J Doe <jdoe@a.com>', 'Johnny <jdoe@b.edu>'], + ... ['JJJ Smith <jjjs@a.com>', 'Jingly <jjjs@b.edu>'], + ... [None, 'Anonymous <a@a.com>']] + >>> excludes = [['J Doe and C, Inc.', 'J Doe <jdoe@a.com>']] + >>> _replace_aliases(['JJJ Smith <jjjs@a.com>', 'Johnny <jdoe@b.edu>', + ... 'Jingly <jjjs@b.edu>', 'Anonymous <a@a.com>'], + ... with_email=True, aliases=aliases, excludes=excludes) + ['J Doe <jdoe@a.com>', 'JJJ Smith <jjjs@a.com>'] + >>> _replace_aliases(['JJJ Smith', 'Johnny', 'Jingly', 'Anonymous'], + ... with_email=False, aliases=aliases, excludes=excludes) + ['J Doe', 'JJJ Smith'] + >>> _replace_aliases(['JJJ Smith <jjjs@a.com>', 'Johnny <jdoe@b.edu>', + ... 'Jingly <jjjs@b.edu>', 'J Doe <jdoe@c.com>'], + ... with_email=True, aliases=aliases, excludes=excludes) + ['J Doe and C, Inc.', 'JJJ Smith <jjjs@a.com>'] + """ + if aliases == None: + aliases = ALIASES + if excludes == None: + excludes = EXCLUDES + if with_email == False: + aliases = [_strip_email(*alias) for alias in aliases] + exclude = [_strip_email(*exclude) for exclude in excludes] + for i,author in enumerate(authors): + for alias in aliases: + if author in alias[1:]: + authors[i] = alias[0] + break + for i,author in enumerate(authors): + for exclude in excludes: + if author in exclude[1:] and exclude[0] in authors: + authors[i] = None + authors = sorted(set(authors)) + if None in authors: + authors.remove(None) + return authors + +def authors_list(): + p = Pipe([['bzr', 'log', '-n0'], + ['grep', '^ *committer\|^ *author'], + ['cut', '-d:', '-f2'], + ['sed', 's/ <.*//;s/^ *//'], + ['sort'], + ['uniq']]) + assert p.status == 0, p.statuses + authors = p.stdout.rstrip().split('\n') + return _replace_aliases(authors, with_email=False) + +def update_authors(verbose=True): + print "updating AUTHORS" + f = file('AUTHORS', 'w') + authors_text = 'Bugs Everywhere was written by:\n%s\n' % '\n'.join(authors_list()) + f.write(authors_text) + f.close() + +def ignored_file(filename, ignored_paths=None, ignored_files=None): + """ + >>> ignored_paths = ['./a/', './b/'] + >>> ignored_files = ['x', 'y'] + >>> ignored_file('./a/z', ignored_paths, ignored_files) + True + >>> ignored_file('./ab/z', ignored_paths, ignored_files) + False + >>> ignored_file('./ab/x', ignored_paths, ignored_files) + True + >>> ignored_file('./ab/xy', ignored_paths, ignored_files) + False + >>> ignored_file('./z', ignored_paths, ignored_files) + False + """ + if ignored_paths == None: + ignored_paths = IGNORED_PATHS + if ignored_files == None: + ignored_files = IGNORED_FILES + for path in ignored_paths: + if filename.startswith(path): + return True + if os.path.basename(filename) in ignored_files: + return True + if os.path.abspath(filename) != os.path.realpath(filename): + return True # symink somewhere in path... + return False + +def _copyright_string(orig_year, final_year, authors): + """ + >>> print _copyright_string(orig_year=2005, + ... final_year=2005, + ... authors=['A <a@a.com>', 'B <b@b.edu>'] + ... ) # doctest: +ELLIPSIS + # Copyright (C) 2005 A <a@a.com> + # B <b@b.edu> + # + # This program... + >>> print _copyright_string(orig_year=2005, + ... final_year=2009, + ... authors=['A <a@a.com>', 'B <b@b.edu>'] + ... ) # doctest: +ELLIPSIS + # Copyright (C) 2005-2009 A <a@a.com> + # B <b@b.edu> + # + # This program... + """ + if orig_year == final_year: + date_range = '%s' % orig_year + else: + date_range = '%s-%s' % (orig_year, final_year) + lines = ['# Copyright (C) %s %s' % (date_range, authors[0])] + for author in authors[1:]: + lines.append('#' + + ' '*(len(' Copyright (C) ')+len(date_range)+1) + + author) + return '%s\n%s' % ('\n'.join(lines), COPYRIGHT_TEXT) + +def _tag_copyright(contents): + """ + >>> contents = '''Some file + ... bla bla + ... # Copyright (copyright begins) + ... # (copyright continues) + ... # bla bla bla + ... (copyright ends) + ... bla bla bla + ... ''' + >>> print _tag_copyright(contents), + Some file + bla bla + -xyz-COPYRIGHT-zyx- + (copyright ends) + bla bla bla + """ + lines = [] + incopy = False + for line in contents.splitlines(): + if incopy == False and line.startswith('# Copyright'): + incopy = True + lines.append(COPYRIGHT_TAG) + elif incopy == True and not line.startswith('#'): + incopy = False + if incopy == False: + lines.append(line.rstrip('\n')) + return '\n'.join(lines)+'\n' + +def _update_copyright(contents, orig_year, authors): + current_year = time.gmtime()[0] + copyright_string = _copyright_string(orig_year, current_year, authors) + contents = _tag_copyright(contents) + return contents.replace(COPYRIGHT_TAG, copyright_string) + +def update_file(filename, verbose=True): + if verbose == True: + print "updating", filename + contents = file(filename, 'r').read() + + p = Pipe([['bzr', 'log', '-n0', filename], + ['grep', '^ *timestamp: '], + ['tail', '-n1'], + ['sed', 's/^ *//;'], + ['cut', '-b', '16-19']]) + if p.status != 0: + assert p.statuses[0] == 3, p.statuses + return # bzr doesn't version that file + assert p.status == 0, p.statuses + orig_year = int(p.stdout.strip()) + + p = Pipe([['bzr', 'log', '-n0', filename], + ['grep', '^ *author: \|^ *committer: '], + ['cut', '-d:', '-f2'], + ['sed', 's/^ *//;s/ *$//'], + ['sort'], + ['uniq']]) + assert p.status == 0, p.statuses + authors = p.stdout.rstrip().split('\n') + authors = _replace_aliases(authors, with_email=True, + aliases=ALIASES+COPYRIGHT_ALIASES) + + contents = _update_copyright(contents, orig_year, authors) + f = file(filename, 'w') + f.write(contents) + f.close() + +def update_files(files=None): + if files == None or len(files) == 0: + p = Pipe([['grep', '-rc', '# Copyright', '.'], + ['grep', '-v', ':0$'], + ['cut', '-d:', '-f1']]) + assert p.status == 0 + files = p.stdout.rstrip().split('\n') + + for filename in files: + if ignored_file(filename) == True: + continue + update_file(filename) + +def test(): + import doctest + doctest.testmod() + +if __name__ == '__main__': + import optparse + usage = """%prog [options] [file ...] + +Update copyright information in source code with information from +the bzr repository. Run from the BE repository root. + +Replaces every line starting with '^# Copyright' and continuing with +'^#' with an auto-generated copyright blurb. If you want to add +#-commented material after a copyright blurb, please insert a blank +line between the blurb and your comment (as in this file), so the +next run of update_copyright.py doesn't clobber your comment. + +If no files are given, a list of files to update is generated +automatically. +""" + p = optparse.OptionParser(usage) + p.add_option('--test', dest='test', default=False, + action='store_true', help='Run internal tests and exit') + options,args = p.parse_args() + + if options.test == True: + test() + sys.exit(0) + + update_authors() + update_files(files=args) |