aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/1f3a2868-5308-42fd-b46c-68d8dfc851b2/body26
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/1f3a2868-5308-42fd-b46c-68d8dfc851b2/values11
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/e9c40503-84a0-47eb-b923-ac7b2f1b8de9/body3
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/e9c40503-84a0-47eb-b923-ac7b2f1b8de9/values11
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/values17
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/3bdf1af4-7d61-4b2d-8e25-e54a1ab7e37b/body24
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/3bdf1af4-7d61-4b2d-8e25-e54a1ab7e37b/values11
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/90dd557c-b6ec-402a-bbae-0ccd31448f07/body3
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/90dd557c-b6ec-402a-bbae-0ccd31448f07/values11
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/values17
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/89d53fa4-635d-4562-adaa-cd04b6c80fcf/body7
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/89d53fa4-635d-4562-adaa-cd04b6c80fcf/values11
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/64ccf451-b61c-491f-aa68-0ac1b5dbfa6d/comments/232cc363-ba55-4557-9840-3451d74e7b6d/body1
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/64ccf451-b61c-491f-aa68-0ac1b5dbfa6d/comments/232cc363-ba55-4557-9840-3451d74e7b6d/values11
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/64ccf451-b61c-491f-aa68-0ac1b5dbfa6d/values17
-rw-r--r--.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/77399855-6300-41a8-91a3-decbb915a3ff/values2
-rw-r--r--AUTHORS4
-rw-r--r--Makefile12
-rw-r--r--NEWS11
-rw-r--r--doc/generate-libbe-txt.py3
-rw-r--r--doc/hacking.txt14
-rw-r--r--doc/index.txt1
-rw-r--r--doc/install.txt2
-rw-r--r--doc/power.txt27
-rw-r--r--doc/tutorial.txt6
-rw-r--r--interfaces/email/interactive/_mailfilterrc3
-rw-r--r--interfaces/email/interactive/_procmailrc3
-rwxr-xr-xinterfaces/email/interactive/be-handle-mail3
-rw-r--r--interfaces/email/interactive/send_pgp_mime.py5
-rw-r--r--libbe/__init__.py1
-rw-r--r--libbe/bug.py46
-rw-r--r--libbe/bugdir.py22
-rw-r--r--libbe/command/__init__.py4
-rw-r--r--libbe/command/assign.py17
-rw-r--r--libbe/command/base.py45
-rw-r--r--libbe/command/comment.py2
-rw-r--r--libbe/command/commit.py30
-rw-r--r--libbe/command/depend.py4
-rw-r--r--libbe/command/diff.py1
-rw-r--r--libbe/command/due.py3
-rw-r--r--libbe/command/help.py1
-rw-r--r--libbe/command/html.py1091
-rw-r--r--libbe/command/import_xml.py12
-rw-r--r--libbe/command/init.py1
-rw-r--r--libbe/command/list.py2
-rw-r--r--libbe/command/merge.py3
-rw-r--r--libbe/command/new.py28
-rw-r--r--libbe/command/remove.py1
-rw-r--r--libbe/command/serve.py66
-rw-r--r--libbe/command/set.py52
-rw-r--r--libbe/command/severity.py29
-rw-r--r--libbe/command/show.py1
-rw-r--r--libbe/command/status.py10
-rw-r--r--libbe/command/subscribe.py3
-rw-r--r--libbe/command/tag.py3
-rw-r--r--libbe/command/util.py3
-rw-r--r--libbe/comment.py27
-rw-r--r--libbe/diff.py1
-rw-r--r--libbe/error.py3
-rw-r--r--libbe/storage/__init__.py3
-rw-r--r--libbe/storage/base.py3
-rw-r--r--libbe/storage/http.py14
-rw-r--r--libbe/storage/util/config.py1
-rw-r--r--libbe/storage/util/mapfile.py1
-rw-r--r--libbe/storage/util/properties.py3
-rw-r--r--libbe/storage/util/settings_object.py3
-rw-r--r--libbe/storage/util/upgrade.py3
-rw-r--r--libbe/storage/vcs/__init__.py3
-rw-r--r--libbe/storage/vcs/arch.py3
-rw-r--r--libbe/storage/vcs/base.py101
-rw-r--r--libbe/storage/vcs/bzr.py55
-rw-r--r--libbe/storage/vcs/darcs.py5
-rw-r--r--libbe/storage/vcs/git.py5
-rw-r--r--libbe/storage/vcs/hg.py19
-rw-r--r--libbe/storage/vcs/monotone.py5
-rw-r--r--libbe/ui/__init__.py3
-rw-r--r--libbe/ui/command_line.py75
-rw-r--r--libbe/ui/util/__init__.py3
-rw-r--r--libbe/ui/util/editor.py3
-rw-r--r--libbe/ui/util/pager.py3
-rw-r--r--libbe/ui/util/user.py39
-rw-r--r--libbe/util/__init__.py3
-rw-r--r--libbe/util/encoding.py8
-rw-r--r--libbe/util/id.py11
-rw-r--r--libbe/util/plugin.py1
-rw-r--r--libbe/util/subproc.py44
-rw-r--r--libbe/util/tree.py3
-rw-r--r--libbe/util/utility.py5
-rw-r--r--libbe/version.py3
-rwxr-xr-xmisc/xml/be-mail-to-xml3
-rwxr-xr-xmisc/xml/be-xml-to-mbox3
-rwxr-xr-xrelease.py3
-rw-r--r--test.py1
-rwxr-xr-xupdate_copyright.py3
94 files changed, 1415 insertions, 818 deletions
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/1f3a2868-5308-42fd-b46c-68d8dfc851b2/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/1f3a2868-5308-42fd-b46c-68d8dfc851b2/body
new file mode 100644
index 0000000..5b88ffe
--- /dev/null
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/1f3a2868-5308-42fd-b46c-68d8dfc851b2/body
@@ -0,0 +1,26 @@
+From 80ce5b9707750edba08842cd267843fa035d7b0a Mon Sep 17 00:00:00 2001
+From: Valtteri Kokkoniemi <rvk@iki.fi>
+Date: Thu, 17 Feb 2011 12:03:56 +0200
+Subject: [PATCH] fixed created tag handling in import-xml
+
+---
+ libbe/bug.py | 4 ++++
+ 1 files changed, 4 insertions(+), 0 deletions(-)
+
+diff --git a/libbe/bug.py b/libbe/bug.py
+index 6d3d836..122c81a 100644
+--- a/libbe/bug.py
++++ b/libbe/bug.py
+@@ -395,6 +395,10 @@ class Bug (settings_object.SavedSettingsObject):
+ if child.tag == 'uuid':
+ uuid = text
+ continue # don't set the bug's uuid tag.
++ elif child.tag == 'created':
++ self.time = utility.str_to_time(text)
++ self.explicit_attrs.append('time')
++ continue
+ elif child.tag == 'extra-string':
+ estrs.append(text)
+ continue # don't set the bug's extra_string yet.
+--
+1.7.1
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/1f3a2868-5308-42fd-b46c-68d8dfc851b2/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/1f3a2868-5308-42fd-b46c-68d8dfc851b2/values
new file mode 100644
index 0000000..3b73aaa
--- /dev/null
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/1f3a2868-5308-42fd-b46c-68d8dfc851b2/values
@@ -0,0 +1,11 @@
+Alt-id: 6ca8e92a-52b2-4909-a088-6bae5d7511a6
+
+
+Author: kokval <kokval@vesuri>
+
+
+Content-type: text/plain
+
+
+Date: Thu, 17 Feb 2011 10:40:51 +0000
+
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/e9c40503-84a0-47eb-b923-ac7b2f1b8de9/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/e9c40503-84a0-47eb-b923-ac7b2f1b8de9/body
new file mode 100644
index 0000000..44ae6b6
--- /dev/null
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/e9c40503-84a0-47eb-b923-ac7b2f1b8de9/body
@@ -0,0 +1,3 @@
+show --xml generates <created> tags for bug creation timestamp, but these break in import
+
+for fix, see attached patch
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/e9c40503-84a0-47eb-b923-ac7b2f1b8de9/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/e9c40503-84a0-47eb-b923-ac7b2f1b8de9/values
new file mode 100644
index 0000000..e9a75cc
--- /dev/null
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/comments/e9c40503-84a0-47eb-b923-ac7b2f1b8de9/values
@@ -0,0 +1,11 @@
+Alt-id: 3193f33d-ed57-4c32-9f6e-598bdc4ea67e
+
+
+Author: kokval <kokval@vesuri>
+
+
+Content-type: text/plain
+
+
+Date: Thu, 17 Feb 2011 10:40:21 +0000
+
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/values
new file mode 100644
index 0000000..fe30a27
--- /dev/null
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/35a50658-c13c-4f29-9bd0-032c411e4a6b/values
@@ -0,0 +1,17 @@
+creator: Valtteri Kokkoniemi <rvk@iki.fi
+
+
+reporter: Valtteri Kokkoniemi <rvk@iki.fi>
+
+
+severity: minor
+
+
+status: fixed
+
+
+summary: created-tags break import-xml
+
+
+time: Thu, 17 Feb 2011 10:38:22 +0000
+
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/3bdf1af4-7d61-4b2d-8e25-e54a1ab7e37b/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/3bdf1af4-7d61-4b2d-8e25-e54a1ab7e37b/body
new file mode 100644
index 0000000..9f3c45f
--- /dev/null
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/3bdf1af4-7d61-4b2d-8e25-e54a1ab7e37b/body
@@ -0,0 +1,24 @@
+From 1a6ce70b03a8e7df3e21189cd552d95d28535c1d Mon Sep 17 00:00:00 2001
+From: Valtteri Kokkoniemi <rvk@iki.fi>
+Date: Thu, 17 Feb 2011 10:11:09 +0200
+Subject: [PATCH] fixed importing new bugs
+
+---
+ libbe/command/import_xml.py | 2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/libbe/command/import_xml.py b/libbe/command/import_xml.py
+index bd25372..b4da2fd 100644
+--- a/libbe/command/import_xml.py
++++ b/libbe/command/import_xml.py
+@@ -184,7 +184,7 @@ class Import_XML (libbe.command.Command):
+ except KeyError:
+ old = None
+ if old == None:
+- bd.append(new)
++ bugdir.append(new)
+ else:
+ old.load_comments(load_full=True)
+ old.merge(new, accept_changes=accept_changes,
+--
+1.7.1
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/3bdf1af4-7d61-4b2d-8e25-e54a1ab7e37b/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/3bdf1af4-7d61-4b2d-8e25-e54a1ab7e37b/values
new file mode 100644
index 0000000..52ef33b
--- /dev/null
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/3bdf1af4-7d61-4b2d-8e25-e54a1ab7e37b/values
@@ -0,0 +1,11 @@
+Alt-id: 1bb12bbd-27b8-4638-abdb-c69384707b60
+
+
+Author: Valtteri Kokkoniemi <rvk@iki.fi>
+
+
+Content-type: text/plain
+
+
+Date: Thu, 17 Feb 2011 08:25:02 +0000
+
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/90dd557c-b6ec-402a-bbae-0ccd31448f07/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/90dd557c-b6ec-402a-bbae-0ccd31448f07/body
new file mode 100644
index 0000000..c2f09cc
--- /dev/null
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/90dd557c-b6ec-402a-bbae-0ccd31448f07/body
@@ -0,0 +1,3 @@
+probably due to refactoring at some point in history
+
+for fix, see the attached patch
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/90dd557c-b6ec-402a-bbae-0ccd31448f07/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/90dd557c-b6ec-402a-bbae-0ccd31448f07/values
new file mode 100644
index 0000000..664a6f1
--- /dev/null
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/comments/90dd557c-b6ec-402a-bbae-0ccd31448f07/values
@@ -0,0 +1,11 @@
+Alt-id: c50ada21-1ce1-4d1b-bcc0-834746ba176c
+
+
+Author: kokval <kokval@vesuri>
+
+
+Content-type: text/plain
+
+
+Date: Thu, 17 Feb 2011 08:21:33 +0000
+
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/values
new file mode 100644
index 0000000..9c07438
--- /dev/null
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/4bc91110-1240-4733-af00-1df1712a7abb/values
@@ -0,0 +1,17 @@
+creator: Valtteri Kokkoniemi <rvk@iki.fi>
+
+
+reporter: Valtteri Kokkoniemi <rvk@iki.fi>
+
+
+severity: minor
+
+
+status: fixed
+
+
+summary: xml import of new bugs is broken
+
+
+time: Thu, 17 Feb 2011 08:19:41 +0000
+
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/89d53fa4-635d-4562-adaa-cd04b6c80fcf/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/89d53fa4-635d-4562-adaa-cd04b6c80fcf/body
new file mode 100644
index 0000000..56d993c
--- /dev/null
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/89d53fa4-635d-4562-adaa-cd04b6c80fcf/body
@@ -0,0 +1,7 @@
+> We still want storage-level hooks for notification emails, etc,
+
+The new `be serve --notify ...` is effectively a command-specific
+storage-level hook (for storage actions that change the repository).
+If it seems to be working out well, we can move the logic down into
+the base storage classes.
+
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/89d53fa4-635d-4562-adaa-cd04b6c80fcf/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/89d53fa4-635d-4562-adaa-cd04b6c80fcf/values
new file mode 100644
index 0000000..2b82539
--- /dev/null
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/5fb11e65-68a0-4015-b404-737238299cdc/comments/89d53fa4-635d-4562-adaa-cd04b6c80fcf/values
@@ -0,0 +1,11 @@
+Author: '"W. Trevor King" <wking@drexel.edu>'
+
+
+Content-type: text/plain
+
+
+Date: Sun, 17 Apr 2011 02:05:09 +0000
+
+
+In-reply-to: 628a050a-f969-4290-8468-f5e991528f40
+
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/64ccf451-b61c-491f-aa68-0ac1b5dbfa6d/comments/232cc363-ba55-4557-9840-3451d74e7b6d/body b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/64ccf451-b61c-491f-aa68-0ac1b5dbfa6d/comments/232cc363-ba55-4557-9840-3451d74e7b6d/body
new file mode 100644
index 0000000..4b19934
--- /dev/null
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/64ccf451-b61c-491f-aa68-0ac1b5dbfa6d/comments/232cc363-ba55-4557-9840-3451d74e7b6d/body
@@ -0,0 +1 @@
+The make install will curently install the bin files in ~/bin and the lib files in ~/lib. Unfortunately this will lead to the lib files not being recognised by python by default. http://www.python.org/dev/peps/pep-0370/ defines that the paths should be set to '~/.local/bin' and '~/.local/lib'. Here they will also be automatically discoverable by python. (At least on Ubuntu 10.10)
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/64ccf451-b61c-491f-aa68-0ac1b5dbfa6d/comments/232cc363-ba55-4557-9840-3451d74e7b6d/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/64ccf451-b61c-491f-aa68-0ac1b5dbfa6d/comments/232cc363-ba55-4557-9840-3451d74e7b6d/values
new file mode 100644
index 0000000..1856450
--- /dev/null
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/64ccf451-b61c-491f-aa68-0ac1b5dbfa6d/comments/232cc363-ba55-4557-9840-3451d74e7b6d/values
@@ -0,0 +1,11 @@
+Alt-id: e9ef9fdd-4d0e-4438-a23d-6658eae9b349
+
+
+Author: dietmarw <dietmarw@HeX>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 01 Feb 2011 21:52:29 +0000
+
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/64ccf451-b61c-491f-aa68-0ac1b5dbfa6d/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/64ccf451-b61c-491f-aa68-0ac1b5dbfa6d/values
new file mode 100644
index 0000000..6a2d2ce
--- /dev/null
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/64ccf451-b61c-491f-aa68-0ac1b5dbfa6d/values
@@ -0,0 +1,17 @@
+creator: dietmarw <dietmarw@HeX>
+
+
+reporter: dietmarw <dietmarw@HeX>
+
+
+severity: minor
+
+
+status: fixed
+
+
+summary: Set default user paths according to PEP370
+
+
+time: Tue, 01 Feb 2011 21:41:27 +0000
+
diff --git a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/77399855-6300-41a8-91a3-decbb915a3ff/values b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/77399855-6300-41a8-91a3-decbb915a3ff/values
index 30d4df6..0f32345 100644
--- a/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/77399855-6300-41a8-91a3-decbb915a3ff/values
+++ b/.be/bea86499-824e-4e77-b085-2d581fa9ccab/bugs/77399855-6300-41a8-91a3-decbb915a3ff/values
@@ -5,7 +5,7 @@ extra_strings:
severity: target
-status: open
+status: fixed
summary: '1.0'
diff --git a/AUTHORS b/AUTHORS
index d112dec..c7823e4 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -2,6 +2,7 @@ Bugs Everywhere was written by:
Aaron Bentley <abentley@panoramicfeedback.com>
Alex Miller <alex55miller@gmail.com>
Alexander Belchenko <bialix@ukr.net>
+Andrew Cooper <andrew.cooper@hkcreations.org>
Anton Batenev <abbat@abbat>
Ben Finney <benf@cybersource.com.au>
Chris Ball <cjb@laptop.org>
@@ -15,8 +16,11 @@ Marien Zwart <marien.zwart@gmail.com>
Mathieu Clabaut <mathieu.clabaut@gmail.com>
Moritz Barsnick (at dot) <moritzbarsnicknet>
Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com>
+Robert Lehmann <mail@robertlehmann.de>
Steve Losh <steve@stevelosh.com>
Thomas Gerigk <tgerigk@gmx.de>
Thomas Habets <thomas@habets.pp.se>
Thomas Keller <me@thomaskeller.biz>
+Tim Guirgies <lt.infiltrator@gmail.com>
+Valtteri Kokkoniemi <rvk@iki.fi>
W. Trevor King <wking@drexel.edu>
diff --git a/Makefile b/Makefile
index fce6060..b64ce52 100644
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,7 @@
#
# Copyright (C) 2008-2011 Anton Batenev <abbat@abbat>
# Ben Finney <benf@cybersource.com.au>
+# Chris Ball <cjb@laptop.org>
# Eric Kow <eric.kow@gmail.com>
# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
@@ -32,9 +33,8 @@ RST2HTML = /usr/bin/rst2html
#PATH = /usr/bin:/bin # must include sphinx-build for 'sphinx' target.
-#PREFIX = /usr/local
-PREFIX = ${HOME}
-INSTALL_OPTIONS = "--prefix=${PREFIX}"
+#INSTALL_OPTIONS = "--prefix=/usr/local"
+INSTALL_OPTIONS = "--user"
# Select the documentation you wish to build
DOC = sphinx man
@@ -51,11 +51,11 @@ MANPAGE_FILES = $(patsubst %,${MAN_DIR}/%,${MANPAGES})
MANPAGE_HTML = $(patsubst %,${MAN_DIR}/%.html,${MANPAGES})
GENERATED_FILES += ${MANPAGE_FILES} ${MANPAGE_HTML}
-
+
.PHONY: all
all: build
-
+
.PHONY: build
build: $(LIBBE_VERSION)
python setup.py build
@@ -75,7 +75,7 @@ clean:
$(RM) -rf ${GENERATED_FILES}
$(MAKE) -C ${DOC_DIR} clean
-
+
.PHONY: libbe/_version.py
libbe/_version.py:
git log -1 --date=short --pretty='format:"Autogenerated by make libbe/_version.py"%nversion_info = {%n "date":"%cd",%n "revision":"%H",%n "committer":"%cn"}%n' > $@
diff --git a/NEWS b/NEWS
index aff73ac..986e887 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,14 @@
+April 16, 2011
+ * Added --preserve-uuids to `be import-xml`.
+ * Added --assigned, --severity, and --status to `be new`.
+ * Added --notify to `be serve`.
+
+March 5, 2011
+ * Release version 1.0.1 (bugfixes).
+
+January 8, 2011
+ * Release version 1.0.0.
+
July 14, 2010
* Added --show-status to `be depend`.
diff --git a/doc/generate-libbe-txt.py b/doc/generate-libbe-txt.py
index e352310..47484df 100644
--- a/doc/generate-libbe-txt.py
+++ b/doc/generate-libbe-txt.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
#
-# Copyright (C) 2010-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2010-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
diff --git a/doc/hacking.txt b/doc/hacking.txt
index 54be7bc..941dfde 100644
--- a/doc/hacking.txt
+++ b/doc/hacking.txt
@@ -71,10 +71,18 @@ 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::
+If you want to find out who's calling your expensive function
+(e.g. :func:`libbe.util.subproc.invoke`), try::
+
+ $ python -c "import pstats; p=pstats.Stats('profile'); p.sort_stats('cumulative').print_callers(20)"
+
+You can also toss::
import sys, traceback
print >> sys.stderr, '-'*60, '\n', '\n'.join(traceback.format_stack()[-10:])
-into expensive functions (e.g. :func:`libbe.util.subproc.invoke`) if
-you're not sure why they're being called.
+into the function itself for a depth-first caller list.
+
+For a more top-down approach, try::
+
+ $ python -c "import pstats; p=pstats.Stats('profile'); p.sort_stats('cumulative').print_callees(20)"
diff --git a/doc/index.txt b/doc/index.txt
index e8d8f07..7745d0c 100644
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -27,6 +27,7 @@ Contents:
email
http
distributed_bugtracking
+ power
hacking
spam
libbe/libbe
diff --git a/doc/install.txt b/doc/install.txt
index c428087..fd2bcaf 100644
--- a/doc/install.txt
+++ b/doc/install.txt
@@ -9,6 +9,7 @@ Dependencies
Package Role Debian Gentoo
============== ======================= =============== ===========================
PyYAML_ serialized data storage python-yaml dev-python/pyyaml
+Jinja_ HTML templating python-jinja2 deb-python/jinja
Sphinx_ see :doc:`doc` python-sphinx dev-python/sphinx
numpydoc_ see :doc:`doc` dev-python/numpydoc [#npd]_
Docutils_ manpage generation python-docutils dev-python/docutils
@@ -17,6 +18,7 @@ Docutils_ manpage generation python-docutils dev-python/docutils
.. [#npd] In the science_ overlay.
.. _PyYAML: http://pyyaml.org/
+.. _Jinja: http://jinja.pocoo.org/
.. _Sphinx: http://sphinx.pocoo.org/
.. _numpydoc: http://pypi.python.org/pypi/numpydoc
.. _Docutils: http://docutils.sourceforge.net/
diff --git a/doc/power.txt b/doc/power.txt
new file mode 100644
index 0000000..b0e93ea
--- /dev/null
+++ b/doc/power.txt
@@ -0,0 +1,27 @@
+**************
+Power features
+**************
+
+BE comes with a number of additional utilities and features that may
+be useful to power users. We'll try to keep an up to date list here,
+but your best bet may be poking around in the source on your own.
+
+Autocompletion
+==============
+
+:file:`misc/completion` contains completion scripts for common shells
+(if we don't have a completion script for your favorite shell, submit
+one!). Basic instructions for installing the completion file for a
+given shell should be given in the completion script comments.
+
+Packagers should install these completion scripts in their system's
+usual spot (on Gentoo it's :file:`/etc/bash_completion.d/`).
+
+XML-handling utilities
+======================
+
+Email threads are quite similar to the bugs/issues that BE tracks.
+There are a number of useful scripts in :file:`misc/xml` to go back
+and forth between the two formats using BE's XML format. The commands
+should be well documented. Use the usual ``<command> --help`` for
+more details on a given command.
diff --git a/doc/tutorial.txt b/doc/tutorial.txt
index e6790cd..c82f7c5 100644
--- a/doc/tutorial.txt
+++ b/doc/tutorial.txt
@@ -16,7 +16,7 @@ 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 :mod:`IDs
-</libbe.util.id>` rather than numbers. There is also a
+<libbe.util.id>` rather than numbers. There is also a
developer-friendly command-line_ interface to compliment the
user-friendly :doc:`web </http>` and :doc:`email </email>` interfaces.
This tutorial will focus on the command-line interface as the most
@@ -102,8 +102,8 @@ if you call it from a directory besides your project's root.
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 :mod:`IDs`
-for details. For BE itself, the bug directory is
+to track bugs, another to track roadmap issues, etc. See :mod:`IDs
+<libbe.util.id>` 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/``.
diff --git a/interfaces/email/interactive/_mailfilterrc b/interfaces/email/interactive/_mailfilterrc
index e2ef53b..26f6709 100644
--- a/interfaces/email/interactive/_mailfilterrc
+++ b/interfaces/email/interactive/_mailfilterrc
@@ -1,4 +1,5 @@
-# Copyright (C) 2010-2011 Gour <gour@gour-nitai.com>
+# Copyright (C) 2010-2011 Chris Ball <cjb@laptop.org>
+# Gour <gour@gour-nitai.com>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/interfaces/email/interactive/_procmailrc b/interfaces/email/interactive/_procmailrc
index 922e575..e020cdc 100644
--- a/interfaces/email/interactive/_procmailrc
+++ b/interfaces/email/interactive/_procmailrc
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/interfaces/email/interactive/be-handle-mail b/interfaces/email/interactive/be-handle-mail
index 0933498..e20934a 100755
--- a/interfaces/email/interactive/be-handle-mail
+++ b/interfaces/email/interactive/be-handle-mail
@@ -1,6 +1,7 @@
#!/usr/bin/env python
#
-# Copyright (C) 2009-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/interfaces/email/interactive/send_pgp_mime.py b/interfaces/email/interactive/send_pgp_mime.py
index 2e6c487..c361bec 100644
--- a/interfaces/email/interactive/send_pgp_mime.py
+++ b/interfaces/email/interactive/send_pgp_mime.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
#
-# Copyright (C) 2009-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
@@ -603,7 +604,7 @@ if __name__ == '__main__':
elif options.mode == "plain":
bodymsg = m.plain()
else:
- print "Unrecognized mode '%s'" % options.mode
+ raise Exception("unrecognized mode '%s'" % options.mode)
message = attach_root(headermsg, bodymsg)
if options.output == True:
diff --git a/libbe/__init__.py b/libbe/__init__.py
index d5a8f2e..c57fd09 100644
--- a/libbe/__init__.py
+++ b/libbe/__init__.py
@@ -1,4 +1,5 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/libbe/bug.py b/libbe/bug.py
index 6d3d836..be6f44d 100644
--- a/libbe/bug.py
+++ b/libbe/bug.py
@@ -1,5 +1,8 @@
-# Copyright (C) 2008-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2008-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
+# Robert Lehmann <mail@robertlehmann.de>
# Thomas Habets <thomas@habets.pp.se>
+# Valtteri Kokkoniemi <rvk@iki.fi>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
@@ -47,11 +50,6 @@ 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/
@@ -199,10 +197,19 @@ class Bug (settings_object.SavedSettingsObject):
def _get_time(self):
if self.time_string == None:
+ self._cached_time_string = None
+ self._cached_time = None
return None
- return utility.str_to_time(self.time_string)
+ if (not hasattr(self, '_cached_time_string')
+ or self.time_string != self._cached_time_string):
+ self._cached_time_string = self.time_string
+ self._cached_time = utility.str_to_time(self.time_string)
+ return self._cached_time
def _set_time(self, value):
- self.time_string = utility.time_to_str(value)
+ if not hasattr(self, '_cached_time') or value != self._cached_time:
+ self.time_string = utility.time_to_str(value)
+ self._cached_time_string = self.time_string
+ self._cached_time = value
time = property(fget=_get_time,
fset=_set_time,
doc="An integer version of .time_string")
@@ -290,6 +297,8 @@ class Bug (settings_object.SavedSettingsObject):
("Reporter", self._setting_attr_string("reporter")),
("Creator", self._setting_attr_string("creator")),
("Created", timestring)]
+ for estr in self.extra_strings:
+ info.append(('Extra string', estr))
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')
@@ -337,7 +346,7 @@ class Bug (settings_object.SavedSettingsObject):
sep = '\n' + istring
return istring + sep.join(lines).rstrip('\n')
- def from_xml(self, xml_string, verbose=True):
+ def from_xml(self, xml_string, preserve_uuids=False, 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()")
@@ -359,9 +368,13 @@ class Bug (settings_object.SavedSettingsObject):
>>> bugB.xml(show_comments=True) == xml
True
>>> bugB.explicit_attrs # doctest: +NORMALIZE_WHITESPACE
- ['severity', 'status', 'creator', 'created', 'summary']
+ ['severity', 'status', 'creator', 'time', 'summary']
>>> len(list(bugB.comments()))
3
+ >>> bugC = Bug()
+ >>> bugC.from_xml(xml, preserve_uuids=True)
+ >>> bugC.uuid == bugA.uuid
+ True
"""
if type(xml_string) == types.UnicodeType:
xml_string = xml_string.strip().encode('unicode_escape')
@@ -383,7 +396,8 @@ class Bug (settings_object.SavedSettingsObject):
pass
elif child.tag == 'comment':
comm = comment.Comment(bug=self)
- comm.from_xml(child)
+ comm.from_xml(
+ child, preserve_uuids=preserve_uuids, verbose=verbose)
comments.append(comm)
continue
elif child.tag in tags:
@@ -392,9 +406,13 @@ class Bug (settings_object.SavedSettingsObject):
else:
text = xml.sax.saxutils.unescape(child.text)
text = text.decode('unicode_escape').strip()
- if child.tag == 'uuid':
+ if child.tag == 'uuid' and not preserve_uuids:
uuid = text
continue # don't set the bug's uuid tag.
+ elif child.tag == 'created':
+ self.time = utility.str_to_time(text)
+ self.explicit_attrs.append('time')
+ continue
elif child.tag == 'extra-string':
estrs.append(text)
continue # don't set the bug's extra_string yet.
@@ -632,8 +650,8 @@ class Bug (settings_object.SavedSettingsObject):
def load_settings(self, settings_mapfile=None):
if settings_mapfile == None:
- settings_mapfile = \
- self.storage.get(self.id.storage('values'), default='\n')
+ settings_mapfile = self.storage.get(
+ self.id.storage('values'), '\n')
try:
settings = mapfile.parse(settings_mapfile)
except mapfile.InvalidMapfileContents, e:
diff --git a/libbe/bugdir.py b/libbe/bugdir.py
index 9741239..c73e097 100644
--- a/libbe/bugdir.py
+++ b/libbe/bugdir.py
@@ -220,14 +220,18 @@ class BugDir (list, settings_object.SavedSettingsObject):
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))
+ self._refresh_uuid_cache()
+ self._uuids_cache = self._uuids_cache.union([bug.uuid for bug in self])
+ return self._uuids_cache
+
+ def _refresh_uuid_cache(self):
+ self._uuids_cache = set()
+ # 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.add(id)
def _clear_bugs(self):
while len(self) > 0:
@@ -248,7 +252,7 @@ class BugDir (list, settings_object.SavedSettingsObject):
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)
+ self._uuids_cache.add(bg.uuid)
return bg
def remove_bug(self, bug):
diff --git a/libbe/command/__init__.py b/libbe/command/__init__.py
index 1cad096..b520f40 100644
--- a/libbe/command/__init__.py
+++ b/libbe/command/__init__.py
@@ -1,4 +1,5 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
@@ -19,6 +20,7 @@
import base
UserError = base.UserError
+UsageError = base.UsageError
UnknownCommand = base.UnknownCommand
get_command = base.get_command
get_command_class = base.get_command_class
@@ -33,7 +35,7 @@ UnconnectedStorageGetter = base.UnconnectedStorageGetter
StorageCallbacks = base.StorageCallbacks
UserInterface = base.UserInterface
-__all__ = [UserError, UnknownCommand,
+__all__ = [UserError, UsageError, UnknownCommand,
get_command, get_command_class, commands,
Option, Argument, Command,
InputOutput, StdInputOutput, StringInputOutput,
diff --git a/libbe/command/assign.py b/libbe/command/assign.py
index 0b3f407..99a657b 100644
--- a/libbe/command/assign.py
+++ b/libbe/command/assign.py
@@ -1,6 +1,8 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# Marien Zwart <marien.zwart@gmail.com>
+# Robert Lehmann <mail@robertlehmann.de>
# Thomas Gerigk <tgerigk@gmx.de>
# W. Trevor King <wking@drexel.edu>
#
@@ -73,11 +75,7 @@ class Assign (libbe.command.Command):
])
def _run(self, **params):
- assigned = params['assigned']
- if assigned == 'none':
- assigned = None
- elif assigned == '-':
- assigned = self._get_user_id()
+ assigned = parse_assigned(self, params['assigned'])
bugdir = self._get_bugdir()
for bug_id in params['bug-id']:
bug,dummy_comment = \
@@ -99,3 +97,12 @@ Special assigned strings:
"-" assign the bug to yourself
"none" un-assigns the bug
"""
+
+def parse_assigned(command, assigned):
+ """Standard processing for the 'assigned' Argument.
+ """
+ if assigned == 'none':
+ assigned = None
+ elif assigned == '-':
+ assigned = command._get_user_id()
+ return assigned
diff --git a/libbe/command/base.py b/libbe/command/base.py
index b5f5a22..11835ee 100644
--- a/libbe/command/base.py
+++ b/libbe/command/base.py
@@ -1,4 +1,6 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# Robert Lehmann <mail@robertlehmann.de>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
@@ -27,13 +29,41 @@ import libbe.ui.util.user
import libbe.util.encoding
import libbe.util.plugin
-class UserError(Exception):
+
+class UserError (Exception):
+ "An error due to improper BE usage."
pass
-class UnknownCommand(UserError):
- def __init__(self, cmd):
- Exception.__init__(self, "Unknown command '%s'" % cmd)
- self.cmd = cmd
+
+class UsageError (UserError):
+ """A serious parsing error due to invalid BE command construction.
+
+ The distinction between `UserError`\s and the more specific
+ `UsageError`\s is that when displaying a `UsageError` to the user,
+ the user is pointed towards the command usage information. Use
+ the more general `UserError` if you feel that usage information
+ would not be particularly enlightening.
+ """
+ def __init__(self, command=None, command_name=None, message=None):
+ super(UsageError, self).__init__(message)
+ self.command = command
+ if command_name is None and command is not None:
+ command_name = command.name
+ self.command_name = command_name
+ self.message = message
+
+
+class UnknownCommand (UsageError):
+ def __init__(self, command_name, message=None):
+ uc_message = "Unknown command '%s'" % command_name
+ if message is None:
+ message = uc_message
+ else:
+ message = '%s\n(%s)' % (uc_message, message)
+ super(UnknownCommand, self).__init__(
+ command_name=command_name,
+ message=message)
+
def get_command(command_name):
"""Retrieves the module for a user command
@@ -43,6 +73,7 @@ def get_command(command_name):
... except UnknownCommand, e:
... print e
Unknown command 'asdf'
+ (No module named asdf)
>>> repr(get_command('list')).startswith("<module 'libbe.command.list' from ")
True
"""
@@ -50,7 +81,7 @@ def get_command(command_name):
cmd = libbe.util.plugin.import_by_name(
'libbe.command.%s' % command_name.replace("-", "_"))
except ImportError, e:
- raise UnknownCommand(command_name)
+ raise UnknownCommand(command_name, message=unicode(e))
return cmd
def get_command_class(module=None, command_name=None):
diff --git a/libbe/command/comment.py b/libbe/command/comment.py
index d182840..7fa6ec7 100644
--- a/libbe/command/comment.py
+++ b/libbe/command/comment.py
@@ -1,5 +1,7 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
+# Robert Lehmann <mail@robertlehmann.de>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/libbe/command/commit.py b/libbe/command/commit.py
index a2ed051..8416107 100644
--- a/libbe/command/commit.py
+++ b/libbe/command/commit.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
@@ -59,16 +60,19 @@ class Commit (libbe.command.Command):
])
self.args.extend([
libbe.command.Argument(
- name='comment', metavar='COMMENT', default=None),
+ name='summary', metavar='SUMMARY', default=None,
+ optional=True),
])
def _run(self, **params):
- if params['comment'] == '-': # read summary from stdin
+ if params['summary'] == '-': # 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']
+ summary = params['summary']
+ if summary == None and params['body'] == None:
+ params['body'] = 'EDITOR'
storage = self._get_storage()
if params['body'] == None:
body = None
@@ -79,6 +83,13 @@ class Commit (libbe.command.Command):
self._check_restricted_access(storage, params['body'])
body = libbe.util.encoding.get_file_contents(
params['body'], decode=True)
+ if summary == None: # use the first body line as the summary
+ if body == None:
+ raise libbe.command.UserError(
+ 'cannot commit without a summary')
+ lines = body.splitlines()
+ summary = lines[0]
+ body = '\n'.join(lines[1:]).strip() + '\n'
try:
revision = storage.commit(summary, body=body,
allow_empty=params['allow-empty'])
@@ -89,7 +100,12 @@ class Commit (libbe.command.Command):
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.
+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. If no summary is given, the first line from
+the body message is used instead. If no summary or body is given, we
+spawn an editor without needing the special "EDITOR" value for the
+"--body" option.
"""
diff --git a/libbe/command/depend.py b/libbe/command/depend.py
index 9ae449a..1aa5053 100644
--- a/libbe/command/depend.py
+++ b/libbe/command/depend.py
@@ -1,4 +1,6 @@
-# Copyright (C) 2009-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
+# Robert Lehmann <mail@robertlehmann.de>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/libbe/command/diff.py b/libbe/command/diff.py
index 08a7efb..a9cdd50 100644
--- a/libbe/command/diff.py
+++ b/libbe/command/diff.py
@@ -1,4 +1,5 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
diff --git a/libbe/command/due.py b/libbe/command/due.py
index e4fd0f1..cf1500d 100644
--- a/libbe/command/due.py
+++ b/libbe/command/due.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
diff --git a/libbe/command/help.py b/libbe/command/help.py
index e4825f0..01eae5c 100644
--- a/libbe/command/help.py
+++ b/libbe/command/help.py
@@ -1,4 +1,5 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# Marien Zwart <marien.zwart@gmail.com>
# Thomas Gerigk <tgerigk@gmx.de>
diff --git a/libbe/command/html.py b/libbe/command/html.py
index bb5b554..7420ce8 100644
--- a/libbe/command/html.py
+++ b/libbe/command/html.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# Mathieu Clabaut <mathieu.clabaut@gmail.com>
# W. Trevor King <wking@drexel.edu>
#
@@ -26,6 +27,8 @@ import string
import time
import xml.sax.saxutils
+from jinja2 import Environment, FileSystemLoader, DictLoader, ChoiceLoader
+
import libbe
import libbe.command
import libbe.command.util
@@ -82,12 +85,12 @@ class HTML (libbe.command.Command):
help='Set the bug repository title (%default)',
arg=libbe.command.Argument(
name='title', metavar='STRING',
- default='BugsEverywhere Issue Tracker')),
+ default='Bugs Everywhere 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')),
+ default='Bugs Everywhere 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',
@@ -112,9 +115,9 @@ class HTML (libbe.command.Command):
bugdir = self._get_bugdir()
bugdir.load_all_bugs()
html_gen = HTMLGen(bugdir,
- template=params['template-dir'],
+ template_dir=params['template-dir'],
title=params['title'],
- index_header=params['index-header'],
+ header=params['index-header'],
min_id_length=params['min-id-length'],
verbose=params['verbose'],
stdout=self.stdout)
@@ -132,28 +135,22 @@ 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",
+ def __init__(self, bd, template_dir=None,
+ title="Site Title", header="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.header = 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._load_templates(template_dir)
self.min_id_length = min_id_length
def run(self, out_dir):
@@ -179,21 +176,24 @@ class HTMLGen (object):
self._create_output_directories(out_dir)
self._write_css_file()
for b in bugs:
- if b.active:
+ if b.severity == 'target':
+ up_link = '../../index_target.html'
+ elif b.active:
up_link = '../../index.html'
else:
- up_link = '../../index_inactive.html'
-
- self._write_bug_file(b, up_link)
+ up_link = '../../index_inactive.html'
+ self._write_bug_file(
+ b, title=self.title, header=self.header,
+ up_link=up_link)
self._write_index_file(
bugs_active, title=self.title,
- index_header=self.index_header, bug_type='active')
+ header=self.header, bug_type='active')
self._write_index_file(
bugs_inactive, title=self.title,
- index_header=self.index_header, bug_type='inactive')
+ header=self.header, bug_type='inactive')
self._write_index_file(
bugs_target, title=self.title,
- index_header=self.index_header, bug_type='target')
+ header=self.header, bug_type='target')
def _truncated_bug_id(self, bug):
return libbe.util.id._truncate(
@@ -217,10 +217,10 @@ class HTMLGen (object):
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'])
+ template = self.template.get_template('style.css')
+ self._write_file(template.render(), [self.out_dir, 'style.css'])
- def _write_bug_file(self, bug, up_link):
+ def _write_bug_file(self, bug, title, header, up_link):
if self.verbose:
print >> self.stdout, '\tCreating bug file for %s' % bug.id.user()
assert hasattr(self, 'out_dir_bugs'), \
@@ -229,96 +229,77 @@ class HTMLGen (object):
if bug.active == True:
index_type = 'Active'
- else :
+ else:
index_type = 'Inactive'
if bug.severity == 'target':
index_type = 'Target'
bug.load_comments(load_full=True)
- comment_entries = self._generate_bug_comment_entries(bug)
+ bug.comment_root.sort(cmp=libbe.comment.cmp_time, reverse=True)
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,
- 'index_type':index_type}
- for attr in ['uuid', 'severity', 'status', 'assigned',
- 'reporter', 'creator', 'time_string', 'summary']:
- template_info[attr] = self._escape(getattr(bug, attr))
+ template_info = {
+ 'title': title,
+ 'charset': self.encoding,
+ 'stylesheet': '../../style.css',
+ 'header': header,
+ 'backlinks': self.template.get_template('bug_backlinks.html'),
+ 'up_link': up_link,
+ 'index_type': index_type,
+ 'bug': bug,
+ 'comment_entry': self.template.get_template(
+ 'bug_comment_entry.html'),
+ 'comments': [(depth,comment) for depth,comment
+ in bug.comment_root.thread(flatten=False)],
+ 'comment_dir': self._truncated_comment_id,
+ 'format_body': self._format_comment_body,
+ 'div_close': _DivCloser(),
+ 'generation_time': self.generation_time,
+ }
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])
+ template = self.template.get_template('bug.html')
+ self._write_file(template.render(template_info), [fullpath])
- def _generate_bug_comment_entries(self, bug):
- assert hasattr(self, 'out_dir_bugs'), \
- 'Must run after ._create_output_directories()'
+ def _write_index_file(self, bugs, title, 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()'
- 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)
+ if bug_type == 'active':
+ filename = 'index.html'
+ elif bug_type == 'inactive':
+ filename = 'index_inactive.html'
+ elif bug_type == 'target':
+ filename = 'index_by_target.html'
+ else:
+ raise ValueError('unrecognized bug_type: "%s"' % bug_type)
+
+ template_info = {
+ 'title': title,
+ 'charset': self.encoding,
+ 'stylesheet': 'style.css',
+ 'header': header,
+ 'active_class': 'tab nsel',
+ 'inactive_class': 'tab nsel',
+ 'target_class': 'tab nsel',
+ 'bugs': bugs,
+ 'bug_entry': self.template.get_template('index_bug_entry.html'),
+ 'bug_dir': self._truncated_bug_id,
+ 'generation_time': self.generation_time,
+ }
+ template_info['%s_class' % bug_type] = 'tab sel'
+ if bug_type == 'target':
+ template = self.template.get_template('target_index.html')
+ template_info['targets'] = [
+ (target, sorted(libbe.command.depend.get_blocked_by(
+ self.bd, target)))
+ for target in bugs]
+ else:
+ template = self.template.get_template('standard_index.html')
+ self._write_file(
+ template.render(template_info)+'\n', [self.out_dir,filename])
def _long_to_linked_user(self, text):
"""
@@ -382,91 +363,46 @@ class HTMLGen (object):
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
- if (bug_type == 'target'):
- bug_entries = self._generate_index_bug_entries_target(bugs)
+ def _format_comment_body(self, bug, comment):
+ link_long_ids = False
+ save_body = False
+ value = comment.body
+ 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:
- bug_entries = self._generate_index_bug_entries(bugs)
-
- if bug_type == 'active':
- filename = 'index.html'
- elif bug_type == 'inactive':
- filename = 'index_inactive.html'
- elif bug_type == 'target':
- filename = 'index_by_target.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',
- 'target_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'
- if bug_type == 'target':
- template_info['active_class'] = 'tab nsel'
- template_info['target_class'] = 'tab sel'
- self._write_file(self.index_file % template_info,
- [self.out_dir, filename])
-
- def _generate_index_bug_entries_target(self, targets):
-
- target_entries = []
- for target in targets:
- bug_entries = []
- template_info_list = {'target':target.summary, 'bug_entries': '', 'status': target.status}
- blocker = libbe.command.depend.get_blocked_by(self.bd, target)
- for bug in blocker:
- 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)
- template_info_list['bug_entries'] = '\n'.join(bug_entries)
- target_entries.append(self.target_bug_list % template_info_list)
- return '\n'.join(target_entries)
-
- def _generate_index_bug_entries(self, bugs):
- bug_entries = []
- template_info_list = {'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)
- template_info_list['bug_entries'] = '\n'.join(bug_entries)
- return self.bug_list % template_info_list
+ 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')
+ return value
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):
@@ -489,388 +425,425 @@ class HTMLGen (object):
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";
- font-size: 14px;
- color: #333;
- width: auto;
- margin: auto;
- }
-
- div.main {
- padding: 20px;
- margin: auto;
- padding-top: 0;
- margin-top: 1em;
- background-color: #fcfcfc;
- -moz-border-radius: 10px;
-
- }
-
- div.footer {
- font-size: small;
- padding-left: 20px;
- padding-right: 20px;
- padding-top: 5px;
- padding-bottom: 5px;
- margin: auto;
- background: #305275;
- color: #fffee7;
- -moz-border-radius: 10px;
- }
-
- div.header {
- font-size: xx-large;
- padding-left: 20px;
- padding-right: 20px;
- padding-top: 10px;
- font-weight:bold;
- padding-bottom: 10px;
- background: #305275;
- color: #fffee7;
- -moz-border-radius: 10px;
- }
-
- div.target_name {
- border: 1px solid;
- border-color: #305275;
- background-color: #305275;
- color: #fff;
- width: auto%;
- -moz-border-radius-topleft: 8px;
- -moz-border-radius-topright: 8px;
- padding-left: 5px;
- padding-right: 5px;
- }
-
- table {
- border-style: solid;
- border: 1px #c3d9ff;
- border-spacing: 0px 0px;
- width: auto;
- padding: 0px;
-
- }
-
- tb { border: 1px; }
-
- tr {
- vertical-align: top;
- border: 1px #c3d9ff;
- border-style: dotted;
- width: auto;
- padding: 0px;
- }
-
- th {
- border-width: 1px;
- border-style: solid;
- border-color: #c3d9ff;
- border-collapse: collapse;
- padding-left: 5px;
- padding-right: 5px;
- }
-
-
- td {
- border-width: 1px;
- border-color: #c3d9ff;
- border-collapse: collapse;
- padding-left: 5px;
- padding-right: 5px;
- 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: #c3d9ff ;
- border: 1px solid #c3d9ff;
- font-weight:bold;
- -moz-border-radius-topleft: 15px;
- -moz-border-radius-topright: 15px;
- }
-
- td.nsel.tab {
- border: 1px solid #c3d9ff;
- font-weight:bold;
- -moz-border-radius-topleft: 5px;
- -moz-border-radius-topright: 5px;
- }
-
- table.bug_list {
- border-width: 1px;
- border-style: solid;
- border-color: #c3d9ff;
- padding: 0px;
- width: 100%;
- border: 1px solid #c3d9ff;
- }
-
- table.target_list {
- border-width: 1px;
- border-style: solid;
- border-collapse: collapse;
- border-color: #c3d9ff;
- padding: 0px;
- width: 100%;
- margin-bottom: 10px;
- }
-
- table.target_list.td {
- border-width: 1px;
- }
-
- tr.wishlist { background-color: #DCFAFF;}
- tr.wishlist:hover { background-color: #C2DCE1; }
-
- tr.minor { background-color: #FFFFA6; }
- tr.minor:hover { background-color: #E6E696; }
-
- tr.serious { background-color: #FF9077;}
- tr.serious:hover { background-color: #E6826B; }
-
- tr.critical { background-color: #FF752A; }
- tr.critical:hover { background-color: #D63905;}
-
- tr.fatal { background-color: #FF3300;}
- tr.fatal:hover { background-color: #D60000;}
-
- td.uuid { width: 5%; border-style: dotted;}
- td.status { width: 5%; border-style: dotted;}
- td.severity { width: 5%; border-style: dotted;}
- td.summary { border-style: dotted;}
- td.date { width: 25%; border-style: dotted;}
-
- /* bug detail pages */
-
- td.bug_detail_label { text-align: right; border: none;}
- td.bug_detail { border: none;}
- 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;
+ for filename,text in self.template_dict.iteritems():
+ if self.verbose:
+ print >> self.stdout, 'Creating %s file'
+ self._write_file(text, [self.out_dir, filename])
+
+ def _load_templates(self, template_dir=None):
+ if template_dir is not None:
+ template_dir = os.path.abspath(os.path.expanduser(template_dir))
+
+ self.template_dict = {
+##
+ 'style.css':
+"""body {
+ font-family: "lucida grande", "sans serif";
+ font-size: 14px;
+ color: #333;
+ width: auto;
+ margin: auto;
+}
+
+div.main {
+ padding: 20px;
+ margin: auto;
+ padding-top: 0;
+ margin-top: 1em;
+ background-color: #fcfcfc;
+ -moz-border-radius: 10px;
+
+}
+
+div.footer {
+ font-size: small;
+ padding-left: 20px;
+ padding-right: 20px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ margin: auto;
+ background: #305275;
+ color: #fffee7;
+ -moz-border-radius: 10px;
+}
+
+div.header {
+ font-size: xx-large;
+ padding-left: 20px;
+ padding-right: 20px;
+ padding-top: 10px;
+ font-weight:bold;
+ padding-bottom: 10px;
+ background: #305275;
+ color: #fffee7;
+ -moz-border-radius: 10px;
+}
+
+th.target_name {
+ text-align:left;
+ border: 1px solid;
+ border-color: #305275;
+ background-color: #305275;
+ color: #fff;
+ width: auto%;
+ -moz-border-radius-topleft: 8px;
+ -moz-border-radius-topright: 8px;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+table {
+ border-style: solid;
+ border: 1px #c3d9ff;
+ border-spacing: 0px 0px;
+ width: auto;
+ padding: 0px;
+
+ }
+
+tb { border: 1px; }
+
+tr {
+ vertical-align: top;
+ border: 1px #c3d9ff;
+ border-style: dotted;
+ width: auto;
+ padding: 0px;
+}
+
+th {
+ border-width: 1px;
+ border-style: solid;
+ border-color: #c3d9ff;
+ border-collapse: collapse;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+
+td {
+ border-width: 1px;
+ border-color: #c3d9ff;
+ border-collapse: collapse;
+ padding-left: 5px;
+ padding-right: 5px;
+ width: auto%;
+}
+
+img { border-style: none; }
+
+ul {
+ list-style-type: none;
+ padding: 0;
+}
+
+p { width: auto; }
+
+p.backlink {
+ width: auto;
+ font-weight: bold;
+}
+
+a {
+ background: inherit;
+ text-decoration: none;
+}
+
+a { color: #553d41; }
+a:hover { color: #003d41; }
+a:visited { color: #305275; }
+.footer a { color: #508d91; }
+
+/* bug index pages */
+
+td.tab {
+ padding-right: 1em;
+ padding-left: 1em;
+}
+
+td.sel.tab {
+ background-color: #c3d9ff ;
+ border: 1px solid #c3d9ff;
+ font-weight:bold;
+ -moz-border-radius-topleft: 15px;
+ -moz-border-radius-topright: 15px;
+}
+
+td.nsel.tab {
+ border: 1px solid #c3d9ff;
+ font-weight:bold;
+ -moz-border-radius-topleft: 5px;
+ -moz-border-radius-topright: 5px;
+}
+
+table.bug_list {
+ border-width: 1px;
+ border-style: solid;
+ border-color: #c3d9ff;
+ padding: 0px;
+ width: 100%;
+ border: 1px solid #c3d9ff;
+}
+
+table.target_list {
+ padding: 0px;
+ width: 100%;
+ margin-bottom: 10px;
+}
+
+table.target_list.td {
+ border-width: 1px;
+}
+
+tr.wishlist { background-color: #DCFAFF;}
+tr.wishlist:hover { background-color: #C2DCE1; }
+
+tr.minor { background-color: #FFFFA6; }
+tr.minor:hover { background-color: #E6E696; }
+
+tr.serious { background-color: #FF9077;}
+tr.serious:hover { background-color: #E6826B; }
+
+tr.critical { background-color: #FF752A; }
+tr.critical:hover { background-color: #D63905;}
+
+tr.fatal { background-color: #FF3300;}
+tr.fatal:hover { background-color: #D60000;}
+
+td.uuid { width: 5%; border-style: dotted;}
+td.status { width: 5%; border-style: dotted;}
+td.severity { width: 5%; border-style: dotted;}
+td.summary { border-style: dotted;}
+td.date { width: 25%; border-style: dotted;}
+
+/* bug detail pages */
+
+td.bug_detail_label { text-align: right; border: none;}
+td.bug_detail { border: none;}
+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;
+}
+""",
+##
+ 'base.html':
+"""<!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 }}</title>
+ <meta http-equiv="Content-Type" content="text/html; charset={{ charset }}" />
+ <link rel="stylesheet" href="{{ stylesheet }}" type="text/css" />
+ </head>
+ <body>
+ <div class="header">{{ header }}</div>
+ <div class="main">
+ {% block content %}{% endblock %}
+ </div>
+ <div class="footer">
+ <p>Generated by <a href="http://www.bugseverywhere.org/">
+ Bugs Everywhere</a> on {{ generation_time }}</p>
+ <p>
+ <a href="http://validator.w3.org/check?uri=referer">
+ Validate XHTML</a>&nbsp;|&nbsp;
+ <a href="http://jigsaw.w3.org/css-validator/check?uri=referer">
+ Validate CSS</a>
+ </p>
+ </div>
+ </body>
+</html>
+""",
+ 'index.html':
+"""{% extends "base.html" %}
+
+{% block content %}
+<table>
+ <tbody>
+ <tr>
+ <td class="{{ active_class }}"><a href="index.html">Active Bugs</a></td>
+ <td class="{{ inactive_class }}"><a href="index_inactive.html">Inactive Bugs</a></td>
+ <td class="{{ target_class }}"><a href="index_by_target.html">Divided by target</a></td>
+ </tr>
+ </tbody>
+</table>
+{% if bugs %}
+{% block bug_table %}{% endblock %}
+{% else %}
+<p>No bugs.</p>
+{% endif %}
+{% endblock %}
+""",
+##
+ 'standard_index.html':
+"""{% extends "index.html" %}
+
+{% block bug_table %}
+<table class="bug_list">
+ <thead>
+ <tr>
+ <th>UUID</th>
+ <th>Status</th>
+ <th>Severity</th>
+ <th>Summary</th>
+ <th>Date</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for bug in bugs %}
+ {{ bug_entry.render({'bug':bug, 'dir':bug_dir(bug)}) }}
+ {% endfor %}
+ </tbody>
+</table>
+{% endblock %}
+""",
+##
+ 'target_index.html':
+"""{% extends "index.html" %}
+
+{% block bug_table %}
+{% for target,bugs in targets %}
+<table class="target_list">
+ <thead>
+ <tr>
+ <th class="target_name" colspan="5">
+ Target: {{ target.summary|e }} ({{ target.status|e }})
+ </th>
+ </tr>
+ <tr>
+ <th>UUID</th>
+ <th>Status</th>
+ <th>Severity</th>
+ <th>Summary</th>
+ <th>Date</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for bug in bugs %}
+ {{ bug_entry.render({'bug':bug, 'dir':bug_dir(bug)}) }}
+ {% endfor %}
+ </tbody>
+</table>
+{% endfor %}
+{% endblock %}
+""",
+##
+ 'index_bug_entry.html':
+"""<tr class="{{ bug.severity }}">
+ <td class="uuid"><a href="bugs/{{ dir }}/index.html">{{ bug.id.user()|e }}</a></td>
+ <td class="status"><a href="bugs/{{ dir }}/index.html">{{ bug.status|e }}</a></td>
+ <td class="severity"><a href="bugs/{{ dir }}/index.html">{{ bug.severity|e }}</a></td>
+ <td class="summary"><a href="bugs/{{ dir }}/index.html">{{ bug.summary|e }}</a></td>
+ <td class="date"><a href="bugs/{{ dir }}/index.html">{{ (bug.time_string or '')|e }}</a></td>
+</tr>
+""",
+##
+ 'bug.html':
+"""{% extends "base.html" %}
+
+{% block content %}
+{{ backlinks.render({'up_link': up_link, 'index_type':index_type}) }}
+<h1>Bug: {{ bug.id.user()|e }}</h1>
+
+<table>
+ <tbody>
+ <tr><td class="bug_detail_label">ID :</td>
+ <td class="bug_detail">{{ bug.uuid|e }}</td></tr>
+ <tr><td class="bug_detail_label">Short name :</td>
+ <td class="bug_detail">{{ bug.id.user()|e }}</td></tr>
+ <tr><td class="bug_detail_label">Status :</td>
+ <td class="bug_detail">{{ bug.status|e }}</td></tr>
+ <tr><td class="bug_detail_label">Severity :</td>
+ <td class="bug_detail">{{ bug.severity|e }}</td></tr>
+ <tr><td class="bug_detail_label">Assigned :</td>
+ <td class="bug_detail">{{ (bug.assigned or '')|e }}</td></tr>
+ <tr><td class="bug_detail_label">Reporter :</td>
+ <td class="bug_detail">{{ (bug.reporter or '')|e }}</td></tr>
+ <tr><td class="bug_detail_label">Creator :</td>
+ <td class="bug_detail">{{ (bug.creator or '')|e }}</td></tr>
+ <tr><td class="bug_detail_label">Created :</td>
+ <td class="bug_detail">{{ (bug.time_string or '')|e }}</td></tr>
+ <tr><td class="bug_detail_label">Summary :</td>
+ <td class="bug_detail">{{ bug.summary|e }}</td></tr>
+ </tbody>
+</table>
+
+<hr/>
+
+{% if comments %}
+{% for depth,comment in comments %}
+{% if depth == 0 %}
+<div class="comment root" id="C{{ comment_dir(comment) }}">
+{% else %}
+<div class="comment" id="C{{ comment_dir(comment) }}">
+{% endif %}
+{{ comment_entry.render({
+ 'depth':depth, 'bug': bug, 'comment':comment, 'comment_dir':comment_dir,
+ 'format_body': format_body, 'div_close': div_close}) }}
+{{ div_close(depth) }}
+{% endfor %}
+{% if comments[-1][0] > 0 %}
+{{ div_close(0) }}
+{% endif %}
+{% else %}
+<p>No comments.</p>
+{% endif %}
+{{ backlinks.render({'up_link': up_link, 'index_type': index_type}) }}
+{% endblock %}
+""",
+##
+ 'bug_backlinks.html':
+"""<p class="backlink"><a href="{{ up_link }}">Back to {{ index_type }} Index</a></p>
+<p class="backlink"><a href="../../index_by_target.html">Back to Target Index</a></p>
+""",
+##
+ 'bug_comment_entry.html':
+"""<table>
+ <tbody>
+ <tr>
+ <td class="bug_comment_label">Comment:</td>
+ <td class="bug_comment">
+ --------- Comment ---------<br/>
+ ID: {{ comment.uuid }}<br/>
+ Short name: {{ comment.id.user() }}<br/>
+ From: {{ (comment.author or '')|e }}<br/>
+ Date: {{ (comment.date or '')|e }}<br/>
+ <br/>
+ {{ format_body(bug, comment) }}
+ </td>
+ </tr>
+ </tbody>
+</table>
+""",
}
- 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="header">%(index_header)s</div>
- <div class="main">
- <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>
- <td class="%(target_class)s"><a href="index_by_target.html">Divided by target</a></td>
- </tr>
- </table>
+ loader = DictLoader(self.template_dict)
+ if template_dir:
+ file_system_loader = FileSystemLoader(template_dir)
+ loader = ChoiceLoader([file_system_loader, loader])
- %(bug_entries)s
+ self.template = Environment(loader=loader)
- </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>&nbsp;|&nbsp;
- <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 class="uuid"><a href="bugs/%(dir)s/index.html">%(shortname)s</a></td>
- <td class="status"><a href="bugs/%(dir)s/index.html">%(status)s</a></td>
- <td class="severity"><a href="bugs/%(dir)s/index.html">%(severity)s</a></td>
- <td class="summary"><a href="bugs/%(dir)s/index.html">%(summary)s</a></td>
- <td class="date"><a href="bugs/%(dir)s/index.html">%(time_string)s</a></td>
- </tr>
- """
- self.target_bug_list = """
- <tr>
- <td>
- <div class="target_name">
- Target: %(target)s (%(status)s)
- </div>
- <div>
- <table class="target_list">
-
- %(bug_entries)s
- </table>
- </div>
- </td>
- </tr>
- """
- self.bug_list = """
- <table class="bug_list">
-
- %(bug_entries)s
-
- </table>
-
- """
- 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="header">BugsEverywhere Bug List</div>
- <div class="main">
- <h5><a href="%(up_link)s">Back to %(index_type)s Index</a></h5>
- <h5><a href="../../index_by_target.html">Back to Target 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_type)s Index</a></h5>
- <h5><a href="../../index_by_target.html">Back to Target 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>&nbsp;|&nbsp;
- <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>
- """
+class _DivCloser (object):
+ def __init__(self, depth=0):
+ self.depth = depth
- # 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')
+ def __call__(self, depth):
+ ret = []
+ while self.depth >= depth:
+ self.depth -= 1
+ ret.append('</div>')
+ self.depth = depth
+ return '\n'.join(ret)
diff --git a/libbe/command/import_xml.py b/libbe/command/import_xml.py
index bd25372..d53df8c 100644
--- a/libbe/command/import_xml.py
+++ b/libbe/command/import_xml.py
@@ -1,4 +1,6 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# Valtteri Kokkoniemi <rvk@iki.fi>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
@@ -76,6 +78,8 @@ class Import_XML (libbe.command.Command):
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='preserve-uuids', short_name='p',
+ help='Preserve UUIDs for trusted input (potential name collisions).'),
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(
@@ -131,11 +135,11 @@ class Import_XML (libbe.command.Command):
for child in be_xml.getchildren():
if child.tag == 'bug':
new = libbe.bug.Bug(bugdir=bugdir)
- new.from_xml(child)
+ new.from_xml(child, preserve_uuids=params['preserve-uuids'])
root_bugs.append(new)
elif child.tag == 'comment':
new = libbe.comment.Comment(croot_bug)
- new.from_xml(child)
+ new.from_xml(child, preserve_uuids=params['preserve-uuids'])
root_comments.append(new)
elif child.tag == 'version':
for gchild in child.getchildren():
@@ -184,7 +188,7 @@ class Import_XML (libbe.command.Command):
except KeyError:
old = None
if old == None:
- bd.append(new)
+ bugdir.append(new)
else:
old.load_comments(load_full=True)
old.merge(new, accept_changes=accept_changes,
diff --git a/libbe/command/init.py b/libbe/command/init.py
index 92e07f9..378e544 100644
--- a/libbe/command/init.py
+++ b/libbe/command/init.py
@@ -1,4 +1,5 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
diff --git a/libbe/command/list.py b/libbe/command/list.py
index 9eda277..59254b2 100644
--- a/libbe/command/list.py
+++ b/libbe/command/list.py
@@ -1,6 +1,8 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com>
+# Robert Lehmann <mail@robertlehmann.de>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/libbe/command/merge.py b/libbe/command/merge.py
index 7fd62bb..0f49de9 100644
--- a/libbe/command/merge.py
+++ b/libbe/command/merge.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2008-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2008-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/libbe/command/new.py b/libbe/command/new.py
index 7dfbed8..deba8da 100644
--- a/libbe/command/new.py
+++ b/libbe/command/new.py
@@ -1,4 +1,6 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Andrew Cooper <andrew.cooper@hkcreations.org>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
@@ -21,6 +23,8 @@ import libbe
import libbe.command
import libbe.command.util
+from .assign import parse_assigned as _parse_assigned
+
class New (libbe.command.Command):
"""Create a new bug
@@ -40,7 +44,8 @@ class New (libbe.command.Command):
>>> 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',])
+ >>> options = {'assigned': 'none'}
+ >>> ret = ui.run(cmd, options=options, args=['this is a test',])
Created bug with ID abc/X
>>> libbe.util.id.uuid_gen = uuid_gen
>>> bd.flush_reload()
@@ -57,6 +62,8 @@ class New (libbe.command.Command):
minor
>>> print bug.status
open
+ >>> print bug.assigned
+ None
>>> ui.cleanup()
>>> bd.cleanup()
"""
@@ -78,6 +85,16 @@ class New (libbe.command.Command):
arg=libbe.command.Argument(
name='assigned', metavar='NAME',
completion_callback=libbe.command.util.complete_assigned)),
+ libbe.command.Option(name='status', short_name='t',
+ help='The bug\'s status level',
+ arg=libbe.command.Argument(
+ name='status', metavar='STATUS',
+ completion_callback=libbe.command.util.complete_status)),
+ libbe.command.Option(name='severity', short_name='s',
+ help='The bug\'s severity',
+ arg=libbe.command.Argument(
+ name='severity', metavar='SEVERITY',
+ completion_callback=libbe.command.util.complete_severity)),
])
self.args.extend([
libbe.command.Argument(name='summary', metavar='SUMMARY')
@@ -89,6 +106,7 @@ class New (libbe.command.Command):
else:
summary = params['summary']
bugdir = self._get_bugdir()
+ bugdir.storage.writeable = False
bug = bugdir.new_bug(summary=summary.strip())
if params['creator'] != None:
bug.creator = params['creator']
@@ -99,7 +117,13 @@ class New (libbe.command.Command):
else:
bug.reporter = bug.creator
if params['assigned'] != None:
- bug.assigned = params['assigned']
+ bug.assigned = _parse_assigned(self, params['assigned'])
+ if params['status'] != None:
+ bug.status = params['status']
+ if params['severity'] != None:
+ bug.severity = params['severity']
+ bugdir.storage.writeable = True
+ bug.save()
print >> self.stdout, 'Created bug with ID %s' % bug.id.user()
return 0
diff --git a/libbe/command/remove.py b/libbe/command/remove.py
index 1d4f265..095f1d3 100644
--- a/libbe/command/remove.py
+++ b/libbe/command/remove.py
@@ -1,4 +1,5 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# Marien Zwart <marien.zwart@gmail.com>
# Thomas Gerigk <tgerigk@gmx.de>
diff --git a/libbe/command/serve.py b/libbe/command/serve.py
index ba4b0d8..00591c0 100644
--- a/libbe/command/serve.py
+++ b/libbe/command/serve.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2010-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2010-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
@@ -58,6 +59,7 @@ import libbe
import libbe.command
import libbe.command.util
import libbe.util.encoding
+import libbe.util.subproc
import libbe.version
if libbe.TESTING == True:
@@ -507,9 +509,10 @@ class ServerApp (WSGI_AppObject):
"""
server_version = "BE-server/" + libbe.version.version()
- def __init__(self, storage, *args, **kwargs):
- WSGI_AppObject.__init__(self, *args, **kwargs)
+ def __init__(self, storage, notify=False, **kwargs):
+ WSGI_AppObject.__init__(self, **kwargs)
self.storage = storage
+ self.notify = notify
self.http_user_error = 418
self.urls = [
@@ -570,6 +573,9 @@ class ServerApp (WSGI_AppObject):
directory = self.data_get_boolean(
data, 'directory', default=False, source=source)
self.storage.add(id, parent=parent, directory=directory)
+ if self.notify:
+ self._notify(environ, 'add', id,
+ [('parent', parent), ('directory', directory)])
return self.ok_response(environ, start_response, None)
def exists(self, environ, start_response):
@@ -593,6 +599,8 @@ class ServerApp (WSGI_AppObject):
self.storage.recursive_remove(id)
else:
self.storage.remove(id)
+ if self.notify:
+ self._notify(environ, 'remove', id, [('recursive', recursive)])
return self.ok_response(environ, start_response, None)
def ancestors(self, environ, start_response):
@@ -641,6 +649,8 @@ class ServerApp (WSGI_AppObject):
raise _HandlerError(406, 'Missing query key value')
value = data['value']
self.storage.set(id, value)
+ if self.notify:
+ self._notify(environ, 'set', id, [('value', value)])
return self.ok_response(environ, start_response, None)
def commit(self, environ, start_response):
@@ -661,6 +671,10 @@ class ServerApp (WSGI_AppObject):
revision = self.storage.commit(summary, body, allow_empty)
except libbe.storage.EmptyCommit, e:
raise _HandlerError(self.http_user_error, 'EmptyCommit')
+ if self.notify:
+ self._notify(environ, 'commit', id,
+ [('allow_empty', allow_empty), ('summary', summary),
+ ('body', body)])
return self.ok_response(environ, start_response, revision)
def revision_id(self, environ, start_response):
@@ -700,9 +714,43 @@ class ServerApp (WSGI_AppObject):
raise _Unauthorized() # only non-guests allowed to write
# allow read-only commands for all users
+ def _notify(self, environ, command, id, params):
+ message = self._format_notification(environ, command, id, params)
+ self._submit_notification(message)
+
+ def _format_notification(self, environ, command, id, params):
+ key_length = len('command')
+ for key,value in params:
+ if len(key) > key_length and '\n' not in str(value):
+ key_length = len(key)
+ key_length += 1
+ lines = []
+ multi_line_params = []
+ for key,value in [('address', environ.get('REMOTE_ADDR', '-')),
+ ('command', command), ('id', id)]+params:
+ v = str(value)
+ if '\n' in v:
+ multi_line_params.append((key,v))
+ continue
+ lines.append('%*.*s %s' % (key_length, key_length, key+':', v))
+ lines.append('')
+ for key,value in multi_line_params:
+ lines.extend(['=== START %s ===' % key, v,
+ '=== STOP %s ===' % key, ''])
+ lines.append('')
+ return '\n'.join(lines)
+
+ def _submit_notification(self, message):
+ libbe.util.subproc.invoke(self.notify, stdin=message, shell=True)
+
class Serve (libbe.command.Command):
- """:class:`~libbe.command.base.Command` wrapper around
+ """Serve bug directory storage over HTTP.
+
+ This allows you to run local `be` commands interfacing with remote
+ data, transmitting file reads/writes/etc. over the network.
+
+ :class:`~libbe.command.base.Command` wrapper around
:class:`ServerApp`.
"""
@@ -721,6 +769,10 @@ class Serve (libbe.command.Command):
name='host', metavar='HOST', default='')),
libbe.command.Option(name='read-only', short_name='r',
help='Dissable operations that require writing'),
+ libbe.command.Option(name='notify', short_name='n',
+ help='Send notification emails for changes.',
+ arg=libbe.command.Argument(
+ name='notify', metavar='EMAIL-COMMAND', default=None)),
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',
@@ -742,7 +794,8 @@ class Serve (libbe.command.Command):
self._check_restricted_access(storage, params['auth'])
users = Users(params['auth'])
users.load()
- app = ServerApp(storage=storage, logger=self.logger)
+ app = ServerApp(
+ storage=storage, notify=params['notify'], logger=self.logger)
if params['auth'] != None:
app = AdminApp(app, users=users, logger=self.logger)
app = AuthenticationApp(app, realm=storage.repo,
@@ -860,6 +913,7 @@ if libbe.TESTING == True:
self.logger.setLevel(logging.INFO)
self.default_environ = { # required by PEP 333
'REQUEST_METHOD': 'GET', # 'POST', 'HEAD'
+ 'REMOTE_ADDR': '192.168.0.123',
'SCRIPT_NAME':'',
'PATH_INFO': '',
#'QUERY_STRING':'', # may be empty or absent
@@ -920,7 +974,7 @@ if libbe.TESTING == True:
self.app.log_request(
environ=self.default_environ, status='-1 OK', bytes=123)
log = self.logstream.getvalue()
- self.failUnless(log.startswith('- -'), log)
+ self.failUnless(log.startswith('192.168.0.123 -'), log)
class ExceptionAppTestCase (WSGITestCase):
def setUp(self):
diff --git a/libbe/command/set.py b/libbe/command/set.py
index b3eb583..d70d33e 100644
--- a/libbe/command/set.py
+++ b/libbe/command/set.py
@@ -1,4 +1,5 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# Marien Zwart <marien.zwart@gmail.com>
# Thomas Gerigk <tgerigk@gmx.de>
@@ -100,7 +101,56 @@ To unset a setting, set it to "none".
Allowed settings are:
-%s""" % ('\n'.join(get_bugdir_settings()),)
+%s
+
+Note that this command does not provide a good interface for some of
+these settings (yet!). You may need to edit the bugdir settings file
+(`.be/<bugdir>/settings`) manually. Examples for each troublesome
+setting are given below.
+
+Add the following lines to override the default severities and use
+your own:
+
+ severities:
+ - - target
+ - The issue is a target or milestone, not a bug.
+ - - wishlist
+ - A feature that could improve usefulness, but not a bug.
+
+You may add as many name/description pairs as you wish to have; they
+are sorted in order from least important at the top, to most important
+at the bottom. The target severity gets special handling by `be
+target`.
+
+Note that the values here _override_ the defaults. That means that if
+you like the defaults, and wish to keep them, you will have to copy
+them here before adding any of your own. See `be severity --help` for
+the current list.
+
+Add the following lines to override the default statuses and use your
+own:
+
+ active_status:
+ - - unconfirmed
+ - A possible bug which lacks independent existance confirmation.
+ - - open
+ - A working bug that has not been assigned to a developer.
+
+ inactive_status:
+ - - closed
+ - The bug is no longer relevant.
+ - - fixed
+ - The bug should no longer occur.
+
+You may add as many name/description pairs as you wish to have; they
+are sorted in order from most important at the top, to least important
+at the bottom.
+
+Note that the values here _override_ the defaults. That means that if
+you like the defaults, and wish to keep them, you will have to copy
+them here before adding any of your own. See `be status --help` for
+the current list.
+""" % ('\n'.join(get_bugdir_settings()),)
def get_bugdir_settings():
settings = []
diff --git a/libbe/command/severity.py b/libbe/command/severity.py
index a84efe8..fa6007a 100644
--- a/libbe/command/severity.py
+++ b/libbe/command/severity.py
@@ -1,7 +1,9 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# Marien Zwart <marien.zwart@gmail.com>
# Thomas Gerigk <tgerigk@gmx.de>
+# Tim Guirgies <lt.infiltrator@gmail.com>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
@@ -79,21 +81,26 @@ class Severity (libbe.command.Command):
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])
+ severity_levels = []
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)
+ s = '%*s : %s' % (longest_severity_len, severity, description)
+ severity_levels.append(s)
+ 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:
+ %s
+
+You can overide the list of allowed severities on a per-repository
+basis. See `be set --help` for details.
+""" % ('\n '.join(severity_levels))
+ return ret
diff --git a/libbe/command/show.py b/libbe/command/show.py
index ea86191..4f85c69 100644
--- a/libbe/command/show.py
+++ b/libbe/command/show.py
@@ -1,4 +1,5 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# Thomas Gerigk <tgerigk@gmx.de>
# Thomas Habets <thomas@habets.pp.se>
diff --git a/libbe/command/status.py b/libbe/command/status.py
index 2e470e4..2eb0755 100644
--- a/libbe/command/status.py
+++ b/libbe/command/status.py
@@ -1,7 +1,9 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# Marien Zwart <marien.zwart@gmail.com>
# Thomas Gerigk <tgerigk@gmx.de>
+# Tim Guirgies <lt.infiltrator@gmail.com>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
@@ -80,6 +82,10 @@ class Status (libbe.command.Command):
return 0
def _long_help(self):
+ try: # See if there are any per-tree status configurations
+ bd = self._get_bugdir()
+ except NotImplementedError:
+ pass # No tree, just show the defaults
longest_status_len = max([len(s) for s in libbe.bug.status_values])
active_statuses = []
for status in libbe.bug.active_status_values :
@@ -106,7 +112,7 @@ Active status levels are:
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.
+You can overide the list of allowed statuses on a per-repository
+basis. See `be set --help` for details.
""" % ('\n '.join(active_statuses), '\n '.join(inactive_statuses))
return ret
diff --git a/libbe/command/subscribe.py b/libbe/command/subscribe.py
index f49abb9..50f1e7e 100644
--- a/libbe/command/subscribe.py
+++ b/libbe/command/subscribe.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/libbe/command/tag.py b/libbe/command/tag.py
index dea6e00..1da8fd9 100644
--- a/libbe/command/tag.py
+++ b/libbe/command/tag.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/libbe/command/util.py b/libbe/command/util.py
index d8e049e..4e5471d 100644
--- a/libbe/command/util.py
+++ b/libbe/command/util.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
diff --git a/libbe/comment.py b/libbe/comment.py
index 6350e2c..3527d50 100644
--- a/libbe/comment.py
+++ b/libbe/comment.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2008-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2008-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# Thomas Habets <thomas@habets.pp.se>
# W. Trevor King <wking@drexel.edu>
#
@@ -53,13 +54,6 @@ 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)
@@ -67,11 +61,6 @@ class MissingReference(ValueError):
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):
@@ -340,7 +329,7 @@ class Comment (Tree, settings_object.SavedSettingsObject):
sep = '\n' + istring
return istring + sep.join(lines).rstrip('\n')
- def from_xml(self, xml_string, verbose=True):
+ def from_xml(self, xml_string, preserve_uuids=False, verbose=True):
u"""
Note: If alt-id is not given, translates any <uuid> fields to
<alt-id> fields.
@@ -360,6 +349,10 @@ class Comment (Tree, settings_object.SavedSettingsObject):
>>> commB.alt_id = None
>>> commB.xml() == xml
True
+ >>> commC = Comment()
+ >>> commC.from_xml(xml, preserve_uuids=True)
+ >>> commC.uuid == commA.uuid
+ True
"""
if type(xml_string) == types.UnicodeType:
xml_string = xml_string.strip().encode('unicode_escape')
@@ -385,7 +378,7 @@ class Comment (Tree, settings_object.SavedSettingsObject):
else:
text = xml.sax.saxutils.unescape(child.text)
text = text.decode('unicode_escape').strip()
- if child.tag == 'uuid':
+ if child.tag == 'uuid' and not preserve_uuids:
uuid = text
continue # don't set the comment's uuid tag.
elif child.tag == 'body':
@@ -607,8 +600,8 @@ class Comment (Tree, settings_object.SavedSettingsObject):
if self.uuid == INVALID_UUID:
return
if settings_mapfile == None:
- settings_mapfile = \
- self.storage.get(self.id.storage("values"), default="\n")
+ settings_mapfile = self.storage.get(
+ self.id.storage('values'), '\n')
try:
settings = mapfile.parse(settings_mapfile)
except mapfile.InvalidMapfileContents, e:
diff --git a/libbe/diff.py b/libbe/diff.py
index 1802be4..4c24073 100644
--- a/libbe/diff.py
+++ b/libbe/diff.py
@@ -1,4 +1,5 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
diff --git a/libbe/error.py b/libbe/error.py
index f385db0..0108177 100644
--- a/libbe/error.py
+++ b/libbe/error.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
diff --git a/libbe/storage/__init__.py b/libbe/storage/__init__.py
index 90e3b0d..46e5bf2 100644
--- a/libbe/storage/__init__.py
+++ b/libbe/storage/__init__.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
diff --git a/libbe/storage/base.py b/libbe/storage/base.py
index ef42c98..a9c1064 100644
--- a/libbe/storage/base.py
+++ b/libbe/storage/base.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
diff --git a/libbe/storage/http.py b/libbe/storage/http.py
index 4cf0f84..c2bb65b 100644
--- a/libbe/storage/http.py
+++ b/libbe/storage/http.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2010-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2010-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
@@ -334,14 +335,14 @@ if TESTING == True:
class GetPostUrlTestCase (unittest.TestCase):
"""Test cases for get_post_url()"""
def test_get(self):
- url = 'http://bugseverywhere.org/be/show/HomePage'
+ url = 'http://bugseverywhere.org/'
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'
+ url = 'http://physics.drexel.edu/~wking/code/be/redirect'
+ expected = 'http://physics.drexel.edu/~wking/'
page,final_url,info = get_post_url(url=url)
self.failUnless(final_url == expected,
'Redirect?\n Expected: "%s"\n Got: "%s"'
@@ -355,9 +356,10 @@ if TESTING == True:
storage=self._storage_backend)
HTTP.__init__(self, repo='http://localhost:8000/', *args, **kwargs)
self.intitialized = False
- # duplicated from libbe.storage.serve.WSGITestCase
+ # duplicated from libbe.command.serve.WSGITestCase
self.default_environ = {
'REQUEST_METHOD': 'GET', # 'POST', 'HEAD'
+ 'REMOTE_ADDR': '192.168.0.123',
'SCRIPT_NAME':'',
'PATH_INFO': '',
#'QUERY_STRING':'', # may be empty or absent
@@ -376,7 +378,7 @@ if TESTING == True:
}
def getURL(self, app, path='/', method='GET', data=None,
scheme='http', environ={}):
- # duplicated from libbe.storage.serve.WSGITestCase
+ # duplicated from libbe.command.serve.WSGITestCase
env = copy.copy(self.default_environ)
env['PATH_INFO'] = path
env['REQUEST_METHOD'] = method
diff --git a/libbe/storage/util/config.py b/libbe/storage/util/config.py
index 8a3d7c8..714d4e7 100644
--- a/libbe/storage/util/config.py
+++ b/libbe/storage/util/config.py
@@ -1,4 +1,5 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
diff --git a/libbe/storage/util/mapfile.py b/libbe/storage/util/mapfile.py
index 9eca586..abea6c8 100644
--- a/libbe/storage/util/mapfile.py
+++ b/libbe/storage/util/mapfile.py
@@ -1,4 +1,5 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
diff --git a/libbe/storage/util/properties.py b/libbe/storage/util/properties.py
index 152f5cc..9b0549a 100644
--- a/libbe/storage/util/properties.py
+++ b/libbe/storage/util/properties.py
@@ -1,5 +1,6 @@
# Bugs Everywhere - a distributed bugtracker
-# Copyright (C) 2008-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2008-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/libbe/storage/util/settings_object.py b/libbe/storage/util/settings_object.py
index 2026906..7c02734 100644
--- a/libbe/storage/util/settings_object.py
+++ b/libbe/storage/util/settings_object.py
@@ -1,5 +1,6 @@
# Bugs Everywhere - a distributed bugtracker
-# Copyright (C) 2008-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2008-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/libbe/storage/util/upgrade.py b/libbe/storage/util/upgrade.py
index 45e3058..3a5aa1c 100644
--- a/libbe/storage/util/upgrade.py
+++ b/libbe/storage/util/upgrade.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/libbe/storage/vcs/__init__.py b/libbe/storage/vcs/__init__.py
index 47d92db..1f7166d 100644
--- a/libbe/storage/vcs/__init__.py
+++ b/libbe/storage/vcs/__init__.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
diff --git a/libbe/storage/vcs/arch.py b/libbe/storage/vcs/arch.py
index 6c519c4..5a40f7c 100644
--- a/libbe/storage/vcs/arch.py
+++ b/libbe/storage/vcs/arch.py
@@ -1,5 +1,6 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
# Ben Finney <benf@cybersource.com.au>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# James Rowe <jnrowe@ukfsn.org>
# W. Trevor King <wking@drexel.edu>
@@ -437,7 +438,7 @@ class Arch(base.VCS):
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__])
diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py
index aba6159..22874a5 100644
--- a/libbe/storage/vcs/base.py
+++ b/libbe/storage/vcs/base.py
@@ -156,11 +156,11 @@ class CachedPathID (object):
>>> 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'),
+ >>> open(os.path.join(dir.path, '.be', 'abc', 'values'),
... 'w').close()
- >>> file(os.path.join(dir.path, '.be', 'abc', 'bugs', '123', 'values'),
+ >>> open(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'),
+ >>> open(os.path.join(dir.path, '.be', 'abc', 'bugs', '123', 'comments', 'def', 'values'),
... 'w').close()
>>> c = CachedPathID()
>>> c.root(dir.path)
@@ -521,6 +521,97 @@ class VCS (libbe.storage.base.VersionedStorage):
self._version = self._vcs_version()
return self._version
+ def version_cmp(self, *args):
+ """Compare the installed VCS 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
+ --------
+
+ >>> v = VCS(repo='.')
+ >>> v._version = '2.3.1 (release)'
+ >>> v.version_cmp(2,3,1)
+ 0
+ >>> v.version_cmp(2,3,2)
+ -1
+ >>> v.version_cmp(2,3,'a',5)
+ 1
+ >>> v.version_cmp(2,3,0)
+ 1
+ >>> v.version_cmp(2,3,1,'a',5)
+ 1
+ >>> v.version_cmp(2,3,1,1)
+ -1
+ >>> v.version_cmp(3)
+ -1
+ >>> v._version = '2.0.0pre2'
+ >>> v._parsed_version = None
+ >>> v.version_cmp(3)
+ -1
+ >>> v.version_cmp(2,0,1)
+ -1
+ >>> v.version_cmp(2,0,0,'pre',1)
+ 1
+ >>> v.version_cmp(2,0,0,'pre',2)
+ 0
+ >>> v.version_cmp(2,0,0,'pre',3)
+ -1
+ >>> v.version_cmp(2,0,0,'a',3)
+ 1
+ >>> v.version_cmp(2,0,0,'rc',1)
+ -1
+ """
+ if not hasattr(self, '_parsed_version') \
+ or self._parsed_version == None:
+ num_part = self.version().split(' ')[0]
+ self._parsed_version = []
+ for num in num_part.split('.'):
+ try:
+ self._parsed_version.append(int(num))
+ except ValueError, e:
+ # bzr version number might contain non-numerical tags
+ splitter = re.compile(r'[\D]') # Match non-digits
+ splits = splitter.split(num)
+ # if len(tag) > 1 some splits will be empty; remove
+ splits = filter(lambda s: s != '', splits)
+ tag_starti = len(splits[0])
+ num_starti = num.find(splits[1], tag_starti)
+ tag = num[tag_starti:num_starti]
+ self._parsed_version.append(int(splits[0]))
+ self._parsed_version.append(tag)
+ self._parsed_version.append(int(splits[1]))
+ for current,other in zip(self._parsed_version, args):
+ if type(current) != type (other):
+ # one of them is a pre-release string
+ if type(current) != types.IntType:
+ return -1
+ else:
+ return 1
+ c = cmp(current,other)
+ if c != 0:
+ return c
+ # see if one is longer than the other
+ verlen = len(self._parsed_version)
+ arglen = len(args)
+ if verlen == arglen:
+ return 0
+ elif verlen > arglen:
+ if type(self._parsed_version[arglen]) != types.IntType:
+ return -1 # self is a prerelease
+ else:
+ return 1
+ else:
+ if type(args[verlen]) != types.IntType:
+ return 1 # args is a prerelease
+ else:
+ return -1
+
def installed(self):
if self.version() != None:
return True
@@ -537,7 +628,7 @@ class VCS (libbe.storage.base.VersionedStorage):
self.user_id = self._vcs_get_user_id()
if self.user_id == None:
# guess missing info
- name = libbe.ui.util.user.get_fallback_username()
+ name = libbe.ui.util.user.get_fallback_fullname()
email = libbe.ui.util.user.get_fallback_email()
self.user_id = libbe.ui.util.user.create_user_id(name, email)
return self.user_id
@@ -1003,7 +1094,7 @@ class VCS (libbe.storage.base.VersionedStorage):
libbe.storage.STORAGE_VERSION+'\n')
self._vcs_add(self._u_rel_path(path))
-
+
if libbe.TESTING == True:
class VCSTestCase (unittest.TestCase):
"""
diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py
index 9464d1d..c01b6c7 100644
--- a/libbe/storage/vcs/bzr.py
+++ b/libbe/storage/vcs/bzr.py
@@ -1,5 +1,6 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
# Ben Finney <benf@cybersource.com.au>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# Marien Zwart <marien.zwart@gmail.com>
# W. Trevor King <wking@drexel.edu>
@@ -39,7 +40,6 @@ import re
import shutil
import StringIO
import sys
-import types
import libbe
import base
@@ -67,57 +67,6 @@ class Bzr(base.VCS):
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._version = '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._version = '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.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.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:
@@ -378,7 +327,7 @@ class Bzr(base.VCS):
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__])
diff --git a/libbe/storage/vcs/darcs.py b/libbe/storage/vcs/darcs.py
index 4a19d1d..aef89cd 100644
--- a/libbe/storage/vcs/darcs.py
+++ b/libbe/storage/vcs/darcs.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
@@ -402,7 +403,7 @@ class Darcs(base.VCS):
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__])
diff --git a/libbe/storage/vcs/git.py b/libbe/storage/vcs/git.py
index 5c17303..f23f0ea 100644
--- a/libbe/storage/vcs/git.py
+++ b/libbe/storage/vcs/git.py
@@ -1,6 +1,7 @@
# Copyright (C) 2008-2011 Ben Finney <benf@cybersource.com.au>
# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
+# Robert Lehmann <mail@robertlehmann.de>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
@@ -74,7 +75,7 @@ class Git(base.VCS):
if name != '' or email != '': # got something!
# guess missing info, if necessary
if name == '':
- name = _user.get_fallback_username()
+ name = _user.get_fallback_fullname()
if email == '':
email = _user.get_fallback_email()
return _user.create_user_id(name, email)
@@ -265,7 +266,7 @@ class Git(base.VCS):
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__])
diff --git a/libbe/storage/vcs/hg.py b/libbe/storage/vcs/hg.py
index d610352..b61e796 100644
--- a/libbe/storage/vcs/hg.py
+++ b/libbe/storage/vcs/hg.py
@@ -1,5 +1,6 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
# Ben Finney <benf@cybersource.com.au>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# Marien Zwart <marien.zwart@gmail.com>
# W. Trevor King <wking@drexel.edu>
@@ -82,14 +83,18 @@ class Hg(base.VCS):
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)
+ output = StringIO.StringIO()
+ if self.version_cmp(1,9):
+ req = mercurial.dispatch.request(fullargs, fout=output)
+ mercurial.dispatch.dispatch(req)
+ else:
+ stdout = sys.stdout
+ sys.stdout = output
+ mercurial.dispatch.dispatch(fullargs)
+ sys.stdout = stdout
os.chdir(cwd)
- sys.stdout = stdout
- return tmp_stdout.getvalue().rstrip('\n')
+ return output.getvalue().rstrip('\n')
def _vcs_get_user_id(self):
output = self._u_invoke_client(
@@ -251,7 +256,7 @@ class Hg(base.VCS):
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__])
diff --git a/libbe/storage/vcs/monotone.py b/libbe/storage/vcs/monotone.py
index e99a6ec..9f4e278 100644
--- a/libbe/storage/vcs/monotone.py
+++ b/libbe/storage/vcs/monotone.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2010-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2010-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
@@ -366,7 +367,7 @@ class Monotone (base.VCS):
def _vcs_changed(self, revision):
return self._parse_diff(self._diff(revision))
-
+
if libbe.TESTING == True:
base.make_vcs_testcase_subclasses(Monotone, sys.modules[__name__])
diff --git a/libbe/ui/__init__.py b/libbe/ui/__init__.py
index 227269c..45068b4 100644
--- a/libbe/ui/__init__.py
+++ b/libbe/ui/__init__.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py
index 5f42147..d5719a6 100644
--- a/libbe/ui/command_line.py
+++ b/libbe/ui/command_line.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
@@ -29,6 +30,8 @@ import libbe.command
import libbe.command.util
import libbe.version
import libbe.ui.util.pager
+import libbe.util.encoding
+
if libbe.TESTING == True:
import doctest
@@ -86,11 +89,11 @@ class CmdOptionParser(optparse.OptionParser):
if '_' in name: # reconstruct original option name
options[name.replace('_', '-')] = options.pop(name)
for name,value in options.items():
+ argument = None
+ option = self._option_by_name[name]
+ if option.arg != None:
+ argument = option.arg
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']
@@ -108,29 +111,36 @@ class CmdOptionParser(optparse.OptionParser):
if i+1 < len(args):
fragment = args[i+1]
self.complete(argument, fragment)
+ elif argument is not None:
+ value = self.process_raw_argument(argument=argument, value=value)
+ options[name] = value
for i,arg in enumerate(parsed_args):
+ 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')
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)
+ else:
+ value = self.process_raw_argument(argument=argument, value=arg)
+ parsed_args[i] = value
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)
+ raise libbe.command.UsageError(
+ command=self.command,
+ message='Missing required argument %s' % arg.metavar)
return (options, parsed_args)
def callback(self, option, opt, value, parser):
@@ -154,6 +164,16 @@ class CmdOptionParser(optparse.OptionParser):
print >> self.command.stdout, '\n'.join(comps)
raise CallbackExit
+ def process_raw_argument(self, argument, value):
+ if value == argument.default:
+ return value
+ if argument.type == 'string':
+ if not hasattr(self, 'argv_encoding'):
+ self.argv_encoding = libbe.util.encoding.get_argv_encoding()
+ return unicode(value, self.argv_encoding)
+ return value
+
+
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
@@ -271,6 +291,13 @@ def dispatch(ui, command, args):
'See http://docs.python.org/library/locale.html for details',
])
return 1
+ except libbe.command.UsageError, e:
+ print >> ui.io.stdout, 'Usage Error:\n', e
+ if e.command:
+ print >> ui.io.stdout, e.command.usage()
+ print >> ui.io.stdout, 'For usage information, try'
+ print >> ui.io.stdout, ' be help %s' % e.command_name
+ return 1
except libbe.command.UserError, e:
print >> ui.io.stdout, 'ERROR:\n', e
return 1
@@ -296,15 +323,19 @@ def main():
options,args = parser.parse_args()
except CallbackExit:
return 0
- except libbe.command.UserError, e:
- if str(e).endswith('COMMAND'):
+ except libbe.command.UsageError, e:
+ if isinstance(e.command, BE):
# no command given, print usage string
- print >> ui.io.stdout, 'ERROR:'
- print >> ui.io.stdout, be.usage(), '\n', e
+ print >> ui.io.stdout, 'Usage Error:\n', e
+ print >> ui.io.stdout, be.usage()
print >> ui.io.stdout, 'For example, try'
print >> ui.io.stdout, ' be help'
else:
- print >> ui.io.stdout, 'ERROR:\n', e
+ print >> ui.io.stdout, 'Usage Error:\n', e
+ if e.command:
+ print >> ui.io.stdout, e.command.usage()
+ print >> ui.io.stdout, 'For usage information, try'
+ print >> ui.io.stdout, ' be help %s' % e.command_name
return 1
command_name = args.pop(0)
diff --git a/libbe/ui/util/__init__.py b/libbe/ui/util/__init__.py
index 227269c..45068b4 100644
--- a/libbe/ui/util/__init__.py
+++ b/libbe/ui/util/__init__.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
diff --git a/libbe/ui/util/editor.py b/libbe/ui/util/editor.py
index dcf73c8..206e9c4 100644
--- a/libbe/ui/util/editor.py
+++ b/libbe/ui/util/editor.py
@@ -1,5 +1,6 @@
# Bugs Everywhere, a distributed bugtracker
-# Copyright (C) 2008-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2008-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/libbe/ui/util/pager.py b/libbe/ui/util/pager.py
index d82dcef..de3b3fc 100644
--- a/libbe/ui/util/pager.py
+++ b/libbe/ui/util/pager.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
diff --git a/libbe/ui/util/user.py b/libbe/ui/util/user.py
index 35665e4..261ecdf 100644
--- a/libbe/ui/util/user.py
+++ b/libbe/ui/util/user.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
@@ -28,32 +29,54 @@ are human-readable tags refering to objects.
try:
from email.utils import formataddr, parseaddr
-except ImportErrror: # adjust to old python < 2.5
+except ImportErrror: # adjust to old python < 2.5
from email.Utils import formataddr, parseaddr
import os
+try:
+ import pwd
+except ImportError: # handle non-Unix systems
+ pwd = None
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"]:
+ for env in ['LOGNAME', 'USERNAME']:
+ if os.environ.has_key(env):
+ name = os.environ[env]
+ break
+ if name is None and pwd:
+ pw_ent = pwd.getpwuid(os.getuid())
+ name = pw_ent.pw_name
+ assert name is not None
+ return name
+
+def get_fallback_fullname():
+ """Return a full name extracted from environmental variables.
+ """
+ name = None
+ for env in ['FULLNAME']:
if os.environ.has_key(env):
name = os.environ[env]
break
- assert name != None
+ if pwd and not name:
+ pw_ent = pwd.getpwuid(os.getuid())
+ name = pw_ent.pw_gecos.split(',', 1)[0]
+ if not name:
+ name = get_fallback_username()
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)
+ return os.getenv('EMAIL') or '%s@%s' % (
+ get_fallback_username(), gethostname())
def create_user_id(name, email=None):
"""Create a user ID string from given `name` and `email` strings.
@@ -122,7 +145,7 @@ def get_user_id(storage=None):
user = storage.get_user_id()
if user != None:
return user
- name = get_fallback_username()
+ name = get_fallback_fullname()
email = get_fallback_email()
user = create_user_id(name, email)
return user
diff --git a/libbe/util/__init__.py b/libbe/util/__init__.py
index b9166f0..78f615f 100644
--- a/libbe/util/__init__.py
+++ b/libbe/util/__init__.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
diff --git a/libbe/util/encoding.py b/libbe/util/encoding.py
index 5950bb9..22a2e30 100644
--- a/libbe/util/encoding.py
+++ b/libbe/util/encoding.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2008-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2008-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
@@ -49,11 +50,14 @@ def get_input_encoding():
return get_encoding()
def get_output_encoding():
- return get_encoding()
+ return sys.__stdout__.encoding or get_encoding()
def get_filesystem_encoding():
return get_encoding()
+def get_argv_encoding():
+ return get_encoding()
+
def known_encoding(encoding):
"""
>>> known_encoding("highly-unlikely-encoding")
diff --git a/libbe/util/id.py b/libbe/util/id.py
index 8d8c75c..c14dd90 100644
--- a/libbe/util/id.py
+++ b/libbe/util/id.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2008-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2008-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
@@ -224,8 +225,8 @@ def _truncate(uuid, other_uuids, min_length=3):
uuid : str
The UUID to truncate.
other_uuids : list of str
- The other UUIDs which the truncation *might* (but doesn't) refer
- to.
+ The other UUIDs which the truncation *might* refer to. May
+ contain `uuid`.
min_length : int
Avoid rapidly outdated truncations, even if they are unique now.
@@ -255,8 +256,8 @@ def _expand(truncated_id, common, other_ids):
`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.
+ The other UUIDs which the truncation *might* refer to. May
+ contain `uuid`.
Raises
------
diff --git a/libbe/util/plugin.py b/libbe/util/plugin.py
index b6200fc..034b750 100644
--- a/libbe/util/plugin.py
+++ b/libbe/util/plugin.py
@@ -1,4 +1,5 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# Marien Zwart <marien.zwart@gmail.com>
# W. Trevor King <wking@drexel.edu>
diff --git a/libbe/util/subproc.py b/libbe/util/subproc.py
index b10f84a..fde5af5 100644
--- a/libbe/util/subproc.py
+++ b/libbe/util/subproc.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
@@ -21,6 +22,7 @@ Functions for running external commands in subprocesses.
from subprocess import Popen, PIPE
import sys
+import types
import libbe
from encoding import get_encoding
@@ -45,7 +47,8 @@ class CommandError(Exception):
self.stderr = stderr
def invoke(args, stdin=None, stdout=PIPE, stderr=PIPE, expect=(0,),
- cwd=None, unicode_output=True, verbose=False, encoding=None):
+ cwd=None, shell=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
@@ -54,18 +57,29 @@ def invoke(args, stdin=None, stdout=PIPE, stderr=PIPE, expect=(0,),
"""
if cwd == None:
cwd = '.'
+ if isinstance(shell, types.StringTypes):
+ list_args = ' '.split(args) # sloppy, but just for logging
+ str_args = args
+ else:
+ list_args = args
+ str_args = ' '.join(args) # sloppy, but just for logging
if verbose == True:
- print >> sys.stderr, '%s$ %s' % (cwd, ' '.join(args))
+ print >> sys.stderr, '%s$ %s' % (cwd, str_args)
try :
if _POSIX:
- q = Popen(args, stdin=PIPE, stdout=stdout, stderr=stderr, cwd=cwd)
+ if shell is None:
+ shell = False
+ q = Popen(args, stdin=PIPE, stdout=stdout, stderr=stderr,
+ shell=shell, cwd=cwd)
else:
assert _MSWINDOWS==True, 'invalid platform'
+ if shell is None:
+ shell = True
# 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)
+ shell=shell, cwd=cwd)
except OSError, e:
- raise CommandError(args, status=e.args[0], stderr=e)
+ raise CommandError(list_args, status=e.args[0], stderr=e)
stdout,stderr = q.communicate(input=stdin)
status = q.wait()
if unicode_output == True:
@@ -78,18 +92,18 @@ def invoke(args, stdin=None, stdout=PIPE, stderr=PIPE, expect=(0,),
if verbose == True:
print >> sys.stderr, '%d\n%s%s' % (status, stdout, stderr)
if status not in expect:
- raise CommandError(args, status, stdout, stderr)
+ raise CommandError(list_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.
+ """Simple interface for executing POSIX-style pipes.
+
+ Based on the `subprocess` module. The only complication is the
+ adaptation of `subprocess.Popen._communicate` 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
diff --git a/libbe/util/tree.py b/libbe/util/tree.py
index f676b0b..6e2ffec 100644
--- a/libbe/util/tree.py
+++ b/libbe/util/tree.py
@@ -1,5 +1,6 @@
# Bugs Everywhere, a distributed bugtracker
-# Copyright (C) 2008-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2008-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/libbe/util/utility.py b/libbe/util/utility.py
index 3f9a1b0..0bdacb6 100644
--- a/libbe/util/utility.py
+++ b/libbe/util/utility.py
@@ -1,4 +1,5 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
@@ -28,6 +29,10 @@ import shutil
import tempfile
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 libbe
if libbe.TESTING == True:
diff --git a/libbe/version.py b/libbe/version.py
index b695b13..29f4eed 100644
--- a/libbe/version.py
+++ b/libbe/version.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python
-# Copyright (C) 2009-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/misc/xml/be-mail-to-xml b/misc/xml/be-mail-to-xml
index adaaf5e..6ac68b1 100755
--- a/misc/xml/be-mail-to-xml
+++ b/misc/xml/be-mail-to-xml
@@ -1,5 +1,6 @@
#!/usr/bin/env python
-# Copyright (C) 2009-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/misc/xml/be-xml-to-mbox b/misc/xml/be-xml-to-mbox
index d947d1c..a7aa916 100755
--- a/misc/xml/be-xml-to-mbox
+++ b/misc/xml/be-xml-to-mbox
@@ -1,5 +1,6 @@
#!/usr/bin/env python
-# Copyright (C) 2009-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
diff --git a/release.py b/release.py
index 7d6b150..9e80599 100755
--- a/release.py
+++ b/release.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
#
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
diff --git a/test.py b/test.py
index 1e7d726..da033fc 100644
--- a/test.py
+++ b/test.py
@@ -1,4 +1,5 @@
# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+# Chris Ball <cjb@laptop.org>
# Marien Zwart <marien.zwart@gmail.com>
# W. Trevor King <wking@drexel.edu>
#
diff --git a/update_copyright.py b/update_copyright.py
index 9e57c6e..c4f606b 100755
--- a/update_copyright.py
+++ b/update_copyright.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
#
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2011 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#